@testrelic/playwright-analytics 2.16.1-next.132 → 2.16.1-next.138

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/reporter.ts","../src/streaming-writer.ts","../src/cloud-config.ts","../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-js-actions.ts","../src/html-js-console.ts","../src/html-js-artifacts.ts","../src/html-js-visual.ts","../src/html-js-dom.ts","../src/html-js-layout.ts","../src/html-js-api.ts","../src/html-css-visual.ts","../src/html-css-layout.ts","../src/html-css-api.ts","../src/html-template.ts","../src/browser-open.ts","../src/jsonl-stream.ts","../src/report-server-routes.ts","../src/report-server.ts","../src/artifact-server.ts","../src/html-report.ts","../src/visual-metrics.ts","../src/artifact-manager.ts","../src/visual-expect.ts","../src/visual-capture.ts","../src/ansi.ts","../src/artifact-index.ts","../src/gitignore-manager.ts","../src/watchdog.ts","../src/timeline-builder.ts","../src/summary-builder.ts","../src/console-summary.ts","../src/action-step-collector.ts","../src/cloud-auth.ts","../src/git-metadata.ts","../src/run-type-env.ts","../src/cloud-deadline.ts","../src/cloud-upload.ts","../src/cloud-queue.ts","../src/cloud-client.ts","../src/test-data-extractor.ts","../src/cloud-farm-detect.ts","../src/cloud-artifact-upload.ts","../src/cloud-reporter.ts","../src/cloud-receipt.ts"],"names":["REPORT_DIR_NAME","generateStreamingTestId","filePath","titlePath","project","retryIndex","input","createHash","generateRetryGroupKey","StreamingWriter","outputDir","join","mkdirSync","testId","data","files","testDir","metaPath","tmpPath","json","writeFileSync","renameSync","err","errorMsg","src","dest","copyFileSync","unlinkSync","entries","compact","_tp","_p","rest","summary","manifest","CONFIG_DIR","CONFIG_FILENAME","LEGACY_CONFIG_FILENAME","MAX_WALK_UP_LEVELS","DANGEROUS_KEYS","DEFAULT_CLOUD_CONFIG","DURATION_UNITS","discoverConfigFile","startDir","currentDir","resolve","i","candidate","existsSync","statSync","legacyCandidate","parentDir","dirname","parseConfigFile","content","readFileSync","parsed","isSafeObject","obj","key","val","resolveEnvVar","value","bracketMatch","dollarMatch","resolveEnvVars","result","parseDuration","match","amount","unit","multiplier","mergeCloudConfig","fileConfig","reporterOptions","target","cloud","repoSection","queue","ms","resolved","envApiKey","envEndpoint","isValidEndpointUrl","envUpload","envTimeout","config","isValidCloudConfig","DEFAULT_REDACTION_PATTERNS","DEFAULT_REDACT_HEADERS","DEFAULT_REDACT_BODY_FIELDS","resolveApiConfig","options","resolveConfig","isValidConfig","createError","ErrorCode","outputPath","fileConfigPath","resolvedFileConfig","cloudConfig","SCHEMA_VERSION","extractCodeSnippet","line","contextLines","lines","startLine","endLine","snippetLines","marker","lineNum","createRedactor","patterns","text","pattern","escaped","flags","cloned","detectGitHubActions","env","detectGitLabCI","detectJenkins","branch","detectCircleCI","detectBitbucketPipelines","runUrl","detectors","detectCI","envVars","detect","CSS","LOGO_SVG_RAW","LOGO_SVG","JS_RENDER","JS_NETWORK","JS_FILTERS","JS_INTERACTIONS","JS_ACTIONS","JS_CONSOLE","JS_ARTIFACTS","JS_VISUAL","JS_DOM","JS_LAYOUT","JS_ROUTER","JS_API","CSS_VISUAL","CSS_DOM","CSS_LAYOUT","CSS_DETAIL_PAGE","CSS_API","JS","renderHtmlDocument","reportJson","serverPort","manifestJson","safeJson","safeManifestJson","renderStreamingHtmlDocument","summaryJson","indexJson","safeSummary","safeIndex","safeManifest","summaryObj","streamingData","JS_STREAMING","openInBrowser","platform","file","args","execFile","tmpdir","readJsonlPage","page","pageSize","knownTotal","skip","items","lineCount","rl","createInterface","createReadStream","total","totalPages","SAFE_ID_PATTERN","TIMESTAMP_PATTERN","MAX_PAGE_SIZE","sendJson","res","status","body","setCorsHeaders","readJsonFile","getDirSize","dirPath","size","readdirSync","entry","fullPath","handleHealth","_req","reportDir","startTime","index","handleSummary","handleTests","req","params","statusFilter","fileFilter","searchQuery","tagFilter","sortField","sortOrder","filtered","t","tag","a","b","cmp","totalItems","startIdx","tests","handleTestDetail","legacyPath","handleTestDataFile","dataType","jsonlPath","meta","handleFiles","fileMap","stats","s","handleArtifactsList","artifactsDir","runs","totalSizeBytes","runDir","testDirs","e","handleArtifactsDeleteAll","deletedCount","freedBytes","rmSync","handleArtifactDelete","folderName","handleShutdown","server","serveStaticFile","ext","extname","contentType","MIME_TYPES","range","parseByteRange","pipeOrFail","stream","header","rawStart","rawEnd","start","end","suffix","DEFAULT_PORT","MAX_PORT_ATTEMPTS","INACTIVITY_TIMEOUT_MS","startReportServer","reject","startPort","inactivityTimer","currentAttempt","resetTimer","htmlReportPath","htmlFile","f","createServer","pathname","testDataMatch","testMatch","deleteMatch","relPath","tryListen","port","errorHandler","addr","startArtifactServer","handle","generateHtmlReport","report","writeHtmlReport","serverHandle","isStreamingMode","html","htmlDir","compactPath","htmlPath","shutdownExistingServers","spawnDetachedServer","keepAlive","cleanup","updatedHtml","tmpRewrite","absolutePath","detectExistingServer","controller","timer","resp","r","cliPath","p","resolvePort","child","spawn","stderr","timeout","chunk","PNG_SIGNATURE","IHDR_WIDTH_OFFSET","IHDR_HEIGHT_OFFSET","PNG_HEADER_BYTES","readPngDimensions","width","height","PIXELS_DIFFERENT_RE","SIZE_MISMATCH_RE","isSizeMismatchFailure","message","SNAPSHOT_NAME_RE","parseDiffMessage","pixels","count","sizes","ew","eh","aw","ah","v","name","computeDiffRatio","diffPixels","dimensions","COPY_CONCURRENCY","activeCopies","pendingCopies","enqueueCopy","copyFile","inflightCopies","flushPendingArtifactCopies","generateTimestampFolderName","now","pad","n","base","sanitizeFolderName","title","ID_SUFFIX_LENGTH","buildArtifactFolderName","testTitle","retryCount","copyArtifacts","attachments","runTimestamp","screenshot","video","trace","artifactSegments","artifactDir","relativeParts","destName","VISUAL_DIR","STEM_HASH_LENGTH","buildVisualFolderName","stem","hash","readDimensions","fd","openSync","bytesRead","readSync","closeSync","VISUAL_ROLES","DOM_ROLES","copyOneComparison","visualDir","relativePrefix","folder","destDir","paths","copiedAny","role","dom","measured","ratio","copyVisualArtifacts","candidates","results","copied","VISUAL_DOM_ATTACHMENT","VISUAL_META_ATTACHMENT","VISUAL_SUFFIX_RE","parseVisualMeta","byStem","attachment","record","toMetaRecord","VALID_STATUSES","parseVisualDom","groupBySnapshotStem","groups","group","isCredibleComparison","deriveStatus","updatingSnapshots","failureMessage","selectMetricsTarget","failed","snapshotName","matches","parseVisualAttachments","updating","vouched","applyMetrics","failedStems","c","stemOf","ANSI_PATTERN","stripAnsi","stripAnsiSafe","MANIFEST_SCHEMA_VERSION","classifyFileType","basename","classifyVisualFileType","scanVisualFolder","snapshotDir","relativeBase","stat","scanTestFolder","runFolder","testName","snapshot","scanRunFolder","testEntry","timestamp","buildArtifactManifest","currentRunTimestamp","legacyTests","legacySize","run","sum","MAX_WALK_DEPTH","findGitRoot","dir","depth","parent","ensureGitignore","gitRoot","gitignorePath","relativePath","relative","l","appendFileSync","TIMED_OUT","CLOUD_INIT_TIMEOUT_MS","CLOUD_FINALIZE_TIMEOUT_MS","REPORT_FINALIZE_TIMEOUT_MS","CLOUD_DISPOSE_TIMEOUT_MS","withTimeout","operation","timeoutMs","label","buildUnifiedTimeline","allSteps","test","identity","buildTestIdentity","testResult","toTestResult","navSteps","buildNavigationSteps","apiSteps","buildApiCallSteps","compareSteps","recalculateNavigationDurations","step","steps","nav","assertions","call","linkedAssertions","response","parseBody","timeA","timeB","typeOrder","typeA","typeB","extractCallNumber","callId","navTime","nextStepTime","j","endTime","nearestRankPercentile","sorted","percentile","stripUrlQuery","rawUrl","getStatusRange","statusCode","buildEnrichedSummary","timelineLength","passed","flaky","skipped","timedout","allApiCalls","totalApiCalls","apiUrlSet","methodCounts","statusRanges","responseTimes","apiResponseTime","acc","totalAssertions","passedAssertions","failedAssertions","assertion","totalNavigations","navUrlSet","totalActionSteps","actionStepsByCategory","countActions","visualChecks","visualFailures","visualNew","visualDomChanges","comparison","padding","printConsoleSummary","quiet","top","bottom","blank","output","parts","methodParts","method","statusParts","catParts","catMap","catStr","INCLUDED_CATEGORIES","mapCategory","pwCategory","convertStep","testStartTime","children","videoOffsetMs","collectActionSteps","LOCALHOST_HOSTS","enforceHttps","endpoint","url","HEALTH_CHECK_TIMEOUT_MS","healthCheck","sleep","parseCloudError","error","details","detailMessages","d","exchangeToken","apiKey","retryAfter","waitMs","retryResponse","isAuthError","refreshAccessToken","currentRefreshToken","resolveRepo","accessToken","gitId","displayName","requestBody","responseBody","GIT_TIMEOUT_MS","execGit","command","cwd","execSync","collectGitMetadata","commitSha","commitMessage","commitAuthor","rawRemoteUrl","getRemoteUrl","remoteUrl","normalizeGitRemoteUrl","originUrl","remotes","firstRemote","normalized","deriveRepoDisplayName","normalizedUrl","segments","readPackageJsonName","raw","pkg","deriveNonGitProjectId","pkgName","ALLOWED","resolveSdkRunTypeFromEnv","init","putDeadlineMs","sizeBytes","scaled","Budget","totalMs","requestMs","GZIP_THRESHOLD_BYTES","RETRY_DELAYS_MS","MAX_RETRIES","DEFAULT_REQUEST_TIMEOUT_MS","MAX_BATCH_PAYLOAD_BYTES","RUN_NONAPI_SLUGS","deriveRunTestType","counts","proto","primaryProtocol","max","mostCommon","slug","buildUploadPayload","repoGitId","git","ci","cloudFarm","environment","runType","typeSignal","apiSignal","effectiveTests","bodyBytes","toApiReportCalls","records","toNetworkRequestsFromApiCalls","ts","toProtocolAssertions","fmt","expr","kind","detail","stripNulls","retryWithBackoff","onTokenRefresh","budget","attempt","slice","newToken","refreshedInit","gzipAsync","promisify","gzip","stripOversizeTests","payload","dropApiCalls","_c","_n","apiCalls","HEAVY_TEST_KEYS","stripToMinimalTests","heavy","out","k","stripAllTests","heavyRunKeys","prepareBody","maxBytes","headers","carrier","testCount","over","limitMb","gz","backing","uploadBatchRun","initRealtimeRun","uploadTestResult","cloudRunId","testData","finalizeRun","finalizeRunFireAndForget","startedAt","QUEUE_ENTRY_VERSION","FLUSH_BATCH_SIZE","errorMessage","isPermanentHttpFailure","writeToQueue","queueDirectory","runId","type","reason","targetEndpoint","filename","flushQueue","batches","batch","queueEntry","isValidQueueEntry","flushBody","prepared","flushHeaders","cleanupExpiredQueue","maxAge","queuedAt","queuedTime","TOKEN_REFRESH_BUFFER_MS","PROJECT_CACHE_TTL_MS","HEALTH_CHECK_INTERVAL_MS","FLUSH_TIMEOUT_MS","DASHBOARD_SETTINGS_URL","unrefTimeout","CloudClient","tokenResult","expiresAt","refreshResult","code","effectiveGitId","cached","cachePath","cache","currentKeyHash","repoId","cacheDir","queueDir","addStatusMix","extractTestData","annotations","isSkipped","navigations","apiAssertions","networkRequestsFile","networkRequestsCount","consoleLogsFile","consoleLogsCount","apiCallsFile","apiCallsCount","apiStatusMix","payloadAttachments","ATTACHMENT_NAME","attachmentParsed","att","isTestRelicFilePayload","isTestRelicDataPayload","payloadLogs","detectCloudFarm","explicitPlatform","normalizePlatform","ws","UPLOAD_CONCURRENCY","JSON_REQUEST_TIMEOUT_MS","CONTENT_TYPE_MAP","activeUploads","pendingUploads","acquireSlot","releaseSlot","getContentType","getFileSize","requestUploadUrl","request","putFileToPresignedUrl","presignedUrl","deadlineMs","nodeStream","webStream","Readable","confirmUpload","artifactId","uploadArtifact","maxSizeMb","urlResult","confirmed","uploadArtifacts","requests","promises","getEffectiveGitId","cloudClient","initCloudRun","uploadStrategy","testRunId","gitMeta","ciMeta","VISUAL_UPLOAD_ROLES","DEFAULT_UPLOAD_ARTIFACTS","DEFAULT_ARTIFACT_MAX_SIZE_MB","NO_ARTIFACTS","uploadRunArtifacts","artifactEntries","visualResult","requestToEntry","visualKeys","absPath","comparisons","vi","uploadResults","keysByTest","uploadResult","existing","perTest","perComparison","keys","uploaded","finalizeAndUpload","completedAt","totalDuration","outcome","noteFailure","tokenValid","gitMetaForFinalize","finalizeTypeSignal","finalizePayload","finalized","artifactKeyMap","visualKeyMap","artifactOutcome","batchGit","batchCi","batchGitId","enrichedReport","injectVisualKeys","injectArtifactKeys","testsWithVisualKeys","failure","queueGit","queueCi","queueGitId","queuePayload","withKeys","enrichedTimeline","changed","rewritten","keyMap","KNOWN_STRATEGIES","UNATTEMPTED_RECEIPT","describeLocalMode","buildInitialReceipt","strategy","isRealtime","applyFinalizeOutcome","receipt","acknowledged","readJsonlSync","item","errorHeadline","informative","first","second","mapPlaywrightStatus","generateTestId","suiteName","getSuiteName","getRetryStatus","passedIndex","statusRangeKey","statusMixOf","mix","firstFailedAssertion","stringifyAssertionValue","countAssertions","accumulateActionCategories","into","buildAggResponseTime","times","MAX_AGG_RESPONSE_SAMPLES","MAX_AGG_UNIQUE_URLS","buildAggContribution","apiCallsRaw","actions","urlSet","codeRaw","rtRaw","emptyStreamingAgg","detectSource","detectOsName","osPlatform","getBrowserDisplayName","use","browserName","channel","ch","bn","defaultBrowserType","dbt","getTestType","tags","inferred","tagTypes","normalizedPath","pathTypes","API_CONFIG_ANNOTATION","serializeApiConfig","_key","TestRelicReporter","testKey","contribution","_suite","updateSnapshots","randomUUID","totalTests","shutdownHandler","partialSummary","token","initialized","embeddedShutdownHandler","_result","lastResult","extracted","firstError","redact","pwFile","assertionLoc","useAssertionLoc","errorFile","errorLine","codeSnippet","specFile","baseTestType","testType","isFlaky","retryStatus","expectedStatus","actualStatus","artifacts","lastTitle","visual","browser","collectedTest","batchConsoleLogs","batchNetworkRequests","batchApiCallsRaw","batchApiCalls","batchNetworkFromApi","batchProtocolAssertions","streamId","isFinalAttempt","detailData","indexEntry","withDiff","fa","startedAtTime","hasNonSkippedTests","artifactManifest","manifestPath","timeline","lastByTestId","finalAttempts","cloudTests","client","pwResult","bestByTestKey","indexFinalCount","bestRetry","folded","m","cat","u","rt","validation","enrichedSummary","writer","settledSummary","_streamingSummary","usage","heapMB","heapRatio","ct","switchApiCallsRaw","networkRequests","consoleLogs"],"mappings":"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AAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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,EAAAA,CAAe,CAAA;AAAA;AAAA;AAAA;AAAA,8BAAA,CAAA,CAMfC,EAAAA,CAAW,CAAA;AAAA;AAAA;AAAA;ACRjB,8BAAA,CAAA,CAAA,IAAMC,EAAAA,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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACFxB,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;ACDnB,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;ACCnB,CAAA,CAAA,IAAMC,EAAAA,CAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACWrB,CAAA,CAAA,IAAMC,EAAAA,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;ACFlB,CAAA,CAAA,IAAMC,EAAAA,CAAS;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACJf,CAAA,CAAA,IAAMC,EAAAA,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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA,CA4TZC,EAAAA,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;AC3TlB,CAAA,CAAA,IAAMC,EAAAA,CAAS;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACEf,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,CAAA,CA0CbC,EAAAA,CAAU;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AC/ChB,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,CAAA,CAuIbC,EAAAA,CAAkB;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;ACvIxB,CAAA,CAAA,IAAMC,EAAAA,CAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","file":"reporter-entry.js","sourcesContent":["/**\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, readFileSync } from 'node:fs';\nimport { dirname, join, relative, resolve } from 'node:path';\nimport { platform as osPlatform } from 'node:os';\nimport type {\n ActionStep,\n ApiAssertion,\n ApiCallsByStatusRange,\n FailedAssertionSummary,\n ReporterConfig,\n TestRunReport,\n Summary,\n TimelineStep,\n TestResult as TRTestResult,\n TestArtifacts,\n VisualComparison,\n NavigationAnnotation,\n FailureDiagnostic,\n TestStatus,\n TestType,\n ReportMode,\n TestIndexEntry,\n TestDetailData,\n StreamingReportSummary,\n StreamingReportManifest,\n StreamingFileStats,\n CapturedNetworkRequest,\n ApiCallRecord,\n ConsoleLogEntry as CoreConsoleLogEntry,\n} from '@testrelic/core';\nimport { StreamingWriter, generateStreamingTestId, generateRetryGroupKey } from './streaming-writer.js';\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, copyVisualArtifacts, generateTimestampFolderName, flushPendingArtifactCopies } from './artifact-manager.js';\nimport { parseVisualAttachments } from './visual-capture.js';\nimport { stripAnsi } from './ansi.js';\nimport { buildArtifactManifest } from './artifact-index.js';\nimport { ensureGitignore } from './gitignore-manager.js';\nimport {\n withTimeout,\n CLOUD_INIT_TIMEOUT_MS,\n CLOUD_FINALIZE_TIMEOUT_MS,\n REPORT_FINALIZE_TIMEOUT_MS,\n CLOUD_DISPOSE_TIMEOUT_MS,\n} from './watchdog.js';\nimport { buildUnifiedTimeline } from './timeline-builder.js';\nimport type { TimelineTestData } from './timeline-builder.js';\nimport { buildEnrichedSummary, stripUrlQuery, nearestRankPercentile } from './summary-builder.js';\nimport { printConsoleSummary } from './console-summary.js';\nimport { collectActionSteps } from './action-step-collector.js';\nimport { CloudClient } from './cloud-client.js';\nimport { uploadTestResult, finalizeRunFireAndForget, toApiReportCalls, toNetworkRequestsFromApiCalls, toProtocolAssertions } from './cloud-upload.js';\nimport type { TestResultForUpload } from './cloud-upload.js';\nimport { extractTestData } from './test-data-extractor.js';\nimport { initCloudRun, finalizeAndUpload } from './cloud-reporter.js';\nimport type { ArtifactEntry } from './cloud-reporter.js';\nimport { buildInitialReceipt, applyFinalizeOutcome, UNATTEMPTED_RECEIPT } from './cloud-receipt.js';\nimport type { CloudUploadReceipt, WithCloudUpload } from './cloud-receipt.js';\n\n/** The streaming summary as this reporter writes it — always carrying the upload receipt. */\ntype SummaryWithReceipt = WithCloudUpload<StreamingReportSummary>;\n\n/** Read all entries from a JSONL file synchronously. */\nfunction readJsonlSync(filePath: string): unknown[] {\n try {\n const content = readFileSync(filePath, 'utf-8');\n return content\n .split('\\n')\n .filter(line => line.length > 0)\n .map(line => { try { return JSON.parse(line); } catch { return null; } })\n .filter((item): item is unknown => item !== null);\n } catch {\n return [];\n }\n}\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 PwProjectUse {\n browserName?: string;\n channel?: string;\n isMobile?: boolean;\n [key: string]: unknown;\n}\n\ninterface PwFullProject {\n use: PwProjectUse;\n [key: string]: unknown;\n}\n\ninterface PwSuiteNode {\n project?(): PwFullProject | undefined;\n parent?: PwSuiteNode;\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 parent?: PwSuiteNode;\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 steps?: readonly PwTestStep[];\n}\n\ninterface PwTestStep {\n title: string;\n category: string;\n startTime: Date;\n duration: number;\n error?: { message?: string };\n steps: readonly PwTestStep[];\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\n/**\n * The one line of a failure worth putting in a list.\n *\n * Playwright's first line is the matcher wrapper — identical across every\n * failure of that kind. The informative line is the one after it.\n */\nexport function errorHeadline(message: string | null | undefined): string | null {\n if (!message) return null;\n const lines = message\n .split('\\n')\n .map((line) => line.trim())\n .filter(Boolean);\n if (lines.length === 0) return null;\n const informative = lines.filter((line) => !/^Error:\\s*expect\\(/.test(line));\n if (informative.length === 0) return lines[0] ?? null;\n\n // Playwright's matcher output is \"Expected: 5\" then \"Received: 4\" on separate\n // lines. Returning only the first left the row saying what was expected and\n // never what actually happened, which is the half that matters. Pair them\n // when the second line is the received value.\n const first = informative[0] as string;\n const second = informative[1];\n if (second !== undefined && /^Received:/.test(second) && /^Expected:/.test(first)) {\n return `${first} · ${second}`;\n }\n return first;\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 is already filter(Boolean)'d: ['project', 'file.spec.ts', ...suites..., 'test title']\n // <= 3 elements means no describe block (project, file, test)\n if (titlePath.length <= 3) 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\n/** Status-range bucket for a response code (local copy that returns a plain\n * string union, so it does not depend on the `ApiCallsByStatusRange` keyof type). */\nfunction statusRangeKey(code: number | null): '2xx' | '3xx' | '4xx' | '5xx' | 'error' {\n if (code === null) return 'error';\n if (code >= 200 && code < 300) return '2xx';\n if (code >= 300 && code < 400) return '3xx';\n if (code >= 400 && code < 500) return '4xx';\n if (code >= 500 && code < 600) return '5xx';\n return 'error';\n}\n\n/** Tally a list of raw API call records into a status mix. */\nfunction statusMixOf(records: readonly Record<string, unknown>[]): ApiCallsByStatusRange {\n const mix: ApiCallsByStatusRange = { '2xx': 0, '3xx': 0, '4xx': 0, '5xx': 0, error: 0 };\n const out = mix as unknown as Record<string, number>;\n for (const r of records) {\n const code = typeof r.responseStatusCode === 'number' ? r.responseStatusCode : null;\n out[statusRangeKey(code)]++;\n }\n return mix;\n}\n\n/**\n * Flatten the first failed assertion for the index row.\n *\n * Values are arbitrary JSON; they are stringified once here so the client\n * never has to, and truncated so one huge response body cannot bloat the\n * index every row of the grid is built from.\n */\nfunction firstFailedAssertion(\n assertions: readonly ApiAssertion[] | null | undefined,\n): FailedAssertionSummary | undefined {\n if (!assertions) return undefined;\n for (const a of assertions) {\n if (a.status !== 'failed') continue;\n return {\n type: a.type,\n expected: stringifyAssertionValue(a.expected),\n actual: stringifyAssertionValue(a.actual),\n ...(a.expression ? { expression: a.expression } : {}),\n };\n }\n return undefined;\n}\n\n/** One assertion value as a short display string. */\nfunction stringifyAssertionValue(v: unknown): string {\n if (v === undefined) return 'undefined';\n if (v === null) return 'null';\n let s: string;\n if (typeof v === 'string') s = v;\n else {\n try { s = JSON.stringify(v) ?? String(v); } catch { s = String(v); }\n }\n return s.length > 200 ? s.slice(0, 200) + '\\u2026' : s;\n}\n\n/** How many assertions in a list landed on the given outcome. */\nfunction countAssertions(\n assertions: readonly ApiAssertion[] | null | undefined,\n outcome: 'passed' | 'failed',\n): number {\n if (!assertions) return 0;\n let n = 0;\n for (const a of assertions) if (a.status === outcome) n++;\n return n;\n}\n\n/** Recursively tally action steps by category into `into` (streaming aggregate). */\nfunction accumulateActionCategories(steps: readonly ActionStep[], into: Record<string, number>): void {\n for (const step of steps) {\n into[step.category] = (into[step.category] ?? 0) + 1;\n if (step.children.length > 0) accumulateActionCategories(step.children, into);\n }\n}\n\n/** Build API response-time stats from accumulated per-call times (null if none). */\nfunction buildAggResponseTime(times: number[]): Summary['apiResponseTime'] {\n if (times.length === 0) return null;\n const sorted = [...times].sort((a, b) => a - b);\n const sum = sorted.reduce((acc, v) => acc + v, 0);\n return {\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// Hard caps so both the per-test contributions AND the folded run-level aggregates\n// stay bounded. `responseTimes` would otherwise grow O(total API calls) and the\n// unique-URL sets O(URL cardinality) on a long crawl. The caps are applied in BOTH\n// places — per attempt in buildAggContribution (URLs deduped into a Set, response-time\n// samples capped) and again in the finalize fold — so a single mega-test retained in\n// `streamingTestAgg` can't blow up memory before finalize. The caps are generous, so\n// percentiles and unique counts stay exact for any realistic run and only clamp\n// pathological volumes; the folded aggregates are O(cap), not O(run size).\nconst MAX_AGG_RESPONSE_SAMPLES = 200_000;\nconst MAX_AGG_UNIQUE_URLS = 100_000;\n\n/** A single test attempt's contribution to the run-level streaming aggregates.\n * Stored per-test (highest retry wins) in `streamingTestAgg` and folded at finalize\n * for FINAL attempts only, so retries never inflate the run-level stats. */\ninterface StreamingAggContribution {\n retryIndex: number;\n apiCalls: number;\n apiUrls: string[];\n methodCounts: Record<string, number>;\n statusRanges: Record<string, number>;\n responseTimes: number[];\n passedAssertions: number;\n failedAssertions: number;\n assertions: number;\n navigations: number;\n navUrls: string[];\n networkRequests: number;\n consoleLogs: number;\n actionSteps: number;\n actionCategories: Record<string, number>;\n}\n\n/** Compute one test attempt's contribution to the streaming run aggregates from data\n * already in hand. `apiCallsRaw` is the parsed JSONL for the attempt; non-finite\n * status codes / response times are skipped so a NaN never poisons the stats. */\nfunction buildAggContribution(\n retryIndex: number,\n apiCallsRaw: Record<string, unknown>[] | undefined,\n apiAssertions: ReadonlyArray<{ status?: string }>,\n navigations: ReadonlyArray<{ url?: string | null }>,\n networkRequestsCount: number,\n consoleLogsCount: number,\n actions: readonly ActionStep[] | null,\n): StreamingAggContribution {\n const c: StreamingAggContribution = {\n retryIndex,\n apiCalls: apiCallsRaw?.length ?? 0,\n apiUrls: [],\n methodCounts: {},\n statusRanges: { '2xx': 0, '3xx': 0, '4xx': 0, '5xx': 0, error: 0 },\n responseTimes: [],\n passedAssertions: 0,\n failedAssertions: 0,\n assertions: apiAssertions.length,\n navigations: navigations.length,\n navUrls: [],\n networkRequests: networkRequestsCount,\n consoleLogs: consoleLogsCount,\n actionSteps: actions?.length ?? 0,\n actionCategories: {},\n };\n if (apiCallsRaw) {\n // Dedup URLs into a Set and cap response-time samples WITHIN this attempt's\n // contribution — not just at the finalize fold. The contribution is retained in\n // streamingTestAgg until finalize, so without per-test bounds a single long crawl\n // (e.g. 1M calls over 5 URLs) would retain 1M url strings here; deduping per test\n // keeps it O(unique URLs) — matching the pre-streaming Set-based accounting.\n const urlSet = new Set<string>();\n for (const call of apiCallsRaw) {\n const method = typeof call.method === 'string' ? call.method : 'OTHER';\n c.methodCounts[method] = (c.methodCounts[method] ?? 0) + 1;\n const url = typeof call.url === 'string' ? call.url : null;\n if (url && urlSet.size < MAX_AGG_UNIQUE_URLS) urlSet.add(stripUrlQuery(url));\n const codeRaw = typeof call.responseStatusCode === 'number' ? call.responseStatusCode\n : typeof call.statusCode === 'number' ? call.statusCode : null;\n const code = codeRaw !== null && Number.isFinite(codeRaw) ? codeRaw : null;\n c.statusRanges[statusRangeKey(code)]++;\n const rtRaw = typeof call.responseTimeMs === 'number' ? call.responseTimeMs\n : typeof call.durationMs === 'number' ? call.durationMs : null;\n if (rtRaw !== null && Number.isFinite(rtRaw) && c.responseTimes.length < MAX_AGG_RESPONSE_SAMPLES) {\n c.responseTimes.push(rtRaw);\n }\n }\n c.apiUrls = Array.from(urlSet);\n }\n for (const assertion of apiAssertions) {\n if (assertion.status === 'passed') c.passedAssertions++;\n else c.failedAssertions++;\n }\n const navUrlSet = new Set<string>();\n for (const nav of navigations) {\n if (nav.url && navUrlSet.size < MAX_AGG_UNIQUE_URLS) navUrlSet.add(nav.url);\n }\n c.navUrls = Array.from(navUrlSet);\n if (actions) accumulateActionCategories(actions, c.actionCategories);\n return c;\n}\n\n/** Empty run-level aggregate accumulator used by the finalize fold. */\nfunction emptyStreamingAgg(): {\n apiUrlSet: Set<string>;\n methodCounts: Record<string, number>;\n statusRanges: Record<string, number>;\n responseTimes: number[];\n passedAssertions: number;\n failedAssertions: number;\n navUrlSet: Set<string>;\n actionCategoryCounts: Record<string, number>;\n volApiCalls: number;\n volAssertions: number;\n volNavigations: number;\n volNetworkRequests: number;\n volConsoleLogs: number;\n volActionSteps: number;\n} {\n return {\n apiUrlSet: new Set<string>(),\n methodCounts: {},\n statusRanges: { '2xx': 0, '3xx': 0, '4xx': 0, '5xx': 0, error: 0 },\n responseTimes: [],\n passedAssertions: 0,\n failedAssertions: 0,\n navUrlSet: new Set<string>(),\n actionCategoryCounts: {},\n volApiCalls: 0,\n volAssertions: 0,\n volNavigations: 0,\n volNetworkRequests: 0,\n volConsoleLogs: 0,\n volActionSteps: 0,\n };\n}\n\nfunction detectSource(ci: import('@testrelic/core').CIMetadata | null): string {\n if (process.env.BROWSERSTACK_USERNAME) return 'BrowserStack';\n if (!ci) return 'Local';\n const map: Record<string, string> = {\n 'github-actions': 'GitHub Actions',\n 'gitlab-ci': 'GitLab CI',\n 'jenkins': 'Jenkins',\n 'circleci': 'CircleCI',\n 'bitbucket-pipelines': 'Bitbucket Pipelines',\n };\n return map[ci.provider] ?? 'CI';\n}\n\nfunction detectOsName(): string {\n const p = osPlatform();\n if (p === 'win32') return 'Windows';\n if (p === 'darwin') return 'macOS';\n return 'Linux';\n}\n\nfunction getBrowserDisplayName(test: Pick<PwTestCase, 'parent'>): string | undefined {\n const use = test.parent?.project?.()?.use;\n if (!use) return undefined;\n\n const { browserName, channel } = use;\n\n // Channel takes priority — it is a specific browser distribution (Chrome, Edge, etc.)\n if (channel) {\n const ch = channel.toLowerCase();\n if (ch === 'chrome' || ch === 'chrome-stable') return 'Chrome';\n if (ch.startsWith('chrome-beta')) return 'Chrome Beta';\n if (ch.startsWith('chrome-dev')) return 'Chrome Dev';\n if (ch.startsWith('chrome-canary')) return 'Chrome Canary';\n if (ch === 'msedge' || ch === 'msedge-stable') return 'Microsoft Edge';\n if (ch.startsWith('msedge-beta')) return 'Edge Beta';\n if (ch.startsWith('msedge-dev')) return 'Edge Dev';\n if (ch.startsWith('msedge-canary')) return 'Edge Canary';\n return channel; // fallback: show channel value as-is\n }\n\n if (browserName) {\n const bn = browserName.toLowerCase();\n if (bn === 'chromium') return 'Chromium';\n if (bn === 'firefox') return 'Firefox';\n if (bn === 'webkit') return 'WebKit';\n return browserName;\n }\n\n // defaultBrowserType is set by Playwright device presets (e.g. devices['Desktop Chrome'])\n // even though browserName is not explicitly set in project.use.\n const defaultBrowserType = use.defaultBrowserType as string | undefined;\n if (defaultBrowserType) {\n const dbt = defaultBrowserType.toLowerCase();\n if (dbt === 'chromium') return 'Chromium';\n if (dbt === 'firefox') return 'Firefox';\n if (dbt === 'webkit') return 'WebKit';\n return defaultBrowserType;\n }\n\n return undefined;\n}\n\nfunction getTestType(\n tags: string[],\n filePath: string,\n test: { parent?: PwSuiteNode },\n): TestType {\n // Step 1: Official Playwright project config — most authoritative signal,\n // requires zero changes to user test suites.\n // project().use.isMobile is set by all Playwright mobile device presets.\n // project().use.browserName is set when explicitly configured (e.g. devices['Desktop Chrome']).\n // NOTE: FullProject.use only contains user-specified values, NOT Playwright's resolved defaults.\n // A project with `use: {}` has browserName === undefined but still launches chromium by default.\n // Therefore any project that exists and is not mobile is treated as a browser/Web test.\n // 'api' type must come from explicit signals (tag @api or /api/ path segment).\n const project = test.parent?.project?.();\n let inferred: TestType = 'unknown';\n if (project !== undefined) {\n const use = project.use;\n if (use.isMobile === true) inferred = 'mobile';\n else inferred = 'e2e';\n }\n\n // Step 2: Explicit tag override — user intent beats project config.\n const tagTypes: TestType[] = ['e2e', 'api', 'unit', 'mobile'];\n for (const type of tagTypes) {\n if (tags.some((tag) => tag === `@${type}` || tag === type)) return type;\n }\n\n // Step 3: File path segment convention — opt-in folder structure.\n const normalizedPath = filePath.replace(/\\\\/g, '/');\n const pathTypes: TestType[] = ['e2e', 'api', 'unit'];\n for (const type of pathTypes) {\n if (normalizedPath.includes(`/${type}/`)) return type;\n }\n\n return inferred;\n}\n\n// Internal state for collected test data (v3: file-based heavy 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 visual: VisualComparison[] | null;\n apiAssertions: ApiAssertion[] | null;\n actions: ActionStep[] | null;\n // File-based heavy data references\n networkRequestsFile: string | null;\n networkRequestsCount: number;\n consoleLogsFile: string | null;\n consoleLogsCount: number;\n apiCallsFile: string | null;\n apiCallsCount: number;\n apiStatusMix: ApiCallsByStatusRange | null;\n // Environment metadata\n browser?: string;\n os?: string;\n source?: string;\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 private fixtureDataReceived = false;\n /** True once any test produced navigation data this run (pre-toggle-drop). */\n private navigationsCaptured = false;\n /** Set once the memory-pressure warning has fired, so it prints at most once per run. */\n private memoryPressureWarned = false;\n private testCount = 0;\n private cloudClient: CloudClient | null = null;\n private cloudRunId: string | null = null;\n private runTimestamp = '';\n\n /**\n * True when the run was started with `--update-snapshots`.\n *\n * A snapshot written under that flag was never compared against anything,\n * so it must not be reported as a pass. Nothing in the attachments\n * distinguishes a rewritten baseline from a first-time one, which is why\n * the flag has to be read from the run's config and carried down here.\n */\n private updatingSnapshots = false;\n\n /**\n * Whether the platform acknowledged this run — written into every summary as\n * `cloudUpload`. Seeded here so an interrupt before `onBegin` finishes still\n * writes a verdict rather than leaving the block missing; `onBegin` replaces it\n * with the real state, and the upload phase replaces it again with the outcome.\n */\n private cloudReceipt: CloudUploadReceipt = UNATTEMPTED_RECEIPT;\n\n // Artifact entries accumulated during onTestEnd for cloud upload (survives streaming cleanup)\n private pendingArtifactEntries: ArtifactEntry[] = [];\n\n // Environment metadata detected once in onBegin, applied to all tests\n private detectedOs = '';\n private detectedSource = '';\n\n // Lightweight per-test cloud upload buffer — persists through both embedded and streaming modes\n private cloudTestsBuffer: TestResultForUpload[] = [];\n\n // Streaming mode fields\n private activeReportMode: ReportMode = 'embedded';\n private streamingWriter: StreamingWriter | null = null;\n private testIndex: TestIndexEntry[] = [];\n private summaryCounters = {\n total: 0, passed: 0, failed: 0, flaky: 0,\n skipped: 0, timedOut: 0, interrupted: 0,\n totalApiCalls: 0, totalAssertions: 0, totalNavigations: 0,\n totalNetworkRequests: 0, totalConsoleLogs: 0, totalActionSteps: 0,\n // Visual totals for the run overview. Streaming builds its summary from\n // these counters rather than from buildEnrichedSummary, so they have to be\n // tallied here or the report's \"captured this run\" panel comes up empty.\n visualChecks: 0, visualFailures: 0, visualDomChanges: 0,\n };\n\n // Streaming-mode run-level aggregate inputs, stored PER TEST (highest-retry attempt\n // wins) and folded at finalize for FINAL attempts only — so the cloud payload built\n // from the streaming summary carries REAL API/assertion/navigation/action stats that\n // dedup retries exactly like the status counts (the per-attempt `isFinalAttempt`\n // flag can't dedup: at each onTestEnd the just-finished attempt is the last one in\n // `test.results`, so every attempt looks \"final\"). Keyed by `${filePath}::${titlePath}`\n // to match the bestByTestKey dedup used for status counts. One small entry per test.\n private streamingTestAgg = new Map<string, StreamingAggContribution>();\n\n /** Record a test attempt's aggregate contribution, keeping only the highest retry\n * per test so retries don't inflate the run-level stats (robust to out-of-order\n * attempt delivery: a later, lower retry never displaces a stored higher one). */\n private upsertAggContribution(testKey: string, contribution: StreamingAggContribution): void {\n const existing = this.streamingTestAgg.get(testKey);\n if (!existing || contribution.retryIndex > existing.retryIndex) {\n this.streamingTestAgg.set(testKey, contribution);\n }\n }\n\n constructor(options?: Partial<ReporterConfig>) {\n this.config = resolveConfig(options);\n this.apiConfig = resolveApiConfig(options);\n }\n\n async onBegin(config: PwFullConfig, _suite: PwSuite): Promise<void> {\n try {\n this.rootDir = config.rootDir;\n // SAFETY: Playwright's FullConfig does carry `updateSnapshots`, but this\n // reporter declares Playwright's types locally rather than importing\n // them, so the field arrives as `unknown` and is narrowed here.\n const updateSnapshots = config.updateSnapshots;\n // Only 'all' and 'changed' rewrite a baseline that already existed.\n // 'missing' is the DEFAULT for an ordinary run and writes a baseline only\n // where none was there — which is the 'new' case, not an update.\n this.updatingSnapshots = updateSnapshots === 'all' || updateSnapshots === 'changed';\n this.startedAt = new Date().toISOString();\n this.testRunId = this.config.testRunId ?? randomUUID();\n\n // Resolve report mode: auto detects based on suite size, streaming is default\n if (this.config.reportMode === 'auto') {\n const totalTests = _suite.allTests().length;\n this.activeReportMode = totalTests >= this.config.streamingThreshold ? 'streaming' : 'embedded';\n } else {\n this.activeReportMode = this.config.reportMode;\n }\n const totalTests = _suite.allTests().length;\n process.stderr.write(\n `[testrelic] Report mode: ${this.activeReportMode} (${totalTests} tests)\\n`,\n );\n\n // Initialize streaming writer if in streaming mode\n if (this.activeReportMode === 'streaming') {\n const outputDir = dirname(this.config.outputPath);\n this.streamingWriter = new StreamingWriter(outputDir);\n // Graceful shutdown: write partial index/summary and finalize cloud run on interrupt\n const shutdownHandler = (): void => {\n if (this.streamingWriter) {\n try {\n this.streamingWriter.writeIndex(this.testIndex as TestIndexEntry[]);\n const partialSummary: SummaryWithReceipt = {\n testRunId: this.testRunId ?? '',\n startedAt: this.startedAt,\n completedAt: new Date().toISOString(),\n totalDuration: Date.now() - new Date(this.startedAt).getTime(),\n ...this.summaryCounters,\n metadata: null,\n reportMode: 'streaming',\n files: [],\n enrichedSummary: null,\n validation: null,\n writeErrors: this.streamingWriter.getWriteErrors(),\n // A killed run gets an honest verdict too. The finalize below is\n // fire-and-forget, so it is NOT claimed as finalized here.\n cloudUpload: this.cloudReceipt,\n };\n this.streamingWriter.writeSummary(partialSummary);\n } catch { /* best effort */ }\n this.streamingWriter.dispose();\n }\n // Best-effort cloud finalize so the run doesn't stay stuck in \"Running\"\n try {\n if (this.cloudClient?.isCloudMode() && this.cloudRunId) {\n const token = this.cloudClient.getAccessToken();\n if (token) {\n finalizeRunFireAndForget(\n this.cloudClient.getEndpoint(),\n token,\n this.cloudRunId,\n this.startedAt,\n );\n }\n }\n } catch { /* best effort */ }\n };\n process.on('SIGTERM', shutdownHandler);\n process.on('SIGINT', shutdownHandler);\n }\n\n // Generate a timestamp-based folder name for this run's artifacts\n if (this.config.includeArtifacts) {\n const outputDir = dirname(this.config.outputPath);\n this.runTimestamp = generateTimestampFolderName(join(outputDir, 'artifacts'));\n\n // Auto-add artifact directory to .gitignore\n try {\n ensureGitignore(join(outputDir, 'artifacts'));\n } catch {\n // FR-026: gitignore failure never crashes the test run\n }\n }\n\n // Detect environment metadata once for the whole run\n const ci = detectCI();\n this.detectedOs = detectOsName();\n this.detectedSource = detectSource(ci);\n\n // Cloud integration: initialize client.\n //\n // This is the single most dangerous await in the reporter: Playwright does\n // not start ANY test until `onBegin` resolves, so a stalled auth/health/run-init\n // call here wedges the whole run before the first test. The individual fetches\n // carry AbortController deadlines, but the phase as a whole had no ceiling —\n // a watchdog bounds it regardless of which call misbehaves (no key, invalid\n // key, black-holed endpoint, proxy that accepts and never answers).\n const initialized = await withTimeout(\n async () => {\n this.cloudClient = new CloudClient(this.config.cloud);\n await this.cloudClient.initialize();\n return initCloudRun(\n this.cloudClient,\n this.config.cloud?.uploadStrategy,\n this.testRunId,\n this.startedAt,\n this.detectedSource,\n );\n },\n CLOUD_INIT_TIMEOUT_MS,\n 'Cloud initialization',\n );\n this.cloudRunId = initialized ?? null;\n if (initialized === undefined) {\n // Timed out or threw — degrade to local reporting and keep testing.\n this.cloudClient?.switchToLocalMode('init_error');\n }\n\n // Record the upload verdict as it stands BEFORE any test runs. `summary.json`\n // has only ever carried `testRunId`, which is generated locally and therefore\n // says nothing about whether the run reached the platform; this block does.\n this.cloudReceipt = buildInitialReceipt(\n this.cloudClient,\n this.config.cloud?.uploadStrategy,\n this.cloudRunId,\n );\n\n // For embedded mode, register a signal handler to finalize the cloud run\n // on interrupt (streaming mode already has its own handler above).\n if (this.activeReportMode !== 'streaming') {\n const embeddedShutdownHandler = (): void => {\n try {\n if (this.cloudClient?.isCloudMode() && this.cloudRunId) {\n const token = this.cloudClient.getAccessToken();\n if (token) {\n finalizeRunFireAndForget(\n this.cloudClient.getEndpoint(),\n token,\n this.cloudRunId,\n this.startedAt,\n );\n }\n }\n } catch { /* best effort */ }\n };\n process.on('SIGTERM', embeddedShutdownHandler);\n process.on('SIGINT', embeddedShutdownHandler);\n }\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 from attachments/annotations (v2: file-based heavy data)\n let extracted = extractTestData(\n lastResult.attachments,\n test.annotations,\n test.title,\n status === 'skipped',\n );\n if (extracted.navigations.length > 0 || extracted.networkRequestsCount > 0 || extracted.apiCallsCount > 0) {\n this.fixtureDataReceived = true;\n }\n // Track whether the fixture produced ANY navigation this run (pre-drop), so\n // onEnd can distinguish \"fixture active but nothing navigated\" from \"fixture\n // never ran\" and from \"navigation disabled\".\n if (extracted.navigations.length > 0) {\n this.navigationsCaptured = true;\n }\n // Master Test Navigation toggle: when disabled, drop navigation data before\n // it reaches any payload. Capture still runs in the worker (harmless); we\n // gate at the reporter boundary where the resolved config is available. A\n // single run-level notice is emitted in onEnd.\n if (!this.config.captureNavigation && extracted.navigations.length > 0) {\n extracted = { ...extracted, navigations: [] };\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\n // Playwright derives the error location from the throwing frame.\n // For a captured API assertion that frame is inside our own matcher\n // proxy, so the snippet showed SDK internals where the reader\n // expected their own test. The assertion we recorded knows the real\n // line; prefer it whenever Playwright's location left the spec file.\n const pwFile = firstError.location?.file ?? null;\n const assertionLoc = extracted.apiAssertions.find(\n (a) => a.status === 'failed',\n )?.location ?? null;\n const useAssertionLoc =\n assertionLoc !== null &&\n (pwFile === null || resolve(pwFile) !== resolve(test.location.file));\n\n const errorFile = useAssertionLoc ? assertionLoc.file : pwFile;\n const errorLine = useAssertionLoc\n ? assertionLoc.line\n : firstError.location?.line ?? null;\n\n let codeSnippet: string | null = null;\n if (this.config.includeCodeSnippets && errorLine !== null && errorFile) {\n codeSnippet = extractCodeSnippet(\n errorFile,\n errorLine,\n this.config.codeContextLines,\n );\n if (codeSnippet) codeSnippet = redact(codeSnippet);\n }\n\n failure = {\n // Playwright formats these for a terminal; the report is not one.\n message: redact(stripAnsi(firstError.message ?? 'Unknown error')),\n line: errorLine,\n code: codeSnippet,\n stack: this.config.includeStackTrace\n ? firstError.stack\n ? redact(stripAnsi(firstError.stack))\n : null\n : null,\n };\n }\n }\n\n const titlePath = test.titlePath().filter(Boolean);\n // Always POSIX separators. `relative` yields backslashes on Windows,\n // and this value is the test's identity in the report, the index, the\n // grid's group header and the cloud payload — so the same suite run on\n // Windows and on Linux produced two different filePaths for one test and\n // the cloud could not correlate them. (getTestType already had to strip\n // backslashes defensively, which was the tell.)\n const specFile = relative(this.rootDir || '.', test.location.file).replace(/\\\\/g, '/');\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 baseTestType = getTestType(tags, filePath, test);\n // Promote to an API test when the test exercised the API request fixture\n // (apiCallsCount > 0), even if it wasn't tagged @api or placed under an\n // api/ path — so a run of untagged API tests still opens the API\n // workspace. Never override a mobile classification.\n const testType =\n baseTestType !== 'mobile' && extracted.apiCallsCount > 0 ? 'api' : baseTestType;\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 // `testId` is required here, not cosmetic: the leaf title alone is not\n // unique across spec files, describe blocks or Playwright projects, and\n // colliding tests would otherwise share one artifact folder and\n // overwrite each other's video/screenshot.\n artifacts = copyArtifacts(lastResult.attachments, lastTitle, lastResult.retry, outputDir, this.runTimestamp || undefined, testId);\n }\n\n // Visual baseline comparisons. Playwright leaves these as attachments\n // suffixed `-expected` / `-actual` / `-diff`, which the artifact copier\n // above ignores because it matches three exact names. They are grouped\n // back into per-snapshot records here and copied under the same test\n // folder, so one attempt remains one directory.\n let visual: VisualComparison[] | null = null;\n if (this.config.includeArtifacts && status !== 'skipped' && lastResult.attachments) {\n try {\n const candidates = parseVisualAttachments(lastResult.attachments, {\n updatingSnapshots: this.updatingSnapshots,\n failureMessage: lastResult.errors\n .map((e) => e.message ?? '')\n .join('\\n'),\n });\n if (candidates.length > 0) {\n const outputDir = dirname(this.config.outputPath);\n const lastTitle = titlePath[titlePath.length - 1] ?? test.title;\n visual = copyVisualArtifacts(\n candidates,\n lastTitle,\n lastResult.retry,\n outputDir,\n this.runTimestamp || undefined,\n testId,\n );\n }\n } catch {\n // FR-026: a visual capture problem must never fail the test run.\n }\n }\n\n // Collect action steps from Playwright's step tree\n const actions = this.config.includeActionSteps && lastResult.steps\n ? collectActionSteps(lastResult.steps, lastResult.startTime)\n : null;\n\n const browser = getBrowserDisplayName(test);\n const collectedTest: CollectedTest = {\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: extracted.navigations,\n testId,\n filePath,\n suiteName,\n testType,\n isFlaky,\n retryStatus,\n expectedStatus,\n actualStatus,\n artifacts,\n visual,\n apiAssertions: extracted.apiAssertions,\n actions,\n // File-based heavy data references\n networkRequestsFile: extracted.networkRequestsFile,\n networkRequestsCount: extracted.networkRequestsCount,\n consoleLogsFile: extracted.consoleLogsFile,\n consoleLogsCount: extracted.consoleLogsCount,\n apiCallsFile: extracted.apiCallsFile,\n apiCallsCount: extracted.apiCallsCount,\n apiStatusMix: extracted.apiStatusMix,\n // Environment metadata\n browser,\n os: this.detectedOs || undefined,\n source: this.detectedSource || undefined,\n };\n\n // Read JSONL artifact files now — before the streaming writer moves them\n // and before heavy fields are nulled. These are included in the batch\n // payload as a reliable fallback for the fire-and-forget realtime upload.\n const batchConsoleLogs = extracted.consoleLogsFile && extracted.consoleLogsCount > 0\n ? (readJsonlSync(extracted.consoleLogsFile) as Record<string, unknown>[])\n : undefined;\n const batchNetworkRequests = extracted.networkRequestsFile && extracted.networkRequestsCount > 0\n ? (readJsonlSync(extracted.networkRequestsFile) as Record<string, unknown>[])\n : undefined;\n // API request/response + assertions for the batch fallback. Without these\n // the suite-end batch re-ingest overwrites the realtime apiReport with one\n // that has no calls/assertions, blanking the API-workspace inspector.\n const batchApiCallsRaw = extracted.apiCallsFile && extracted.apiCallsCount > 0\n ? (readJsonlSync(extracted.apiCallsFile) as Record<string, unknown>[])\n : undefined;\n const batchApiCalls = batchApiCallsRaw ? toApiReportCalls(batchApiCallsRaw) : undefined;\n // Pure-API tests have no browser networkRequestsFile — map apiCalls so the\n // batch fallback still populates network.json.\n const batchNetworkFromApi = !batchNetworkRequests && batchApiCallsRaw\n ? toNetworkRequestsFromApiCalls(batchApiCallsRaw)\n : undefined;\n const batchProtocolAssertions = extracted.apiAssertions && extracted.apiAssertions.length > 0\n ? toProtocolAssertions(extracted.apiAssertions as unknown as Record<string, unknown>[])\n : undefined;\n\n // Track cloud test data for the batch upload (used in batchUpload at suite end).\n // Actions, consoleLogs, and networkRequests are included so the batch acts as\n // a guaranteed fallback if the per-test fire-and-forget realtime upload fails.\n this.cloudTestsBuffer.push({\n testId,\n title,\n status,\n duration: lastResult.duration,\n suiteName,\n filePath,\n testType,\n isFlaky,\n startedAt,\n completedAt,\n tags,\n failure,\n retry: lastResult.retry,\n retryCount: test.results.length - 1,\n retryStatus,\n browser,\n os: this.detectedOs || undefined,\n source: this.detectedSource || undefined,\n ...(actions && actions.length > 0 ? { actions: actions as unknown as Record<string, unknown>[] } : {}),\n // Navigation entries — carried in the batch payload so Test Navigation\n // data reaches the server under the default 'batch' upload strategy (the\n // realtime per-test path that also carries navigations never runs there).\n ...(extracted.navigations && extracted.navigations.length > 0\n ? { navigations: extracted.navigations as unknown as Record<string, unknown>[] }\n : {}),\n ...(batchConsoleLogs ? { consoleLogs: batchConsoleLogs } : {}),\n ...(batchNetworkRequests\n ? { networkRequests: batchNetworkRequests }\n : batchNetworkFromApi\n ? { networkRequests: batchNetworkFromApi }\n : {}),\n ...(batchApiCalls ? { apiCalls: batchApiCalls } : {}),\n ...(batchProtocolAssertions ? { protocolAssertions: batchProtocolAssertions } : {}),\n // The comparisons themselves, not just the images. `visual` is already\n // in scope here and was simply never put on the payload, so the cloud\n // received the baseline/actual/diff PNGs as artifacts with nothing\n // saying which snapshot they belonged to or whether it passed.\n ...(visual && visual.length > 0\n ? { visual: visual as unknown as Record<string, unknown>[] }\n : {}),\n });\n\n // Realtime cloud upload — must happen BEFORE streaming writer moves JSONL\n // files and BEFORE heavy fields are nulled for memory release.\n try {\n const uploadStrategy = this.config.cloud?.uploadStrategy;\n if (this.cloudClient?.isCloudMode() && this.cloudRunId && (uploadStrategy === 'realtime' || uploadStrategy === 'both')) {\n const token = this.cloudClient.getAccessToken();\n if (token) {\n uploadTestResult(\n this.cloudClient.getEndpoint(),\n token,\n this.cloudRunId,\n this.buildCloudTestPayload(collectedTest),\n );\n }\n }\n } catch {\n // Realtime upload errors never crash tests\n }\n\n // Streaming mode: write detail to disk, keep only index in memory\n if (this.activeReportMode === 'streaming' && this.streamingWriter) {\n const streamId = generateStreamingTestId(filePath, titlePath, test.id ?? '', lastResult.retry);\n\n // Determine if this is a non-final retry\n const isFinalAttempt = lastResult.retry === test.results.length - 1;\n\n // Build metadata (small data kept inline)\n const detailData: TestDetailData = {\n id: streamId,\n navigations: extracted.navigations,\n apiAssertions: extracted.apiAssertions,\n actions: actions ?? [],\n artifacts,\n visual,\n failureDiagnostic: failure,\n hasNetworkFile: extracted.networkRequestsFile !== null,\n networkRequestsCount: extracted.networkRequestsCount,\n hasConsoleFile: extracted.consoleLogsFile !== null,\n consoleLogsCount: extracted.consoleLogsCount,\n hasApiCallsFile: extracted.apiCallsFile !== null,\n apiCallsCount: extracted.apiCallsCount,\n startedAt,\n };\n\n // Write metadata + move JSONL files to report directory (never loaded into memory)\n this.streamingWriter.writeTestDetail(streamId, detailData, {\n networkRequestsFile: extracted.networkRequestsFile,\n consoleLogsFile: extracted.consoleLogsFile,\n apiCallsFile: extracted.apiCallsFile,\n });\n\n // Build compact index entry\n const indexEntry: TestIndexEntry = {\n id: streamId,\n title: titlePath[titlePath.length - 1] ?? test.title,\n titlePath,\n filePath,\n status,\n duration: lastResult.duration,\n project: titlePath[1] ?? '',\n retryIndex: lastResult.retry,\n tags,\n hasNetworkData: extracted.networkRequestsCount > 0,\n hasConsoleData: extracted.consoleLogsCount > 0,\n hasArtifacts: artifacts !== null,\n hasActionSteps: (actions?.length ?? 0) > 0,\n visualCount: visual?.length ?? 0,\n visualFailedCount: visual?.filter((v) => v.status === 'failed').length ?? 0,\n domChangeCount: visual?.reduce((sum, v) => sum + (v.domChangeCount ?? 0), 0) ?? 0,\n // The first diff image, so the run overview can SHOW a visual\n // failure instead of only describing it.\n ...(() => {\n const withDiff = visual?.find((v) => v.diff);\n return withDiff?.diff ? { visualDiffPath: withDiff.diff } : {};\n })(),\n errorMessage: errorHeadline(failure?.message),\n networkCount: extracted.networkRequestsCount,\n consoleCount: extracted.consoleLogsCount,\n actionCount: actions?.length ?? 0,\n // The grid badges and filters on these, and it must never open a\n // test's detail file to draw a row.\n testType,\n apiCallCount: extracted.apiCallsCount,\n ...(extracted.apiStatusMix ? { apiStatusMix: extracted.apiStatusMix } : {}),\n assertionsPassed: countAssertions(extracted.apiAssertions, 'passed'),\n assertionsFailed: countAssertions(extracted.apiAssertions, 'failed'),\n ...(() => {\n const fa = firstFailedAssertion(extracted.apiAssertions);\n return fa ? { firstFailedAssertion: fa } : {};\n })(),\n isRetry: !isFinalAttempt,\n retryGroupKey: generateRetryGroupKey(filePath, titlePath, test.id ?? ''),\n };\n this.testIndex.push(indexEntry);\n\n // Update running counters (only count final attempts)\n if (isFinalAttempt) {\n this.summaryCounters.total++;\n switch (status) {\n case 'passed': this.summaryCounters.passed++; break;\n case 'failed': this.summaryCounters.failed++; break;\n case 'flaky': this.summaryCounters.flaky++; break;\n case 'skipped': this.summaryCounters.skipped++; break;\n case 'timedout': this.summaryCounters.timedOut++; break;\n default: this.summaryCounters.interrupted++; break;\n }\n }\n // Record this attempt's run-level aggregate contribution, keyed per test so\n // ONLY the final (highest-retry) attempt is folded at finalize — retries must\n // not inflate the volume counters or the API/assertion/navigation/action\n // breakdowns (the volume totals are derived from this map at finalize, in\n // lockstep with the status counts). `batchApiCallsRaw` was already read from\n // the JSONL for the cloud batch buffer, so there is no extra I/O here.\n this.upsertAggContribution(\n `${filePath}::${titlePath.join(' > ')}`,\n buildAggContribution(\n lastResult.retry,\n batchApiCallsRaw,\n extracted.apiAssertions,\n extracted.navigations,\n extracted.networkRequestsCount,\n extracted.consoleLogsCount,\n actions,\n ),\n );\n\n // Preserve artifact paths for cloud upload before releasing heavy data\n if (artifacts || visual) {\n this.pendingArtifactEntries.push({\n testId,\n artifacts: artifacts ?? {},\n visual,\n outputDir: dirname(this.config.outputPath),\n });\n }\n\n // Release references — heavy data is already on disk\n collectedTest.actions = null;\n collectedTest.apiAssertions = null;\n collectedTest.navigations = [];\n collectedTest.artifacts = null;\n collectedTest.failure = null;\n collectedTest.networkRequestsFile = null;\n collectedTest.consoleLogsFile = null;\n collectedTest.apiCallsFile = null;\n }\n\n // Only accumulate full test data in embedded mode.\n // In streaming mode, data is on disk — no need to hold it in memory.\n if (this.activeReportMode !== 'streaming') {\n this.collectedTests.push(collectedTest);\n }\n this.testCount++;\n\n // Memory pressure check\n this.checkMemoryPressure();\n } catch {\n // FR-026: never crash the test run\n }\n }\n\n async onEnd(_result: PwFullResult): Promise<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 // Warn if no test produced fixture data\n const hasNonSkippedTests = this.activeReportMode === 'streaming'\n ? this.testIndex.some((t) => t.status !== 'skipped')\n : this.collectedTests.some((t) => t.status !== 'skipped');\n if (!this.fixtureDataReceived && hasNonSkippedTests) {\n process.stderr.write(\n '\\n⚠ TestRelic: No fixture data received from any test.\\n' +\n ' To enable network logs, video sync, and Test Navigation tracking, import the TestRelic fixture:\\n' +\n \" import { test, expect } from '@testrelic/playwright-analytics/fixture';\\n\" +\n \" instead of:\\n\" +\n \" import { test, expect } from '@playwright/test';\\n\\n\",\n );\n }\n\n // Test Navigation diagnostics (one notice per run). The fixture-absence case\n // is already covered above; here we distinguish \"disabled by config\" from\n // \"fixture active but nothing navigated\".\n if (!this.config.captureNavigation) {\n process.stderr.write(\n '[testrelic] Test Navigation capture is disabled (captureNavigation: false). ' +\n 'Remove the option or set it to true to capture the navigation graph.\\n',\n );\n } else if (this.fixtureDataReceived && !this.navigationsCaptured && hasNonSkippedTests) {\n process.stderr.write(\n '[testrelic] Test Navigation: 0 navigation events captured this run. ' +\n 'Navigation is recorded automatically on page.goto()/clicks; if you expected a ' +\n 'navigation graph, ensure your tests drive page navigations (and use the ' +\n \"'@testrelic/playwright-analytics/fixture' import).\\n\",\n );\n }\n\n // Wait for all async artifact copies to complete before building manifest.\n // Bounded: a stuck copy (network drive, locked file) must not hold the run open.\n await withTimeout(\n () => flushPendingArtifactCopies(),\n REPORT_FINALIZE_TIMEOUT_MS,\n 'Artifact flush',\n );\n\n // Build artifact manifest before HTML report so it can be embedded\n let artifactManifest: import('@testrelic/core').ArtifactRunManifest | null = null;\n if (this.config.includeArtifacts && this.runTimestamp) {\n try {\n const outputDir = dirname(this.config.outputPath);\n artifactManifest = buildArtifactManifest(outputDir, this.runTimestamp);\n\n const manifestPath = join(outputDir, 'artifact-manifest.json');\n const manifestJson = JSON.stringify(artifactManifest);\n const tmpPath = manifestPath + '.tmp';\n writeFileSync(tmpPath, manifestJson, 'utf-8');\n renameSync(tmpPath, manifestPath);\n } catch {\n // FR-026: manifest failure never crashes the test run\n }\n }\n\n if (this.activeReportMode === 'streaming' && this.streamingWriter) {\n // --- Streaming mode: finalize on-disk report ---\n await withTimeout(\n () => this.finalizeStreamingReport(completedAt, totalDuration, _result, artifactManifest),\n CLOUD_FINALIZE_TIMEOUT_MS,\n 'Report finalization',\n );\n } else {\n // --- Embedded mode: original behavior ---\n const timeline = this.buildTimeline();\n // Deduplicate by testId, keeping only the last attempt (highest retry index)\n const lastByTestId = new Map<string, CollectedTest>();\n for (const t of this.collectedTests) {\n const existing = lastByTestId.get(t.testId);\n if (!existing || t.retry > existing.retry) lastByTestId.set(t.testId, t);\n }\n const finalAttempts = Array.from(lastByTestId.values());\n const summary = buildEnrichedSummary(finalAttempts, 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 await withTimeout(\n () => writeHtmlReport(report, this.config, artifactManifest),\n REPORT_FINALIZE_TIMEOUT_MS,\n 'HTML report write',\n );\n printConsoleSummary(summary, this.config.outputPath, this.config.htmlReportPath, this.config.quiet);\n\n // Cloud upload — bounded, so a slow or black-holed ingest endpoint cannot\n // hold `playwright test` open after every test has already finished.\n if (this.cloudClient) {\n const artifactEntries = this.collectArtifactEntries();\n const cloudTests = this.cloudTestsBuffer.slice();\n this.cloudTestsBuffer.length = 0;\n const outcome = await withTimeout(\n () => finalizeAndUpload(\n this.cloudClient as CloudClient,\n this.config.cloud,\n this.config.cloud?.uploadStrategy,\n this.testRunId,\n this.cloudRunId,\n report,\n completedAt,\n totalDuration,\n summary,\n artifactEntries,\n cloudTests,\n ),\n CLOUD_FINALIZE_TIMEOUT_MS,\n 'Cloud upload',\n );\n // Embedded mode writes no summary.json, so this receipt is not persisted —\n // it is still folded so the reporter's own state stays truthful and the\n // verdict is available to anything reading it in-process.\n this.cloudReceipt = applyFinalizeOutcome(this.cloudReceipt, outcome, this.testRunId);\n }\n }\n } catch {\n // FR-026: never crash the test run\n } finally {\n // Teardown must happen on EVERY path — including when a cloud phase above\n // timed out and therefore never reached its own `dispose()`. `dispose()`\n // clears the health-check interval; leaving it armed would keep Node's\n // event loop alive and stop `playwright test` from exiting after a run\n // that otherwise completed fine.\n await this.disposeCloudClient();\n }\n }\n\n /** Best-effort, bounded cloud-client teardown. Never throws, always settles. */\n private async disposeCloudClient(): Promise<void> {\n const client = this.cloudClient;\n if (!client) return;\n this.cloudClient = null;\n await withTimeout(() => client.dispose(), CLOUD_DISPOSE_TIMEOUT_MS, 'Cloud client shutdown');\n }\n\n /**\n * Collect artifact entries from all collected tests for cloud upload.\n * In streaming mode, uses the separately accumulated pendingArtifactEntries\n * since collectedTests artifacts are nulled after writing to disk.\n */\n private collectArtifactEntries(): ArtifactEntry[] {\n if (this.activeReportMode === 'streaming') {\n return this.pendingArtifactEntries;\n }\n const outputDir = dirname(this.config.outputPath);\n const entries: ArtifactEntry[] = [];\n for (const t of this.collectedTests) {\n if (t.artifacts || t.visual) {\n entries.push({\n testId: t.testId,\n artifacts: t.artifacts ?? {},\n visual: t.visual,\n outputDir,\n });\n }\n }\n return entries;\n }\n\n private async finalizeStreamingReport(\n completedAt: string,\n totalDuration: number,\n pwResult: PwFullResult,\n artifactManifest: import('@testrelic/core').ArtifactRunManifest | null,\n ): Promise<void> {\n if (!this.streamingWriter) return;\n\n // Reset ALL counters. Both status and volume are re-derived here from the FINAL\n // attempt of each test — status from the index dedup below, volume from the\n // streamingTestAgg fold — so retries inflate neither (the per-attempt increments\n // were removed; this map/index is the single source of truth).\n this.summaryCounters = {\n total: 0, passed: 0, failed: 0, flaky: 0, skipped: 0, timedOut: 0, interrupted: 0,\n totalApiCalls: 0, totalAssertions: 0, totalNavigations: 0,\n totalNetworkRequests: 0, totalConsoleLogs: 0, totalActionSteps: 0,\n visualChecks: 0, visualFailures: 0, visualDomChanges: 0,\n };\n\n const bestByTestKey = new Map<string, number>();\n const fileMap = new Map<string, StreamingFileStats & { total: number; passed: number; failed: number; flaky: number; skipped: number; timedOut: number }>();\n\n // Pass 1 of 1: find best retry, mark entries, count statuses, build file stats\n for (const entry of this.testIndex) {\n const testKey = `${entry.filePath}::${entry.titlePath.join(' > ')}`;\n const existing = bestByTestKey.get(testKey);\n if (existing === undefined || entry.retryIndex > existing) {\n bestByTestKey.set(testKey, entry.retryIndex);\n }\n }\n let indexFinalCount = 0;\n for (const entry of this.testIndex) {\n const testKey = `${entry.filePath}::${entry.titlePath.join(' > ')}`;\n const bestRetry = bestByTestKey.get(testKey)!;\n (entry as { isRetry: boolean }).isRetry = entry.retryIndex < bestRetry;\n if (entry.isRetry) continue;\n\n indexFinalCount++;\n this.summaryCounters.total++;\n switch (entry.status) {\n case 'passed': this.summaryCounters.passed++; break;\n case 'failed': this.summaryCounters.failed++; break;\n case 'flaky': this.summaryCounters.flaky++; break;\n case 'skipped': this.summaryCounters.skipped++; break;\n case 'timedout': this.summaryCounters.timedOut++; break;\n default: this.summaryCounters.interrupted++; break;\n }\n\n // Visual totals ride the same dedup: counted per final attempt only.\n this.summaryCounters.visualChecks += entry.visualCount ?? 0;\n this.summaryCounters.visualFailures += entry.visualFailedCount ?? 0;\n this.summaryCounters.visualDomChanges += entry.domChangeCount ?? 0;\n\n // Build file stats inline\n let stats = fileMap.get(entry.filePath);\n if (!stats) {\n stats = { filePath: entry.filePath, total: 0, passed: 0, failed: 0, flaky: 0, skipped: 0, timedOut: 0 };\n fileMap.set(entry.filePath, stats);\n }\n stats.total++;\n switch (entry.status) {\n case 'passed': stats.passed++; break;\n case 'failed': stats.failed++; break;\n case 'flaky': stats.flaky++; break;\n case 'skipped': stats.skipped++; break;\n case 'timedout': stats.timedOut++; break;\n }\n }\n const files: StreamingFileStats[] = Array.from(fileMap.values());\n\n // Fold the per-test aggregate contributions into the run-level breakdowns + volume\n // totals. streamingTestAgg already holds ONLY the highest-retry attempt per test,\n // so this dedups retries exactly like the status counts above. Per-test URLs are\n // already deduped and response-times capped (buildAggContribution); the fold caps\n // again so the run-level accumulators stay O(cap) regardless of run size.\n const folded = emptyStreamingAgg();\n for (const c of this.streamingTestAgg.values()) {\n folded.volApiCalls += c.apiCalls;\n folded.volAssertions += c.assertions;\n folded.volNavigations += c.navigations;\n folded.volNetworkRequests += c.networkRequests;\n folded.volConsoleLogs += c.consoleLogs;\n folded.volActionSteps += c.actionSteps;\n for (const m in c.methodCounts) folded.methodCounts[m] = (folded.methodCounts[m] ?? 0) + c.methodCounts[m];\n folded.statusRanges['2xx'] += c.statusRanges['2xx'];\n folded.statusRanges['3xx'] += c.statusRanges['3xx'];\n folded.statusRanges['4xx'] += c.statusRanges['4xx'];\n folded.statusRanges['5xx'] += c.statusRanges['5xx'];\n folded.statusRanges.error += c.statusRanges.error;\n folded.passedAssertions += c.passedAssertions;\n folded.failedAssertions += c.failedAssertions;\n for (const cat in c.actionCategories) folded.actionCategoryCounts[cat] = (folded.actionCategoryCounts[cat] ?? 0) + c.actionCategories[cat];\n for (const u of c.apiUrls) { if (folded.apiUrlSet.size >= MAX_AGG_UNIQUE_URLS) break; folded.apiUrlSet.add(u); }\n for (const u of c.navUrls) { if (folded.navUrlSet.size >= MAX_AGG_UNIQUE_URLS) break; folded.navUrlSet.add(u); }\n for (const rt of c.responseTimes) { if (folded.responseTimes.length >= MAX_AGG_RESPONSE_SAMPLES) break; folded.responseTimes.push(rt); }\n }\n this.summaryCounters.totalApiCalls = folded.volApiCalls;\n this.summaryCounters.totalAssertions = folded.volAssertions;\n this.summaryCounters.totalNavigations = folded.volNavigations;\n this.summaryCounters.totalNetworkRequests = folded.volNetworkRequests;\n this.summaryCounters.totalConsoleLogs = folded.volConsoleLogs;\n this.summaryCounters.totalActionSteps = folded.volActionSteps;\n\n // Write test index (full for server API, compact for HTML embedding)\n this.streamingWriter.writeIndex(this.testIndex);\n this.streamingWriter.writeCompactIndex(this.testIndex);\n\n // Cross-validate summary counters against index and Playwright\n const validation = {\n reporterTotal: this.summaryCounters.total,\n indexTotal: indexFinalCount,\n playwrightStatus: pwResult.status,\n isValid: this.summaryCounters.total === indexFinalCount,\n };\n if (!validation.isValid) {\n process.stderr.write(\n `[testrelic] WARNING: Summary count mismatch — reporter: ${validation.reporterTotal}, index: ${validation.indexTotal}\\n`,\n );\n }\n\n // Build enriched summary from the folded per-test aggregates (final attempts only).\n const enrichedSummary: Summary = {\n total: this.summaryCounters.total,\n passed: this.summaryCounters.passed,\n failed: this.summaryCounters.failed,\n flaky: this.summaryCounters.flaky,\n skipped: this.summaryCounters.skipped,\n timedout: this.summaryCounters.timedOut,\n totalApiCalls: this.summaryCounters.totalApiCalls,\n uniqueApiUrls: folded.apiUrlSet.size,\n apiCallsByMethod: { ...folded.methodCounts },\n apiCallsByStatusRange: {\n '2xx': folded.statusRanges['2xx'],\n '3xx': folded.statusRanges['3xx'],\n '4xx': folded.statusRanges['4xx'],\n '5xx': folded.statusRanges['5xx'],\n error: folded.statusRanges.error,\n },\n apiResponseTime: buildAggResponseTime(folded.responseTimes),\n totalAssertions: this.summaryCounters.totalAssertions,\n passedAssertions: folded.passedAssertions,\n failedAssertions: folded.failedAssertions,\n totalNavigations: this.summaryCounters.totalNavigations,\n uniqueNavigationUrls: folded.navUrlSet.size,\n // Final attempts only — retry index rows are excluded (they were counted before).\n totalTimelineSteps: indexFinalCount,\n totalActionSteps: this.summaryCounters.totalActionSteps,\n actionStepsByCategory: { ...folded.actionCategoryCounts },\n // The HTML report is built from THIS object, not from the streaming\n // summary written to disk — so the visual totals have to be on both or\n // the run overview shows counters the JSON already knows.\n visualChecks: this.summaryCounters.visualChecks,\n visualFailures: this.summaryCounters.visualFailures,\n visualDomChanges: this.summaryCounters.visualDomChanges,\n };\n\n // Write streaming summary.\n //\n // `testRunId` is a locally generated UUID — it is written on every run, offline\n // ones included, so its presence is NOT evidence the run reached the platform.\n // `cloudUpload` is that evidence. Written here with the pre-upload verdict and\n // rewritten below once the upload settles, so a process killed mid-upload leaves\n // the conservative verdict on disk instead of a claim it cannot back up.\n const summary: SummaryWithReceipt = {\n testRunId: this.testRunId,\n startedAt: this.startedAt,\n completedAt,\n totalDuration,\n ...this.summaryCounters,\n metadata: this.config.metadata,\n reportMode: 'streaming',\n files,\n enrichedSummary,\n validation,\n writeErrors: this.streamingWriter.getWriteErrors(),\n cloudUpload: this.cloudReceipt,\n };\n this.streamingWriter.writeSummary(summary);\n\n // Write manifest\n const manifest: StreamingReportManifest = {\n schemaVersion: '2.0',\n generatedAt: new Date().toISOString(),\n reportMode: 'streaming',\n summaryFile: 'summary.json',\n indexFile: 'index.json',\n testsDir: 'tests',\n artifactsDir: 'artifacts',\n testCount: this.testIndex.length,\n totalSizeBytes: this.streamingWriter.computeTotalSize(),\n };\n this.streamingWriter.writeManifest(manifest);\n\n // Generate lightweight HTML report with embedded summary + index\n await writeHtmlReport(\n this.buildStreamingReport(summary, enrichedSummary, completedAt, totalDuration),\n this.config,\n artifactManifest,\n );\n\n // Print console summary\n printConsoleSummary(enrichedSummary, this.config.outputPath, this.config.htmlReportPath, this.config.quiet);\n\n const reportDir = this.streamingWriter.getReportDir();\n process.stderr.write(\n `[testrelic] Streaming report written to ${reportDir}\\n` +\n `[testrelic] ${this.testIndex.length} test detail files on disk\\n` +\n `[testrelic] View report: npx testrelic serve ${reportDir}\\n`,\n );\n\n if (this.streamingWriter.getWriteErrors().length > 0) {\n process.stderr.write(\n `[testrelic] WARNING: ${this.streamingWriter.getWriteErrors().length} write error(s) during streaming\\n`,\n );\n }\n\n // Cloud upload for streaming mode.\n //\n // The writer is disposed only after the summary rewrite below, so hold it in a\n // local and dispose in `finally` — the reorder must not be able to skip teardown.\n const writer = this.streamingWriter;\n try {\n if (this.cloudClient) {\n const report = this.buildStreamingReport(summary, enrichedSummary, completedAt, totalDuration);\n const artifactEntries = this.collectArtifactEntries();\n const cloudTests = this.cloudTestsBuffer.slice();\n this.cloudTestsBuffer.length = 0;\n const outcome = await finalizeAndUpload(\n this.cloudClient,\n this.config.cloud,\n this.config.cloud?.uploadStrategy,\n this.testRunId,\n this.cloudRunId,\n report,\n completedAt,\n totalDuration,\n enrichedSummary,\n artifactEntries,\n cloudTests,\n );\n\n // Rewrite the summary with what the platform actually did. `writeSummary` is\n // atomic (tmp + rename), so summary.json is never torn between the two writes.\n // The local report was already complete before this ran — a cloud failure here\n // still leaves a usable report, now with a verdict that says so.\n this.cloudReceipt = applyFinalizeOutcome(this.cloudReceipt, outcome, this.testRunId);\n const settledSummary: SummaryWithReceipt = { ...summary, cloudUpload: this.cloudReceipt };\n try {\n writer.writeSummary(settledSummary);\n } catch {\n // FR-026: a failed rewrite leaves the earlier, more conservative verdict.\n }\n }\n } finally {\n writer.dispose();\n }\n\n // Explicit cleanup: release all references so GC can reclaim memory.\n // Mirrors Playwright's InternalReporter pattern of nulling suite refs after onEnd.\n this.testIndex.length = 0;\n this.collectedTests.length = 0;\n this.pendingArtifactEntries.length = 0;\n this.cloudTestsBuffer.length = 0;\n this.streamingWriter = null;\n this.cloudClient = null;\n }\n\n /** Build a lightweight TestRunReport from streaming data (for HTML generation and cloud). */\n private buildStreamingReport(\n _streamingSummary: StreamingReportSummary,\n enrichedSummary: Summary,\n completedAt: string,\n totalDuration: number,\n ): TestRunReport {\n // In streaming mode, timeline is empty in the report — details are served by the server\n return {\n schemaVersion: SCHEMA_VERSION,\n testRunId: this.testRunId,\n startedAt: this.startedAt,\n completedAt,\n totalDuration,\n summary: enrichedSummary,\n ci: detectCI(),\n metadata: this.config.metadata,\n timeline: [],\n shardRunIds: null,\n };\n }\n\n printsToStdio(): boolean {\n return false;\n }\n\n /** Check heap usage and warn or auto-switch to streaming mode. */\n private checkMemoryPressure(): void {\n try {\n const usage = process.memoryUsage();\n const heapMB = usage.heapUsed / 1024 / 1024;\n // Only check pressure when heap is substantial (avoids false positives in tests)\n if (heapMB < 200) return;\n const heapRatio = usage.heapUsed / usage.heapTotal;\n if (heapRatio > 0.9 && this.activeReportMode === 'embedded') {\n process.stderr.write(\n `[testrelic] WARNING: High memory (${Math.round(heapMB)} MB). ` +\n `Auto-switching to streaming mode.\\n`,\n );\n this.switchToStreamingMode();\n } else if (heapRatio > 0.8 && heapMB >= 500 && !this.memoryPressureWarned) {\n // Require an absolute floor in addition to the ratio: heapTotal grows in\n // steps, so a modest heapUsed can transiently read as a high ratio right\n // after a growth step. Also fire at most once per run — this is advisory,\n // not the auto-switch trigger above, so it doesn't need to repeat per test.\n this.memoryPressureWarned = true;\n process.stderr.write(\n `[testrelic] WARNING: Memory pressure detected (${Math.round(heapMB)} MB used).\\n`,\n );\n }\n } catch {\n // Never crash due to memory check\n }\n }\n\n /** Switch from embedded to streaming mode mid-run. */\n private switchToStreamingMode(): void {\n try {\n if (this.activeReportMode === 'streaming') return;\n this.activeReportMode = 'streaming';\n const outputDir = dirname(this.config.outputPath);\n this.streamingWriter = new StreamingWriter(outputDir);\n\n // Flush any already-collected tests to disk and release from memory\n for (const ct of this.collectedTests) {\n const streamId = generateStreamingTestId(ct.filePath, ct.titlePath, ct.testId, ct.retry);\n // Read the apiCalls JSONL for the run-level breakdowns BEFORE writeTestDetail\n // (below) moves the file to the report dir — after that the path is gone.\n // Bounded by the pre-switch test count; we are already flushing each test here.\n const switchApiCallsRaw = ct.apiCallsFile && ct.apiCallsCount > 0\n ? (readJsonlSync(ct.apiCallsFile) as Record<string, unknown>[])\n : undefined;\n const detailData: TestDetailData = {\n id: streamId,\n navigations: ct.navigations ?? [],\n apiAssertions: ct.apiAssertions ?? [],\n actions: ct.actions ?? [],\n artifacts: ct.artifacts,\n failureDiagnostic: ct.failure,\n hasNetworkFile: ct.networkRequestsFile !== null,\n networkRequestsCount: ct.networkRequestsCount,\n hasConsoleFile: ct.consoleLogsFile !== null,\n consoleLogsCount: ct.consoleLogsCount,\n hasApiCallsFile: ct.apiCallsFile !== null,\n apiCallsCount: ct.apiCallsCount,\n startedAt: ct.startedAt,\n };\n this.streamingWriter.writeTestDetail(streamId, detailData, {\n networkRequestsFile: ct.networkRequestsFile,\n consoleLogsFile: ct.consoleLogsFile,\n apiCallsFile: ct.apiCallsFile,\n });\n\n const indexEntry: TestIndexEntry = {\n id: streamId,\n title: ct.titlePath[ct.titlePath.length - 1] ?? ct.title,\n titlePath: ct.titlePath,\n filePath: ct.filePath,\n status: ct.status,\n duration: ct.duration,\n project: ct.titlePath[1] ?? '',\n retryIndex: ct.retry,\n tags: ct.tags,\n hasNetworkData: ct.networkRequestsCount > 0,\n hasConsoleData: ct.consoleLogsCount > 0,\n hasArtifacts: ct.artifacts !== null,\n hasActionSteps: (ct.actions?.length ?? 0) > 0,\n errorMessage: ct.failure?.message?.split('\\n')[0] ?? null,\n networkCount: ct.networkRequestsCount,\n consoleCount: ct.consoleLogsCount,\n actionCount: ct.actions?.length ?? 0,\n testType: ct.testType,\n apiCallCount: ct.apiCallsCount,\n ...(ct.apiStatusMix\n ? { apiStatusMix: ct.apiStatusMix }\n : switchApiCallsRaw\n ? { apiStatusMix: statusMixOf(switchApiCallsRaw) }\n : {}),\n assertionsPassed: countAssertions(ct.apiAssertions, 'passed'),\n assertionsFailed: countAssertions(ct.apiAssertions, 'failed'),\n ...(() => {\n const fa = firstFailedAssertion(ct.apiAssertions);\n return fa ? { firstFailedAssertion: fa } : {};\n })(),\n isRetry: false,\n retryGroupKey: generateRetryGroupKey(ct.filePath, ct.titlePath, ct.testId),\n };\n this.testIndex.push(indexEntry);\n\n // Update running counters\n this.summaryCounters.total++;\n switch (ct.status) {\n case 'passed': this.summaryCounters.passed++; break;\n case 'failed': this.summaryCounters.failed++; break;\n case 'flaky': this.summaryCounters.flaky++; break;\n case 'skipped': this.summaryCounters.skipped++; break;\n case 'timedout': this.summaryCounters.timedOut++; break;\n default: this.summaryCounters.interrupted++; break;\n }\n // Feed the run-level aggregates for this pre-switch test so the streaming\n // summary's breakdowns stay in lockstep with its volume totals (both are\n // derived from streamingTestAgg at finalize). Without this, pre-switch tests\n // would contribute to the totals but not the method/status/assertion/category\n // breakdowns — totals and breakdowns would contradict each other.\n this.upsertAggContribution(\n `${ct.filePath}::${ct.titlePath.join(' > ')}`,\n buildAggContribution(\n ct.retry,\n switchApiCallsRaw,\n ct.apiAssertions ?? [],\n ct.navigations ?? [],\n ct.networkRequestsCount,\n ct.consoleLogsCount,\n ct.actions ?? null,\n ),\n );\n }\n\n // Release all collected tests from memory\n this.collectedTests.length = 0;\n } catch {\n // Never crash due to mode switch\n }\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: null,\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: null,\n actions: t.actions,\n consoleLogs: null,\n }));\n\n return buildUnifiedTimeline(testData, {\n navigationTypes: this.config.navigationTypes,\n });\n }\n\n\n private readJsonlFile<T>(filePath: string | null): T[] | null {\n if (!filePath) return null;\n const items = readJsonlSync(filePath);\n return items.length > 0 ? (items as T[]) : null;\n }\n\n private toTestResult(test: CollectedTest, options?: { loadFiles?: boolean }): TRTestResult {\n let networkRequests: CapturedNetworkRequest[] | null = null;\n let consoleLogs: CoreConsoleLogEntry[] | null = null;\n let apiCalls: ApiCallRecord[] | null = null;\n\n // For cloud upload, read the JSONL files so the data is sent to the backend\n if (options?.loadFiles) {\n networkRequests = this.readJsonlFile<CapturedNetworkRequest>(test.networkRequestsFile);\n consoleLogs = this.readJsonlFile<CoreConsoleLogEntry>(test.consoleLogsFile);\n apiCalls = this.readJsonlFile<ApiCallRecord>(test.apiCallsFile);\n }\n\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 visual: test.visual,\n networkRequests,\n apiCalls,\n apiAssertions: test.apiAssertions,\n actions: test.actions ?? null,\n consoleLogs,\n };\n }\n\n /**\n * Build a full payload for cloud upload that includes console logs,\n * network requests, navigations, and actions read from JSONL files.\n * Called before streaming writer moves files and before fields are nulled.\n */\n private buildCloudTestPayload(test: CollectedTest): Record<string, unknown> {\n const payload: Record<string, unknown> = {\n title: test.title,\n status: test.status,\n duration: test.duration,\n startedAt: test.startedAt,\n completedAt: test.completedAt,\n retry: test.retry,\n retryCount: test.retryCount,\n retryStatus: test.retryStatus,\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 expectedStatus: test.expectedStatus,\n actualStatus: test.actualStatus,\n artifacts: test.artifacts,\n visual: test.visual ?? [],\n actions: test.actions ?? [],\n navigations: test.navigations ?? [],\n steps: test.actions ?? [],\n browser: test.browser,\n os: test.os,\n source: test.source,\n };\n\n // Captured API assertions → canonical `protocolAssertions` shape the\n // inspector renders (the server persists `protocolAssertions`, not the raw\n // `apiAssertions` capture field — see toProtocolAssertions).\n if (test.apiAssertions && test.apiAssertions.length > 0) {\n payload.protocolAssertions = toProtocolAssertions(\n test.apiAssertions as unknown as Record<string, unknown>[],\n );\n }\n\n if (test.consoleLogsFile) {\n payload.consoleLogs = readJsonlSync(test.consoleLogsFile);\n }\n if (test.networkRequestsFile) {\n payload.networkRequests = readJsonlSync(test.networkRequestsFile);\n }\n\n // For API tests: apiCallsFile has request/response data captured by ApiRequestTracker.\n // Send `apiCalls` in the canonical `apiReport.apiCalls` shape (what the API\n // workspace inspector reads) and — if no browser network data — also map to\n // `networkRequests` so the backend stores it in network.json for the\n // generic Inspector UI.\n if (test.apiCallsFile) {\n const apiCalls = readJsonlSync(test.apiCallsFile) as Record<string, unknown>[];\n payload.apiCalls = toApiReportCalls(apiCalls);\n if (!payload.networkRequests || (payload.networkRequests as unknown[]).length === 0) {\n payload.networkRequests = toNetworkRequestsFromApiCalls(apiCalls);\n }\n }\n\n return payload;\n }\n\n private writeReport(report: TestRunReport): void {\n try {\n const json = JSON.stringify(report);\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 * StreamingWriter — writes test data to disk incrementally.\n *\n * v3 format: each test gets a directory with separate files:\n * tests/<testId>/meta.json — lightweight metadata\n * tests/<testId>/network.jsonl — network requests (moved from temp)\n * tests/<testId>/console.jsonl — console logs (moved from temp)\n * tests/<testId>/api-calls.jsonl — API calls (moved from temp)\n *\n * Heavy data files are moved (renamed) from the worker's temp directory,\n * never loaded into the reporter's memory.\n */\n\nimport { createHash } from 'node:crypto';\nimport { mkdirSync, writeFileSync, renameSync, unlinkSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type {\n TestIndexEntry,\n TestDetailData,\n StreamingReportSummary,\n StreamingReportManifest,\n StreamingWriteError,\n} from '@testrelic/core';\n\nconst REPORT_DIR_NAME = '.testrelic-report';\n\n/** Generate a deterministic test ID from test identity fields. */\nexport function generateStreamingTestId(\n filePath: string,\n titlePath: readonly string[],\n project: string,\n retryIndex: number,\n): string {\n const input = `${filePath}|${titlePath.join('|')}|${project}|${retryIndex}`;\n return createHash('sha256').update(input).digest('hex').substring(0, 12);\n}\n\n/**\n * Generate a key shared by every attempt of the same test, so retries can be\n * correlated with their final attempt without re-sending the full titlePath.\n * Same inputs as {@link generateStreamingTestId} minus `retryIndex`.\n */\nexport function generateRetryGroupKey(\n filePath: string,\n titlePath: readonly string[],\n project: string,\n): string {\n const input = `${filePath}|${titlePath.join('|')}|${project}`;\n return createHash('sha256').update(input).digest('hex').substring(0, 12);\n}\n\nexport class StreamingWriter {\n private readonly reportDir: string;\n private readonly testsDir: string;\n private readonly writeErrors: StreamingWriteError[] = [];\n private totalBytesWritten = 0;\n\n constructor(outputDir: string) {\n this.reportDir = join(outputDir, REPORT_DIR_NAME);\n this.testsDir = join(this.reportDir, 'tests');\n mkdirSync(this.testsDir, { recursive: true });\n }\n\n /** Get the report directory path. */\n getReportDir(): string {\n return this.reportDir;\n }\n\n /** Get accumulated write errors. */\n getWriteErrors(): readonly StreamingWriteError[] {\n return this.writeErrors;\n }\n\n /**\n * Write a single test's detail data to disk.\n *\n * Creates a per-test directory containing:\n * - meta.json: lightweight metadata (navigations, assertions, actions, artifacts, failure)\n * - network.jsonl: moved from temp (if provided)\n * - console.jsonl: moved from temp (if provided)\n * - api-calls.jsonl: moved from temp (if provided)\n *\n * @param testId — Deterministic test ID\n * @param data — Metadata (inline small data)\n * @param files — Paths to JSONL files to move into the test directory\n */\n writeTestDetail(\n testId: string,\n data: TestDetailData,\n files?: {\n networkRequestsFile?: string | null;\n consoleLogsFile?: string | null;\n apiCallsFile?: string | null;\n },\n ): boolean {\n try {\n const testDir = join(this.testsDir, testId);\n mkdirSync(testDir, { recursive: true });\n\n // Write metadata\n const metaPath = join(testDir, 'meta.json');\n const tmpPath = metaPath + '.tmp';\n const json = JSON.stringify(data);\n writeFileSync(tmpPath, json, 'utf-8');\n renameSync(tmpPath, metaPath);\n this.totalBytesWritten += Buffer.byteLength(json, 'utf-8');\n\n // Move JSONL data files from temp to test directory\n if (files?.networkRequestsFile) {\n this.moveFile(files.networkRequestsFile, join(testDir, 'network.jsonl'));\n }\n if (files?.consoleLogsFile) {\n this.moveFile(files.consoleLogsFile, join(testDir, 'console.jsonl'));\n }\n if (files?.apiCallsFile) {\n this.moveFile(files.apiCallsFile, join(testDir, 'api-calls.jsonl'));\n }\n\n return true;\n } catch (err) {\n const errorMsg = err instanceof Error ? err.message : String(err);\n this.writeErrors.push({\n testId,\n error: errorMsg,\n timestamp: new Date().toISOString(),\n });\n process.stderr.write(\n `[testrelic] Failed to write test detail for ${testId}: ${errorMsg}\\n`,\n );\n return false;\n }\n }\n\n /**\n * Move a file from source to destination.\n * Falls back to copy+delete if rename fails (cross-device move).\n */\n private moveFile(src: string, dest: string): void {\n try {\n renameSync(src, dest);\n } catch {\n // Cross-device: copy then delete\n try {\n const { copyFileSync } = require('node:fs') as typeof import('node:fs');\n copyFileSync(src, dest);\n try { unlinkSync(src); } catch { /* best effort cleanup */ }\n } catch (err) {\n process.stderr.write(\n `[testrelic] Failed to move file ${src} → ${dest}: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n }\n }\n }\n\n /** Write the test index to disk. */\n writeIndex(entries: readonly TestIndexEntry[]): void {\n const filePath = join(this.reportDir, 'index.json');\n const tmpPath = filePath + '.tmp';\n const json = JSON.stringify(entries);\n writeFileSync(tmpPath, json, 'utf-8');\n renameSync(tmpPath, filePath);\n this.totalBytesWritten += Buffer.byteLength(json, 'utf-8');\n }\n\n /**\n * Write a compact index for HTML embedding (strips titlePath/project to save ~100 bytes/entry).\n */\n writeCompactIndex(entries: readonly TestIndexEntry[]): void {\n const filePath = join(this.reportDir, 'index-compact.json');\n const tmpPath = filePath + '.tmp';\n const compact = entries.map(({ titlePath: _tp, project: _p, ...rest }) => rest);\n const json = JSON.stringify(compact);\n writeFileSync(tmpPath, json, 'utf-8');\n renameSync(tmpPath, filePath);\n this.totalBytesWritten += Buffer.byteLength(json, 'utf-8');\n }\n\n /** Write the summary to disk. */\n writeSummary(summary: StreamingReportSummary): void {\n const filePath = join(this.reportDir, 'summary.json');\n const tmpPath = filePath + '.tmp';\n const json = JSON.stringify(summary);\n writeFileSync(tmpPath, json, 'utf-8');\n renameSync(tmpPath, filePath);\n this.totalBytesWritten += Buffer.byteLength(json, 'utf-8');\n }\n\n /** Write the manifest to disk. */\n writeManifest(manifest: StreamingReportManifest): void {\n const filePath = join(this.reportDir, 'manifest.json');\n const tmpPath = filePath + '.tmp';\n const json = JSON.stringify(manifest);\n writeFileSync(tmpPath, json, 'utf-8');\n renameSync(tmpPath, filePath);\n this.totalBytesWritten += Buffer.byteLength(json, 'utf-8');\n }\n\n /** Get total bytes written (tracked incrementally — no filesystem scan). */\n computeTotalSize(): number {\n return this.totalBytesWritten;\n }\n\n /** Cleanup — no-op for now, but available for future resource release. */\n dispose(): void {\n // Reserved for future cleanup\n }\n}\n","/**\n * Cloud configuration discovery, parsing, and merging.\n *\n * Discovers `.testrelic/testrelic-config.json` config files, resolves environment variable\n * references, and merges configuration from multiple sources.\n *\n * Priority order (highest wins):\n * 1. Environment variables (TESTRELIC_API_KEY, TESTRELIC_CLOUD_ENDPOINT, etc.)\n * 2. Reporter options in playwright.config.ts\n * 3. .testrelic/testrelic-config.json config file\n * 4. Built-in defaults\n */\n\nimport { readFileSync, existsSync, statSync } from 'node:fs';\nimport { join, dirname, resolve } from 'node:path';\nimport type { CloudConfig, UploadStrategy, CloudReporterOptions } from '@testrelic/core';\nimport { isValidCloudConfig, isValidEndpointUrl } from '@testrelic/core';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst CONFIG_DIR = '.testrelic';\nconst CONFIG_FILENAME = 'testrelic-config.json';\nconst LEGACY_CONFIG_FILENAME = '.testrelic';\nconst MAX_WALK_UP_LEVELS = 5;\n\nconst DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\n\nconst DEFAULT_CLOUD_CONFIG: CloudConfig = Object.freeze({\n apiKey: null,\n endpoint: 'https://platform.testrelic.ai/api/v1',\n uploadStrategy: 'batch',\n timeout: 30000,\n uploadBudgetMs: 120000,\n projectName: null,\n queueMaxAge: 604800000, // 7 days\n queueDirectory: `${CONFIG_DIR}/queue`,\n uploadArtifacts: true,\n artifactMaxSizeMb: 50,\n});\n\n/** Duration string multipliers. */\nconst DURATION_UNITS: Record<string, number> = {\n s: 1000,\n m: 60 * 1000,\n h: 60 * 60 * 1000,\n d: 24 * 60 * 60 * 1000,\n};\n\n// ---------------------------------------------------------------------------\n// Config File Discovery\n// ---------------------------------------------------------------------------\n\n/**\n * Search for `.testrelic/testrelic-config.json` starting from `startDir`,\n * walking up the directory tree up to MAX_WALK_UP_LEVELS levels.\n * Falls back to the legacy flat `.testrelic` file (emits a deprecation warning).\n * Returns the absolute path if found, or null.\n */\nexport function discoverConfigFile(startDir: string): string | null {\n let currentDir = resolve(startDir);\n for (let i = 0; i <= MAX_WALK_UP_LEVELS; i++) {\n const candidate = join(currentDir, CONFIG_DIR, CONFIG_FILENAME);\n if (existsSync(candidate)) {\n try {\n if (statSync(candidate).isFile()) return candidate;\n } catch {\n // ignore stat errors, continue walking\n }\n }\n\n // Backward-compat: fall back to legacy flat `.testrelic` file\n const legacyCandidate = join(currentDir, LEGACY_CONFIG_FILENAME);\n if (existsSync(legacyCandidate)) {\n try {\n if (statSync(legacyCandidate).isFile()) {\n process.stderr.write(\n '[testrelic] Deprecation: config file \".testrelic\" has moved to \".testrelic/testrelic-config.json\". ' +\n 'Please migrate your config file to the new location.\\n',\n );\n return legacyCandidate;\n }\n } catch {\n // ignore stat errors\n }\n }\n\n const parentDir = dirname(currentDir);\n if (parentDir === currentDir) break; // filesystem root\n currentDir = parentDir;\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Config File Parsing\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a `.testrelic/testrelic-config.json` (or legacy `.testrelic`) JSON config file.\n * Applies prototype pollution prevention per Constitution Principle VIII.\n * Returns null if the file cannot be read or parsed.\n */\nexport function parseConfigFile(filePath: string): Record<string, unknown> | null {\n try {\n const content = readFileSync(filePath, 'utf-8');\n const parsed: unknown = JSON.parse(content);\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n return null;\n }\n // Check for prototype pollution at top level and nested objects\n if (!isSafeObject(parsed as Record<string, unknown>)) {\n return null;\n }\n return parsed as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\n/** Recursively check for dangerous keys. */\nfunction isSafeObject(obj: Record<string, unknown>): boolean {\n for (const key of Object.keys(obj)) {\n if (DANGEROUS_KEYS.has(key)) return false;\n const val = obj[key];\n if (typeof val === 'object' && val !== null && !Array.isArray(val)) {\n if (!isSafeObject(val as Record<string, unknown>)) return false;\n }\n }\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Environment Variable Resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve environment variable references in string values.\n * Supports `$VAR_NAME` and `${VAR_NAME}` syntax.\n * Returns the resolved string, or null if the env var is not defined.\n */\nexport function resolveEnvVar(value: string): string | null {\n // Match ${VAR_NAME} syntax\n const bracketMatch = /^\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}$/.exec(value);\n if (bracketMatch) {\n return process.env[bracketMatch[1]] ?? null;\n }\n // Match $VAR_NAME syntax (entire string must be the reference)\n const dollarMatch = /^\\$([A-Za-z_][A-Za-z0-9_]*)$/.exec(value);\n if (dollarMatch) {\n return process.env[dollarMatch[1]] ?? null;\n }\n return value;\n}\n\n/**\n * Resolve all string values starting with `$` in a config object.\n * Returns a new object with resolved values.\n */\nexport function resolveEnvVars(obj: Record<string, unknown>): Record<string, unknown> {\n const result = Object.create(null) as Record<string, unknown>;\n for (const key of Object.keys(obj)) {\n const val = obj[key];\n if (typeof val === 'string' && val.startsWith('$')) {\n result[key] = resolveEnvVar(val);\n } else if (typeof val === 'object' && val !== null && !Array.isArray(val)) {\n result[key] = resolveEnvVars(val as Record<string, unknown>);\n } else {\n result[key] = val;\n }\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Duration Parsing\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a duration string like \"7d\", \"24h\", \"30m\" into milliseconds.\n * Returns null if the string cannot be parsed.\n */\nexport function parseDuration(value: string): number | null {\n const match = /^(\\d+)\\s*([smhd])$/.exec(value.trim());\n if (!match) return null;\n const amount = parseInt(match[1], 10);\n const unit = match[2];\n const multiplier = DURATION_UNITS[unit];\n if (!multiplier || amount <= 0) return null;\n return amount * multiplier;\n}\n\n// ---------------------------------------------------------------------------\n// Config Merging\n// ---------------------------------------------------------------------------\n\n/**\n * Merge cloud configuration from multiple sources.\n *\n * Priority (highest wins):\n * 1. Environment variables\n * 2. Reporter options (from playwright.config.ts)\n * 3. Config file (.testrelic/testrelic-config.json)\n * 4. Built-in defaults\n */\nexport function mergeCloudConfig(\n fileConfig: Record<string, unknown> | null,\n reporterOptions: CloudReporterOptions | undefined,\n): CloudConfig {\n const target = Object.create(null) as Record<string, unknown>;\n\n // Start with defaults\n Object.assign(target, DEFAULT_CLOUD_CONFIG);\n\n // Layer 3: Config file values\n if (fileConfig) {\n const cloud = fileConfig.cloud as Record<string, unknown> | undefined;\n if (cloud && typeof cloud === 'object') {\n if (typeof cloud.endpoint === 'string') target.endpoint = cloud.endpoint;\n if (typeof cloud.upload === 'string') target.uploadStrategy = cloud.upload;\n if (typeof cloud.timeout === 'number') target.timeout = cloud.timeout;\n if (typeof cloud.uploadBudgetMs === 'number') target.uploadBudgetMs = cloud.uploadBudgetMs;\n // apiKey from config file (should be an env var reference, already resolved)\n if (typeof cloud.apiKey === 'string' && cloud.apiKey.length > 0) {\n target.apiKey = cloud.apiKey;\n }\n }\n // Support both new 'testrelic-repo' key and legacy 'project' key (deprecated)\n const repoSection = (fileConfig['testrelic-repo'] ?? fileConfig.project) as\n | Record<string, unknown>\n | undefined;\n if (repoSection && typeof repoSection === 'object') {\n if (typeof repoSection.name === 'string') target.projectName = repoSection.name;\n }\n if (fileConfig.project && !fileConfig['testrelic-repo']) {\n process.stderr.write(\n '[testrelic] Deprecation: \"project\" key in config is deprecated. Rename it to \"testrelic-repo\".\\n',\n );\n }\n const queue = fileConfig.queue as Record<string, unknown> | undefined;\n if (queue && typeof queue === 'object') {\n if (typeof queue.maxAge === 'string') {\n const ms = parseDuration(queue.maxAge as string);\n if (ms !== null) target.queueMaxAge = ms;\n }\n if (typeof queue.directory === 'string') target.queueDirectory = queue.directory;\n }\n }\n\n // Layer 2: Reporter options\n if (reporterOptions) {\n if (typeof reporterOptions.apiKey === 'string' && reporterOptions.apiKey.length > 0) {\n // Resolve env var reference if present\n const resolved = reporterOptions.apiKey.startsWith('$')\n ? resolveEnvVar(reporterOptions.apiKey)\n : reporterOptions.apiKey;\n if (resolved) target.apiKey = resolved;\n }\n if (typeof reporterOptions.endpoint === 'string') target.endpoint = reporterOptions.endpoint;\n if (typeof reporterOptions.upload === 'string') target.uploadStrategy = reporterOptions.upload;\n if (typeof reporterOptions.timeout === 'number') target.timeout = reporterOptions.timeout;\n if (typeof reporterOptions.uploadBudgetMs === 'number') target.uploadBudgetMs = reporterOptions.uploadBudgetMs;\n if (typeof reporterOptions.projectName === 'string') target.projectName = reporterOptions.projectName;\n if (typeof reporterOptions.queueMaxAge === 'string') {\n const ms = parseDuration(reporterOptions.queueMaxAge);\n if (ms !== null) target.queueMaxAge = ms;\n }\n if (typeof reporterOptions.queueDirectory === 'string') target.queueDirectory = reporterOptions.queueDirectory;\n if (typeof reporterOptions.uploadArtifacts === 'boolean') target.uploadArtifacts = reporterOptions.uploadArtifacts;\n if (typeof reporterOptions.artifactMaxSizeMb === 'number') target.artifactMaxSizeMb = reporterOptions.artifactMaxSizeMb;\n }\n\n // Layer 1: Environment variable overrides (highest priority)\n const envApiKey = process.env.TESTRELIC_API_KEY;\n if (envApiKey && envApiKey.length > 0) {\n target.apiKey = envApiKey;\n }\n\n const envEndpoint = process.env.TESTRELIC_CLOUD_ENDPOINT;\n if (envEndpoint && isValidEndpointUrl(envEndpoint)) {\n target.endpoint = envEndpoint;\n }\n\n const envUpload = process.env.TESTRELIC_UPLOAD_STRATEGY;\n if (envUpload && ['batch', 'realtime', 'both'].includes(envUpload)) {\n target.uploadStrategy = envUpload as UploadStrategy;\n }\n\n const envTimeout = process.env.TESTRELIC_CLOUD_TIMEOUT;\n if (envTimeout) {\n const parsed = parseInt(envTimeout, 10);\n if (!isNaN(parsed) && parsed >= 1000 && parsed <= 120000) {\n target.timeout = parsed;\n }\n }\n\n const config = Object.freeze(target) as unknown as CloudConfig;\n\n // Validate before returning — fall back to defaults on invalid config\n if (!isValidCloudConfig(config)) {\n return DEFAULT_CLOUD_CONFIG;\n }\n\n return config;\n}\n\nexport { DEFAULT_CLOUD_CONFIG };\n","/**\n * Config resolution with prototype pollution prevention.\n * Merges user options into defaults, freezes the result.\n */\n\nimport type { ReporterConfig, NavigationType, CloudConfig, ReportMode } from '@testrelic/core';\nimport { isValidConfig, createError, ErrorCode } from '@testrelic/core';\nimport { discoverConfigFile, parseConfigFile, resolveEnvVars, mergeCloudConfig } from './cloud-config.js';\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 /** Master on/off for Test Navigation capture. Default true. When false the\n * reporter drops navigation data before upload and logs a one-time notice. */\n readonly captureNavigation: 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 readonly includeActionSteps: boolean;\n readonly captureConsoleLogs: boolean;\n readonly cloud: CloudConfig | null;\n readonly reportMode: ReportMode;\n readonly streamingThreshold: number;\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.captureNavigation = options?.captureNavigation ?? 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 target.includeActionSteps = options?.includeActionSteps ?? true;\n target.captureConsoleLogs = options?.captureConsoleLogs ?? true;\n\n // Cloud config: merge from config file + reporter options + env vars\n const fileConfigPath = discoverConfigFile(process.cwd());\n const fileConfig = fileConfigPath ? parseConfigFile(fileConfigPath) : null;\n const resolvedFileConfig = fileConfig ? resolveEnvVars(fileConfig) : null;\n const cloudConfig = mergeCloudConfig(resolvedFileConfig, options?.cloud);\n target.cloud = cloudConfig.apiKey ? cloudConfig : null;\n target.reportMode = options?.reportMode ?? 'streaming';\n target.streamingThreshold = options?.streamingThreshold ?? 0;\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.6.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 runUrl: env.GITHUB_SERVER_URL && env.GITHUB_REPOSITORY && env.GITHUB_RUN_ID\n ? `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}`\n : 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 runUrl: env.CI_PIPELINE_URL ?? 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 runUrl: env.BUILD_URL ?? null,\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 runUrl: env.CIRCLE_BUILD_URL ?? null,\n };\n}\n\nfunction detectBitbucketPipelines(env: Record<string, string | undefined>): CIMetadata | null {\n if (!env.BITBUCKET_PIPELINE_UUID) return null;\n const runUrl = env.BITBUCKET_WORKSPACE && env.BITBUCKET_REPO_SLUG && env.BITBUCKET_PIPELINE_UUID\n ? `https://bitbucket.org/${env.BITBUCKET_WORKSPACE}/${env.BITBUCKET_REPO_SLUG}/pipelines/results/${env.BITBUCKET_PIPELINE_UUID}`\n : null;\n return {\n provider: 'bitbucket-pipelines',\n buildId: env.BITBUCKET_BUILD_NUMBER ?? null,\n commitSha: env.BITBUCKET_COMMIT ?? null,\n branch: env.BITBUCKET_BRANCH ?? null,\n runUrl,\n };\n}\n\nconst detectors: DetectFn[] = [\n detectGitHubActions,\n detectGitLabCI,\n detectJenkins,\n detectCircleCI,\n detectBitbucketPipelines,\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.test-row-retries{background:var(--bg-2);border-bottom:1px solid var(--bd-s)}\n.test-row-retry-item{padding-left:42px;border-bottom:1px solid var(--bd-s)}\n.test-row-retry-item:last-child{border-bottom:none}\n.test-row-retry-item .test-row-title{font-size:13px;color:var(--fg-2);font-weight:400}\n.test-row-retry-item .status-indicator{width:8px;height:8px}\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.copy-prompt-btn{margin-left:auto;display:inline-flex;align-items:center;gap:5px;padding:4px 10px;font-size:11px;font-weight:500;font-family:inherit;color:var(--fg-1);background:var(--bg-3);border:1px solid var(--bd);border-radius:6px;cursor:pointer;transition:all .15s;white-space:nowrap}\n.copy-prompt-btn:hover{background:var(--bg-2);color:var(--fg);border-color:var(--bd-m)}\n.copy-prompt-btn.copied{color:#34d399;border-color:rgba(52,211,153,0.3);background:rgba(52,211,153,0.08)}\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{border:1px solid var(--bd);border-radius:8px;overflow:hidden;background:var(--bg-1)}\n.ndt-toolbar{display:flex;align-items:center;gap:4px;padding:4px 8px;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:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\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;background:var(--bg)}\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:4px 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-row.seekable{cursor:pointer}\n.ndt-row--active{background:rgba(3,183,156,0.1) !important;border-left:3px solid #03b79c}\n.ndt-row--active .ndt-url{color:#2dd4a8}\n.ndt-offset{color:#03b79c !important;font-weight:600}\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/* ── Action Steps ── */\n.actions-header{font-size:11px;font-weight:700;color:var(--fg-1);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px;display:flex;align-items:center;gap:6px}\n.actions-header svg{opacity:.6}\n.actions-count{font-weight:400;color:var(--fg-2);font-size:10px}\n.actions-timeline{display:flex;flex-direction:column;gap:0}\n.action-connector{width:2px;height:6px;background:var(--bd-m);margin-left:18px}\n.action-step{display:flex;align-items:center;gap:8px;padding:6px 12px;border-radius:6px;font-size:12px;transition:background .1s}\n.action-step:hover{background:var(--hvr)}\n.action-badge{font-size:9px;font-weight:600;padding:2px 7px;border-radius:4px;text-transform:uppercase;letter-spacing:.03em;flex-shrink:0;white-space:nowrap}\n.action-badge-ui{background:rgba(59,130,246,0.12);color:#60a5fa}\n.action-badge-assertion{background:rgba(34,197,94,0.12);color:#22c55e}\n.action-badge-assertion.failed{background:rgba(239,68,68,0.12);color:#ef4444}\n.action-badge-custom{background:rgba(107,114,128,0.1);color:var(--fg-2)}\n.action-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--fg)}\n.action-duration{font-size:10px;color:var(--fg-2);flex-shrink:0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n.action-failed{background:rgba(239,68,68,0.04)}\n.action-failed:hover{background:rgba(239,68,68,0.08)}\n.action-error{font-size:11px;color:#ef4444;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre-wrap;word-break:break-word;line-height:1.5;padding:4px 12px 8px;margin-bottom:2px}\n.action-step.seekable{cursor:pointer}\n.action-step.seekable:hover{background:rgba(3,183,156,0.06)}\n.action-offset{font-size:9px;color:#03b79c;font-weight:600;flex-shrink:0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;padding:1px 5px;border-radius:3px;background:rgba(3,183,156,0.08)}\n.action-active{background:rgba(3,183,156,0.12);border-left:3px solid #03b79c;transition:background .2s,border-left .2s}\n.action-active .action-title{color:#2dd4a8}\n.action-active .action-offset{background:rgba(3,183,156,0.2)}\n.action-step.seeking{animation:trSeekFlash .4s ease-out}\n@keyframes trSeekFlash{0%{background:rgba(3,183,156,0.15)}100%{background:transparent}}\n\n/* ── Console/Logs Panel ── */\n.console-panel{border:1px solid var(--bd);border-radius:8px;overflow:hidden;background:var(--bg-1)}\n.console-header{font-size:11px;font-weight:700;color:var(--fg-1);text-transform:uppercase;letter-spacing:.06em;display:flex;align-items:center;gap:6px;padding:8px 12px;border-bottom:1px solid var(--bd);background:var(--bg-2)}\n.console-header svg{opacity:.6}\n.console-count{font-weight:400;color:var(--fg-2);font-size:10px}\n.console-error-count{color:#f87171;font-weight:500;font-size:10px;text-transform:none;letter-spacing:0;background:rgba(248,113,113,0.1);padding:1px 6px;border-radius:3px}\n.console-warn-count{color:#fbbf24;font-weight:500;font-size:10px;text-transform:none;letter-spacing:0;background:rgba(251,191,36,0.1);padding:1px 6px;border-radius:3px}\n.console-toolbar{display:flex;gap:3px;padding:4px 8px;border-bottom:1px solid var(--bd);flex-wrap:wrap;background:var(--bg-2)}\n.console-filter-btn{font-size:10px;padding:2px 8px;border-radius:4px;border:1px solid transparent;background:transparent;color:var(--fg-2);cursor:pointer;transition:all .15s;font-family:inherit}\n.console-filter-btn.active{background:var(--bg-3);color:var(--fg-1);border-color:var(--bd-m);font-weight:600}\n.console-filter-btn:hover{background:var(--hvr);color:var(--fg-1)}\n.console-list{max-height:400px;overflow-y:auto;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;background:var(--bg)}\n.console-list::-webkit-scrollbar{width:5px}.console-list::-webkit-scrollbar-thumb{background:var(--scroll-thumb);border-radius:4px}\n.console-entry{display:flex;align-items:baseline;gap:6px;padding:2px 10px;border-bottom:1px solid var(--bd-xs);line-height:20px}\n.console-entry:hover{background:var(--hvr)}\n.console-level-badge{font-weight:600;font-size:9px;min-width:32px;text-transform:uppercase;letter-spacing:.03em}\n.console-time{color:var(--fg-2);font-size:10px;white-space:nowrap;min-width:65px}\n.console-text{flex:1;word-break:break-word;white-space:pre-wrap;color:var(--fg)}\n.console-loc{color:var(--fg-2);font-size:10px;white-space:nowrap;margin-left:auto;max-width:180px;overflow:hidden;text-overflow:ellipsis}\n.console-entry.seekable{cursor:pointer}\n.console-entry.seekable:hover{background:rgba(3,183,156,0.06)}\n.console-active{background:rgba(3,183,156,0.12) !important;border-left:3px solid #03b79c}\n.console-active .console-text{color:#2dd4a8}\n.console-offset{color:#03b79c;font-size:9px;font-weight:600;min-width:42px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n.console-level-error,.console-level-stderr{background:rgba(248,113,113,0.06)}\n.console-level-warn{background:rgba(251,191,36,0.06)}\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/* Loading Overlay */\n.loading-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:var(--bg);z-index:9999;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px}\n.loading-spinner{width:36px;height:36px;border:3px solid var(--bd-l);border-top-color:var(--fg-1);border-radius:50%;animation:spin .8s linear infinite}\n.loading-text{font:13px/1 -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;color:var(--fg-1)}\n@keyframes spin{to{transform:rotate(360deg)}}\n/* Global scrollbar styling */\n*{scrollbar-width:thin;scrollbar-color:var(--scroll-thumb) transparent}\n::-webkit-scrollbar{width:6px;height:6px}\n::-webkit-scrollbar-track{background:transparent}\n::-webkit-scrollbar-thumb{background:var(--scroll-thumb);border-radius:3px}\n::-webkit-scrollbar-thumb:hover{background:var(--fg-2)}\n::-webkit-scrollbar-corner{background:transparent}\n/* Virtual grid sentinel */\n.vg-sentinel{height:1px}\n/* ── Streaming: detail loading / error states ── */\n.detail-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:48px 20px;gap:14px}\n.detail-loading .loading-spinner{width:28px;height:28px;border:3px solid var(--bd-l);border-top-color:var(--fg-1);border-radius:50%;animation:spin .8s linear infinite}\n.detail-loading .loading-text{font-size:12px;color:var(--fg-2)}\n.detail-error{padding:32px 20px;text-align:center;color:var(--fg-2);font-size:13px;line-height:1.7}\n.detail-error .failure-message{margin-bottom:16px;padding:12px;background:rgba(239,68,68,0.06);border:1px solid rgba(239,68,68,0.15);border-radius:8px;font-size:12px;color:#f87171;text-align:left;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre-wrap;word-break:break-word}\n.detail-error code{font-size:11px;padding:2px 6px;background:var(--bg-2);border-radius:4px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n.server-offline-banner{padding:8px 14px;background:rgba(251,191,36,0.08);border:1px solid rgba(251,191,36,0.2);border-radius:6px;font-size:11px;color:#fbbf24;text-align:center;margin-bottom:12px}\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,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\"/g,'&quot;').replace(/'/g,'&#39;')}\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,visual:t.visual||[],networkRequests:t.networkRequests||[],steps:[],apiCalls:[],actions:t.actions||[],consoleLogs:t.consoleLogs||[],\n apiAssertions:t.apiAssertions||[],\n apiCallCount:0,apiStatusMix:{'2xx':0,'3xx':0,'4xx':0,'5xx':0,'error':0},\n assertionsPassed:0,assertionsFailed:0,\n device:t.device||null,aiDefects:t.aiDefects||[]\n };\n var _as=testMap[t.testId].apiAssertions;\n for(var _ai=0;_ai<_as.length;_ai++){\n if(_as[_ai].status==='failed')testMap[t.testId].assertionsFailed++;\n else testMap[t.testId].assertionsPassed++;\n }\n }\n if(entry.type==='api_call'){\n var _mt=testMap[t.testId];\n _mt.apiCallCount++;\n var _code=entry.response&&typeof entry.response.status==='number'?entry.response.status:null;\n var _bucket=_code===null?'error':(_code>=500?'5xx':(_code>=400?'4xx':(_code>=300?'3xx':(_code>=200?'2xx':'error'))));\n _mt.apiStatusMix[_bucket]++;\n _mt.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||entry.timestamp,duration:entry.duration||entry.durationOnUrl||0,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 /* ── Streaming mode: build tests from compact index, grouping retries under the final attempt ── */\n if(data.reportMode==='streaming'&&data.index&&data.index.length>0){\n tests=[];testMap={};\n var _retryGroups={};\n for(var xi=0;xi<data.index.length;xi++){\n var ix=data.index[xi];\n var gk=ix.retryGroupKey||ix.id;\n if(!_retryGroups[gk])_retryGroups[gk]={final:null,attempts:[]};\n if(ix.isRetry){_retryGroups[gk].attempts.push(ix);}\n else{_retryGroups[gk].final=ix;}\n }\n for(var gk2 in _retryGroups){\n if(!_retryGroups.hasOwnProperty(gk2))continue;\n var grp=_retryGroups[gk2];\n // Defensive: if every attempt in a group was marked isRetry (shouldn't happen),\n // fall back to the highest retryIndex so the test still surfaces.\n var final=grp.final;\n if(!final&&grp.attempts.length>0){\n final=grp.attempts.reduce(function(a,b){return b.retryIndex>a.retryIndex?b:a;});\n grp.attempts=grp.attempts.filter(function(a){return a!==final;});\n }\n if(!final)continue;\n var ix2=final;\n var st={\n testId:ix2.id,title:ix2.title,status:ix2.status,duration:ix2.duration,\n filePath:ix2.filePath,tags:ix2.tags||[],isFlaky:ix2.status==='flaky',\n testType:ix2.testType||'e2e',retryCount:grp.attempts.length,suiteName:'',\n startedAt:'',completedAt:'',\n networkRequests:[],steps:[],apiCalls:[],actions:[],consoleLogs:[],\n artifacts:null,failure:null,retryStatus:null,expectedStatus:null,actualStatus:null,\n hasNetworkData:ix2.hasNetworkData,hasConsoleData:ix2.hasConsoleData,\n hasActionSteps:ix2.hasActionSteps,hasArtifacts:ix2.hasArtifacts,\n errorMessage:ix2.errorMessage,networkCount:ix2.networkCount||0,\n consoleCount:ix2.consoleCount||0,actionCount:ix2.actionCount||0,\n /* Carried from the index so the grid and the filter bar can count\n visual comparisons without opening every test's detail file. */\n visualCount:ix2.visualCount||0,visualFailedCount:ix2.visualFailedCount||0,\n domChangeCount:ix2.domChangeCount||0,visualDiffPath:ix2.visualDiffPath||null,\n /* API and mobile evidence, same rule: the grid must never open a\n test's detail file to draw a row. */\n apiCallCount:ix2.apiCallCount||0,apiStatusMix:ix2.apiStatusMix||null,\n assertionsPassed:ix2.assertionsPassed||0,assertionsFailed:ix2.assertionsFailed||0,\n firstFailedAssertion:ix2.firstFailedAssertion||null,\n device:ix2.device||null,stepKinds:ix2.stepKinds||null,\n deviceLogErrors:ix2.deviceLogErrors||0,hasCrashLog:!!ix2.hasCrashLog,\n hasVideo:!!ix2.hasVideo,aiDefectCount:ix2.aiDefectCount||0,\n retryAttempts:grp.attempts.slice().sort(function(a,b){return a.retryIndex-b.retryIndex;}).map(function(a){\n return {testId:a.id,retryIndex:a.retryIndex,status:a.status,duration:a.duration,errorMessage:a.errorMessage};\n })\n };\n testMap[st.testId]=st;\n tests.push(st);\n // Register each retry attempt in testMap too (keyed by its own id) so opening\n // its drawer has a title/meta to merge with the fetched detail — it never\n // appears in the grid itself, only as a nested row under the final attempt.\n for(var ai=0;ai<st.retryAttempts.length;ai++){\n var ra=st.retryAttempts[ai];\n testMap[ra.testId]={\n testId:ra.testId,title:st.title+' (attempt '+(ra.retryIndex+1)+')',status:ra.status,\n duration:ra.duration,filePath:st.filePath,tags:st.tags,isFlaky:false,\n testType:st.testType,retryCount:0,suiteName:'',startedAt:'',completedAt:'',\n networkRequests:[],steps:[],apiCalls:[],actions:[],consoleLogs:[],\n artifacts:null,failure:null,retryStatus:'attempt '+(ra.retryIndex+1)+' of '+(st.retryAttempts.length+1),\n expectedStatus:null,actualStatus:null,errorMessage:ra.errorMessage,\n networkCount:0,consoleCount:0,actionCount:0\n };\n }\n }\n tests.sort(function(a,b){\n if(a.filePath!==b.filePath)return a.filePath<b.filePath?-1:1;\n return 0;\n });\n }\n\n /* ── Streaming detail cache (LRU, max 50 entries) ── */\n var _detailCache={};\n var _detailCacheKeys=[];\n var _detailCacheMax=50;\n function cacheDetail(testId,data){\n if(_detailCache[testId]){\n var ki=_detailCacheKeys.indexOf(testId);\n if(ki>-1)_detailCacheKeys.splice(ki,1);\n }else if(_detailCacheKeys.length>=_detailCacheMax){\n var evict=_detailCacheKeys.shift();\n delete _detailCache[evict];\n }\n _detailCache[testId]=data;\n _detailCacheKeys.push(testId);\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 drawerBodyEl=document.getElementById('drawer-body');\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 /* renderSummary lives in html-js-layout: the six equal stat chips were\n replaced by the verdict band, which collapses empty statuses. */\n\n /* Longest test in the run, so each bar reads as \"relative to the slowest\".\n Recomputed per grid render because filtering changes the set. */\n var _maxDur=1;\n function rowBarPct(d){\n var pct=Math.round(((d||0)/_maxDur)*100);\n /* A bar too short to see says nothing; floor it at something visible. */\n return Math.max(4,Math.min(100,pct));\n }\n\n /* Small square icons: what was captured, before you open the test. */\n\n /* Both report modes reach these renderers with different shapes: the\n streaming index carries counters, the embedded testMap carries the arrays.\n Reading only the counters made the evidence column, the needs-attention\n chips and the diff thumbnail vanish in embedded mode. */\n function evVisualCount(t){return t.visual?t.visual.length:(t.visualCount||0);}\n function evVisualFailed(t){\n if(t.visual){var n=0;for(var i=0;i<t.visual.length;i++)if(t.visual[i].status==='failed')n++;return n;}\n return t.visualFailedCount||0;\n }\n function evDomChanges(t){\n if(t.visual){var n=0;for(var i=0;i<t.visual.length;i++)n+=(t.visual[i].domChangeCount||0);return n;}\n return t.domChangeCount||0;\n }\n function evHasArtifacts(t){\n if(t.artifacts)return !!(t.artifacts.screenshot||t.artifacts.video||t.artifacts.trace);\n return !!t.hasArtifacts;\n }\n function evNetworkCount(t){return t.networkRequests?t.networkRequests.length:(t.networkCount||0);}\n function evConsoleCount(t){return t.consoleLogs?t.consoleLogs.length:(t.consoleCount||0);}\n function evDiffPath(t){\n if(t.visualDiffPath)return t.visualDiffPath;\n if(t.visual){for(var i=0;i<t.visual.length;i++){if(t.visual[i].diff)return t.visual[i].diff;}}\n return null;\n }\n\n function rowEvidence(t){\n var h='<div class=\"trow-evidence\">';\n var any=false;\n if(typeof apiRowEvidence==='function'){\n var _ae=apiRowEvidence(t);\n if(_ae){h+=_ae;any=true;}\n }\n if(typeof mobileRowEvidence==='function'){\n var _me=mobileRowEvidence(t);\n if(_me){h+=_me;any=true;}\n }\n var _vis=evVisualCount(t),_visFail=evVisualFailed(t),_dom=evDomChanges(t);\n var _net=evNetworkCount(t),_con=evConsoleCount(t);\n if(_vis>0){\n any=true;\n h+='<span class=\"tev'+(_visFail>0?' tev-visual':'')+'\" title=\"'+(_visFail>0?'Visual comparison failed':'Visual comparison')+'\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/><path d=\"M12 3v18\"/></svg></span>';\n }\n if(_dom>0){\n any=true;\n h+='<span class=\"tev tev-dom\" title=\"'+_dom+' DOM elements changed\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M8 3H5a2 2 0 00-2 2v3M16 3h3a2 2 0 012 2v3M8 21H5a2 2 0 01-2-2v-3M16 21h3a2 2 0 002-2v-3\"/></svg></span>';\n }\n if(evHasArtifacts(t)){\n any=true;\n h+='<span class=\"tev\" title=\"Video and trace\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polygon points=\"5 3 19 12 5 21\"/></svg></span>';\n }\n if(_net>0){\n any=true;\n h+='<span class=\"tev\" title=\"'+_net+' network requests\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M3 12h4l3 8 4-16 3 8h4\"/></svg></span>';\n }\n if(_con>0){\n any=true;\n h+='<span class=\"tev\" title=\"'+_con+' console messages\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M4 17l6-6-6-6M12 19h8\"/></svg></span>';\n }\n /* Keep the column even when empty so the rows stay aligned. */\n if(!any)h+='<span style=\"width:18px\"></span>';\n return h+'</div>';\n }\n\n /* ── Virtual Grid: progressive chunked rendering ── */\n var _vgGroups=[];\n var _vgRenderedTo=0;\n var _vgChunkSize=10;\n var _vgSentinel=null;\n var _vgObserver=null;\n\n function _vgBuildGroups(filtered){\n var groups=[];var lastFile='';var g=null;\n for(var i=0;i<filtered.length;i++){\n var t=filtered[i];\n if(t.filePath!==lastFile){\n g={filePath:t.filePath,tests:[]};\n groups.push(g);\n lastFile=t.filePath;\n }\n g.tests.push(t);\n }\n return groups;\n }\n\n function _vgRenderGroupHtml(g){\n var h='<div class=\"file-group\">';\n h+='<div class=\"file-group-header\">'+esc(g.filePath)+'</div>';\n h+='<div class=\"file-group-card\">';\n for(var i=0;i<g.tests.length;i++){\n var t=g.tests[i];\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 tri-inline\">';\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+=rowEvidence(t);\n h+='<div class=\"trow-time\"><div class=\"trow-bar\"><span class=\"tb-'+t.status+'\" style=\"width:'+rowBarPct(t.duration)+'%\"></span></div>';\n h+='<span class=\"test-row-dur\">'+fmtDur(t.duration)+'</span></div>';\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 if(t.retryAttempts&&t.retryAttempts.length>0){\n h+='<div class=\"test-row-retries\">';\n for(var ri=0;ri<t.retryAttempts.length;ri++){\n var ra=t.retryAttempts[ri];\n h+='<div class=\"test-row test-row-retry-item\" data-testid=\"'+esc(ra.testId)+'\">';\n h+='<div class=\"status-indicator si-'+ra.status+'\"></div>';\n h+='<div class=\"test-row-info\"><div class=\"test-row-title\">Attempt '+(ra.retryIndex+1)+' &mdash; '+esc(ra.status)+(ra.errorMessage?': '+esc(ra.errorMessage):'')+'</div></div>';\n h+='<span class=\"test-row-dur\">'+fmtDur(ra.duration)+'</span>';\n h+='<svg class=\"test-row-arrow\" width=\"14\" height=\"14\" 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 h+='</div>';\n }\n }\n h+='</div></div>';\n return h;\n }\n\n function _vgRenderChunk(){\n if(_vgRenderedTo>=_vgGroups.length){\n if(_vgSentinel)_vgSentinel.style.display='none';\n return;\n }\n var end=Math.min(_vgRenderedTo+_vgChunkSize,_vgGroups.length);\n var frag=document.createDocumentFragment();\n var tmp=document.createElement('div');\n var h='';\n for(var i=_vgRenderedTo;i<end;i++){h+=_vgRenderGroupHtml(_vgGroups[i]);}\n tmp.innerHTML=h;\n while(tmp.firstChild)frag.appendChild(tmp.firstChild);\n /* Insert before sentinel */\n if(_vgSentinel&&_vgSentinel.parentNode){\n _vgSentinel.parentNode.insertBefore(frag,_vgSentinel);\n }\n _vgRenderedTo=end;\n if(_vgRenderedTo>=_vgGroups.length&&_vgSentinel){\n _vgSentinel.style.display='none';\n }\n }\n\n function _vgSetupObserver(){\n if(_vgObserver){_vgObserver.disconnect();_vgObserver=null;}\n if(!_vgSentinel)return;\n if(typeof IntersectionObserver==='undefined'){\n /* Fallback: render everything */\n while(_vgRenderedTo<_vgGroups.length)_vgRenderChunk();\n return;\n }\n _vgObserver=new IntersectionObserver(function(entries){\n if(entries[0].isIntersecting)_vgRenderChunk();\n },{rootMargin:'600px'});\n _vgObserver.observe(_vgSentinel);\n }\n\n /* ── Render: Test Grid ── */\n function renderTestGrid(){\n if(_vgObserver){_vgObserver.disconnect();_vgObserver=null;}\n var filtered=getFilteredTests();\n /* The bar on each row is relative to the slowest test CURRENTLY SHOWN, so\n filtering to the slow ones still spreads them across the full width. */\n _maxDur=1;\n for(var md=0;md<filtered.length;md++){if(filtered[md].duration>_maxDur)_maxDur=filtered[md].duration;}\n if(filtered.length===0){testGridEl.innerHTML='<div class=\"no-tests\">No tests match your filters</div>';_vgGroups=[];return;}\n _vgGroups=_vgBuildGroups(filtered);\n _vgRenderedTo=0;\n /* Render initial batch directly, then progressively load the rest */\n var initCount=Math.min(_vgChunkSize,_vgGroups.length);\n var h='';\n for(var i=0;i<initCount;i++){h+=_vgRenderGroupHtml(_vgGroups[i]);}\n _vgRenderedTo=initCount;\n h+='<div class=\"vg-sentinel\" id=\"vg-sentinel\"></div>';\n testGridEl.innerHTML=h;\n _vgSentinel=document.getElementById('vg-sentinel');\n if(_vgRenderedTo<_vgGroups.length){\n _vgSetupObserver();\n }else if(_vgSentinel){\n _vgSentinel.style.display='none';\n }\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,hasT=!!artifacts.trace;\n if(!hasS&&!hasV&&!hasT)return '<div class=\"artifact-empty\">No artifacts captured</div>';\n var tabs=[];\n if(hasS)tabs.push('screenshot');\n if(hasV)tabs.push('video');\n if(hasT)tabs.push('trace');\n\n var h='';\n if(tabs.length>1){\n h+='<div class=\"seg-ctrl\" data-seg-group=\"artifacts\">';\n for(var ti=0;ti<tabs.length;ti++){\n var t=tabs[ti],ico='';\n if(t==='screenshot')ico='<svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/><circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\"/><path d=\"M21 15l-5-5L5 21\"/></svg>';\n else if(t==='video')ico='<svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polygon points=\"5 3 19 12 5 21\"/></svg>';\n else ico='<svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M4 4h16v6H4zM4 14h16v6H4z\"/></svg>';\n var label=t==='screenshot'?'Screenshot':(t==='video'?'Video':'Trace');\n h+='<button class=\"seg-btn'+(ti===0?' active':'')+'\" data-seg=\"'+t+'\">'+ico+label+'</button>';\n }\n h+='</div>';\n }\n\n /* No lazy loading. A lazily-loaded image inside a display:none pane is\n never fetched by Chrome, and every pane but the first starts hidden —\n the screenshot simply never appeared. The same bug was fixed for the\n visual comparison images and left standing here. */\n if(hasS){\n h+='<div class=\"seg-pane'+(tabs[0]==='screenshot'?' active':'')+'\" data-seg-pane=\"screenshot\">';\n h+='<img class=\"artifact-col-img\" src=\"'+esc(artifacts.screenshot)+'\" alt=\"Screenshot\">';\n h+='<div class=\"artifact-col-caption\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7\"/></svg>Click to enlarge</div></div>';\n }\n if(hasV){\n h+='<div class=\"seg-pane'+(tabs[0]==='video'?' active':'')+'\" data-seg-pane=\"video\">';\n h+='<div class=\"artifact-col-video\"><video controls preload=\"metadata\" src=\"'+esc(artifacts.video)+'\"></video></div>';\n h+='<div class=\"artifact-col-caption\">'+esc(artifacts.video.split('/').pop())+'</div></div>';\n }\n /* The trace is the thing a failure is actually debugged from, and the\n report had no way to reach it even though it was captured and copied. */\n if(hasT){\n h+='<div class=\"seg-pane'+(tabs[0]==='trace'?' active':'')+'\" data-seg-pane=\"trace\">';\n h+='<div class=\"trace-open\"><a class=\"trace-link\" href=\"'+esc(artifacts.trace)+'\" download>';\n h+='<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M12 3v12M7 10l5 5 5-5M5 21h14\"/></svg>';\n h+='Download '+esc(artifacts.trace.split('/').pop())+'</a>';\n h+='<div class=\"trace-hint\">Open it with <code>npx playwright show-trace &lt;file&gt;</code>, or drop it on <code>trace.playwright.dev</code>.</div>';\n h+='</div></div>';\n }\n return h;\n }\n\n /* ── Render: Drawer Content ── */\n function renderDrawer(testId,detail){\n var t=detail||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 /* ── Stacked sections: 1) Artifacts 2) Timeline / Network 3) API Calls ── */\n var hasArt=t.artifacts&&(t.artifacts.screenshot||t.artifacts.video||t.artifacts.trace);\n var hasVisual=t.visual&&t.visual.length>0;\n var hasSteps=t.steps&&t.steps.length>0;\n var hasApiCalls=t.apiCalls&&t.apiCalls.length>0;\n var hasActions=t.actions&&t.actions.length>0;\n var hasConsoleLogs=t.consoleLogs&&t.consoleLogs.length>0;\n var hasNetworkReqs=t.networkRequests&&t.networkRequests.length>0;\n var hasVideo=!!(t.artifacts&&t.artifacts.video);\n /* Compute base time for video offset calculation (console/network) */\n var baseTime=0;\n if(hasVideo&&t.startedAt){baseTime=Date.parse(t.startedAt);}\n if(!baseTime&&hasVideo&&t.consoleLogs&&t.consoleLogs.length>0&&t.consoleLogs[0].timestamp){baseTime=Date.parse(t.consoleLogs[0].timestamp);}\n\n var hasFailure=(t.status==='failed'||t.status==='flaky')&&t.failure;\n var hasDom=false;\n if(t.visual){for(var dv=0;dv<t.visual.length;dv++){if(t.visual[dv].domChangeCount)hasDom=true;}}\n\n /* What the rail is allowed to offer — built from what actually rendered,\n so a jump can never land on a section that is not there. */\n var present={};\n if(hasFailure)present.failure={};\n if(hasVisual)present.visual={n:t.visual.length};\n if(hasDom)present.dom={n:(function(){var n=0;for(var q=0;q<t.visual.length;q++)n+=t.visual[q].domChangeCount||0;return n;})()};\n if(hasArt)present.artifacts={};\n if(hasActions)present.actions={n:t.actions.length};\n if(hasSteps)present.timeline={n:t.steps.length};\n var _asserts=t.apiAssertions||[];\n var hasAssertions=_asserts.length>0;\n if(hasAssertions){\n var _af=0;\n for(var _q=0;_q<_asserts.length;_q++)if(_asserts[_q].status==='failed')_af++;\n present.assertions={n:_asserts.length,bad:_af>0};\n }\n if(hasApiCalls)present.api={n:t.apiCalls.length};\n if(!hasSteps&&hasConsoleLogs)present.console={n:t.consoleLogs.length};\n if(!hasSteps&&hasNetworkReqs)present.network={n:t.networkRequests.length};\n\n var anySection=hasArt||hasVisual||hasSteps||hasApiCalls||hasActions||hasConsoleLogs||hasNetworkReqs||hasAssertions;\n\n h+='<div class=\"drawer-layout\">';\n h+='<div class=\"drawer-rail\">'+renderDrawerRail(present,t)+'</div>';\n h+='<div class=\"drawer-main\">';\n\n if(hasFailure)h+='<div class=\"drawer-section\" data-section=\"failure\">'+renderFailure(t.failure,t.filePath,t.title,t.tags)+'</div>';\n else if(t.status==='failed')h+='<div class=\"drawer-section\" data-section=\"failure\" style=\"padding:14px;background:var(--bg-2);border:1px solid var(--bd);border-radius:10px;font-size:12px;color:var(--fg-2);font-style:italic\">No diagnostic information available</div>';\n\n if(anySection){\n h+='<div class=\"drawer-sections\">';\n if(hasVisual)h+='<div class=\"drawer-section\" data-section=\"visual\">'+renderVisualSection(t.visual)+'</div>';\n if(hasArt)h+='<div class=\"drawer-section\" data-section=\"artifacts\">'+renderArtifactColumn(t.artifacts)+'</div>';\n if(hasActions)h+='<div class=\"drawer-section\" data-section=\"actions\">'+renderActionsColumn(t.actions,hasVideo)+'</div>';\n if(hasSteps){var _pgMeta=t._networkTotal?{testId:t._testId,page:t._networkPage||1,total:t._networkTotal,pageSize:t._networkPageSize||500}:null;h+='<div class=\"drawer-section\" data-section=\"timeline\">'+renderTimelineColumn(t.steps,t.networkRequests,t.consoleLogs,t.testType,hasVideo,baseTime,_pgMeta)+'</div>';}\n if(!hasSteps&&hasNetworkReqs&&typeof renderNetworkDevTools==='function'){var _pgMeta2=t._networkTotal?{testId:t._testId,page:t._networkPage||1,total:t._networkTotal,pageSize:t._networkPageSize||500}:null;h+='<div class=\"drawer-section\" data-section=\"network\">'+renderNetworkDevTools(t.networkRequests,false,0,_pgMeta2)+'</div>';}\n if(!hasSteps&&hasConsoleLogs)h+='<div class=\"drawer-section\" data-section=\"console\">'+renderConsoleColumn(t.consoleLogs,t.testType,hasVideo,baseTime)+'</div>';\n /* Assertions before the calls: the assertion is what failed, the call\n is only where it happened. */\n if(hasAssertions&&typeof renderAssertionsSection==='function')\n h+='<div class=\"drawer-section\" data-section=\"assertions\">'+renderAssertionsSection(_asserts,t.apiCalls)+'</div>';\n if(hasApiCalls)h+='<div class=\"drawer-section\" data-section=\"api\">'+renderApiCallsColumn(t.apiCalls,_asserts)+'</div>';\n h+='</div>';\n }\n\n h+='</div></div>';\n\n h+='</div>';\n drawerBodyEl.innerHTML=h;\n /* Wire up video ↔ action step sync if both are present */\n if(typeof syncActionsWithVideo==='function')syncActionsWithVideo();\n }\n\n /* ── Drawer open / close ── */\n /* Fetch (streaming) and render one test into the detail view. The routing\n around it lives in html-js-layout; this only fills the page. */\n function loadAndRenderTest(testId){\n if(data.reportMode==='streaming'){\n if(_detailCache[testId]){renderDrawer(testId,_detailCache[testId]);return;}\n drawerBodyEl.innerHTML='<div class=\"detail-loading\"><div class=\"loading-spinner\"></div><div class=\"loading-text\">Loading test details...</div></div>';\n if(window.__trStreamingFetchDetail){\n window.__trStreamingFetchDetail(testId,function(err,detail){\n if(err){\n var t=testMap[testId];\n var emsg=t&&t.errorMessage?'<div class=\"failure-message\">'+esc(t.errorMessage)+'</div>':'';\n drawerBodyEl.innerHTML='<div class=\"detail-error\">'+emsg+'<p>Server unavailable &mdash; run <code>npx testrelic serve &lt;path&gt;</code> to view full details</p></div>';\n return;\n }\n /* Merge index entry with server detail metadata */\n var idx=testMap[testId]||{};\n var merged={};\n var k;for(k in idx){if(idx.hasOwnProperty(k))merged[k]=idx[k];}\n merged.actions=detail.actions||[];\n merged.apiAssertions=detail.apiAssertions||[];\n merged.artifacts=detail.artifacts||null;\n merged.visual=detail.visual||[];\n merged.failure=detail.failureDiagnostic||detail.failure||null;\n merged.steps=detail.navigations||[];\n if(detail.startedAt)merged.startedAt=detail.startedAt;\n /* Mark counts for lazy loading */\n merged._networkCount=detail.networkRequestsCount||idx.networkCount||0;\n merged._consoleCount=detail.consoleLogsCount||idx.consoleCount||0;\n merged._apiCallsCount=detail.apiCallsCount||0;\n merged._testId=testId;\n /* Initially empty — loaded on demand */\n merged.networkRequests=[];\n merged.consoleLogs=[];\n merged.apiCalls=[];\n cacheDetail(testId,merged);\n renderDrawer(testId,merged);\n /* Lazy-load heavy data once the page is showing */\n loadHeavyData(testId,merged);\n });\n }else{\n var t=testMap[testId];\n var emsg=t&&t.errorMessage?'<div class=\"failure-message\">'+esc(t.errorMessage)+'</div>':'';\n drawerBodyEl.innerHTML='<div class=\"detail-error\">'+emsg+'<p>Server unavailable &mdash; run <code>npx testrelic serve &lt;path&gt;</code> to view full details</p></div>';\n }\n return;\n }\n renderDrawer(testId);\n }\n\n /* Lazy-load heavy data (network, console, API calls) after drawer renders */\n function loadHeavyData(testId,merged){\n var fetchData=window.__trFetchTestData;\n if(!fetchData)return;\n var pending=0;\n var loaded=0;\n function onLoaded(){loaded++;if(loaded>=pending){renderDrawer(testId,merged);}}\n /* Load first page of network requests */\n if(merged._networkCount>0){\n pending++;\n fetchData(testId,'network',1,500,function(err,result){\n if(!err&&result){\n merged.networkRequests=result.items;\n merged._networkTotal=result.total;\n merged._networkPage=1;\n merged._networkPageSize=result.pageSize;\n merged._networkLoading=false;\n }\n onLoaded();\n });\n }\n /* Load first page of console logs */\n if(merged._consoleCount>0){\n pending++;\n fetchData(testId,'console',1,500,function(err,result){\n if(!err&&result){\n merged.consoleLogs=result.items;\n merged._consoleTotal=result.total;\n merged._consolePage=1;\n }\n onLoaded();\n });\n }\n /* Load first page of API calls */\n if(merged._apiCallsCount>0){\n pending++;\n fetchData(testId,'api-calls',1,500,function(err,result){\n if(!err&&result){\n merged.apiCalls=result.items;\n merged._apiCallsTotal=result.total;\n merged._apiCallsPage=1;\n }\n onLoaded();\n });\n }\n }\n\n /* ── Search (debounced) ── */\n var _searchTimer=0;\n function doSearch(q){\n clearTimeout(_searchTimer);\n _searchTimer=setTimeout(function(){searchQuery=q;applyFilters();},150);\n }\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,emptyMsg){\n if(!headers)return '<div class=\"ndt-body-empty\">'+(emptyMsg||('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\">'+(emptyMsg||('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\">Body truncated</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 var NDT_ROW_H=32;\n var NDT_DETAIL_H=280;\n var NDT_VBUF=30;\n\n function ndtVideoOffset(req,baseTime){\n if(!baseTime||!req.startedAt)return -1;\n var t=Date.parse(req.startedAt);\n if(isNaN(t))return -1;\n var off=(t-baseTime)/1000;\n return off>=0?off:-1;\n }\n\n function renderNetworkDevTools(reqs,hasVideo,baseTime,pageMeta){\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 /* Virtualized request list */\n h+='<div class=\"ndt-list ndt-vlist\" data-ndt-list=\"'+uid+'\" data-ndt-uid=\"'+uid+'\" style=\"overflow-y:auto;max-height:400px;position:relative\">';\n h+='<div class=\"ndt-vspacer\" style=\"height:'+(reqs.length*NDT_ROW_H)+'px;position:relative\"></div>';\n h+='</div></div>';\n\n /* Pre-compute video offsets */\n var ndtVideoOffsets=[];\n if(hasVideo&&baseTime){\n for(var noi=0;noi<reqs.length;noi++){ndtVideoOffsets.push(ndtVideoOffset(reqs[noi],baseTime));}\n }\n\n /* Store reqs data for virtual rendering */\n setTimeout(function(){\n var el=document.getElementById(uid);if(!el)return;\n var list=el.querySelector('.ndt-vlist');if(!list)return;\n var spacer=list.querySelector('.ndt-vspacer');\n var allReqs=reqs;\n var filteredIdxs=null;\n var openIdx=-1;\n var openTab='general';\n var highlightIdx=-1;\n\n function getVisible(){return filteredIdxs||allReqs.map(function(_,i){return i});}\n\n var lastDetailH=NDT_DETAIL_H;\n var NDT_TABS=['general','req-headers','req-body','res-headers','res-body'];\n\n function renderDetailWithTab(r,ri,activeTab){\n var html=renderNdtDetail(r,ri);\n /* Replace the default active tab/pane with the tracked one */\n if(activeTab&&activeTab!=='general'){\n /* Remove default active from general tab button and pane */\n html=html.replace('data-ndt-tab=\"general\" data-ndt-target','data-ndt-tab=\"general\" data-ndt-target');\n /* Swap active classes: remove from general, add to activeTab */\n var parts=html;\n /* Tabs: remove active from all, add to target */\n for(var t=0;t<NDT_TABS.length;t++){\n var tn=NDT_TABS[t];\n var isAct=tn===activeTab;\n parts=parts.replace(new RegExp('class=\"ndt-detail-tab'+(tn==='general'?' active':'')+'\" data-ndt-tab=\"'+tn+'\"'),'class=\"ndt-detail-tab'+(isAct?' active':'')+'\" data-ndt-tab=\"'+tn+'\"');\n }\n /* Panes: remove active from all, add to target */\n for(var p=0;p<NDT_TABS.length;p++){\n var pn=NDT_TABS[p];\n var isPAct=pn===activeTab;\n parts=parts.replace(new RegExp('class=\"ndt-detail-pane'+(pn==='general'?' active':'')+'\" data-ndt-pane=\"'+pn+'\"'),'class=\"ndt-detail-pane'+(isPAct?' active':'')+'\" data-ndt-pane=\"'+pn+'\"');\n }\n html=parts;\n }\n return html.replace('class=\"ndt-detail\"','class=\"ndt-detail show\"');\n }\n\n function paint(){\n var vis=getVisible();\n /* Find the visual index of the open row (if any) */\n var openVi=-1;\n if(openIdx>=0){for(var oi=0;oi<vis.length;oi++){if(vis[oi]===openIdx){openVi=oi;break;}}}\n var detailExtra=openVi>=0?lastDetailH:0;\n var totalH=vis.length*NDT_ROW_H+detailExtra;\n spacer.style.height=totalH+'px';\n var st=list.scrollTop;var vh=list.clientHeight;\n var startRow=Math.max(0,Math.floor(st/NDT_ROW_H)-NDT_VBUF);\n var endRow=Math.min(vis.length,Math.ceil((st+vh+detailExtra)/NDT_ROW_H)+NDT_VBUF);\n var out='';\n for(var vi=startRow;vi<endRow;vi++){\n var ri=vis[vi];var r=allReqs[ri];\n var isFail=r.statusCode>=400||r.statusCode===0;\n var isOpen=ri===openIdx;\n var isActive=ri===highlightIdx;\n var off=ndtVideoOffsets[ri];\n var seekable=off>=0;\n /* Rows after the open row shift down by the detail panel height */\n var rowTop=vi*NDT_ROW_H+(openVi>=0&&vi>openVi?lastDetailH:0);\n out+='<div class=\"ndt-row'+(isFail?' ndt-row--fail':'')+(isOpen?' ndt-row--open':'')+(seekable?' seekable':'')+(isActive?' ndt-row--active':'')+'\" data-ndt-idx=\"'+ri+'\" data-ndt-type=\"'+esc(r.resourceType)+'\" data-ndt-url=\"'+esc(r.url.toLowerCase())+'\"'+(seekable?' data-offset=\"'+off.toFixed(2)+'\"':'')+' style=\"position:absolute;top:'+rowTop+'px;left:0;right:0;height:'+NDT_ROW_H+'px\">';\n out+='<span class=\"ndt-status '+ndtStatusClass(r.statusCode)+'\">'+r.statusCode+'</span>';\n out+='<span class=\"ndt-method\">'+esc(r.method)+'</span>';\n out+='<span class=\"ndt-url\" title=\"'+esc(r.url)+'\">'+esc(urlPath(r.url))+'</span>';\n out+='<span class=\"ndt-type\">'+esc(RES_LABELS[r.resourceType]||r.resourceType)+'</span>';\n out+='<span class=\"ndt-size\">'+fmtBytes(r.responseSize||0)+'</span>';\n if(seekable){out+='<span class=\"ndt-time ndt-offset\">'+off.toFixed(1)+'s</span>';}\n else{out+='<span class=\"ndt-time\">'+fmtDur(r.responseTimeMs)+'</span>';}\n out+='</div>';\n if(isOpen){out+='<div style=\"position:absolute;top:'+(rowTop+NDT_ROW_H)+'px;left:0;right:0;z-index:1\" class=\"ndt-detail-wrap\">'+renderDetailWithTab(r,ri,openTab)+'</div>';}\n }\n spacer.innerHTML=out;\n /* Measure actual detail height and adjust layout without full repaint */\n if(openVi>=0){\n var detailWrap=spacer.querySelector('.ndt-detail-wrap');\n if(detailWrap){\n var measuredH=detailWrap.offsetHeight;\n if(measuredH>0&&Math.abs(measuredH-lastDetailH)>2){\n lastDetailH=measuredH;\n /* Update spacer height and row positions without re-rendering */\n var newTotalH=vis.length*NDT_ROW_H+lastDetailH;\n spacer.style.height=newTotalH+'px';\n var rows=spacer.querySelectorAll('.ndt-row');\n for(var rr=0;rr<rows.length;rr++){\n var rrIdx=parseInt(rows[rr].getAttribute('data-ndt-idx'),10);\n /* Find visual index for this row */\n for(var rv=startRow;rv<endRow;rv++){if(vis[rv]===rrIdx){\n if(rv>openVi){rows[rr].style.top=(rv*NDT_ROW_H+lastDetailH)+'px';}\n break;\n }}\n }\n detailWrap.style.top=((openVi*NDT_ROW_H)+NDT_ROW_H)+'px';\n }\n }\n }\n var countEl=el.querySelector('.ndt-count');\n if(countEl)countEl.textContent=vis.length+(vis.length!==allReqs.length?' / '+allReqs.length:'')+' requests';\n }\n\n var rafPending=false;\n /* Infinite scroll: pagination state */\n var _pgTestId=pageMeta&&pageMeta.testId?pageMeta.testId:null;\n var _pgPage=pageMeta&&pageMeta.page?pageMeta.page:1;\n var _pgTotal=pageMeta&&pageMeta.total?pageMeta.total:allReqs.length;\n var _pgPageSize=pageMeta&&pageMeta.pageSize?pageMeta.pageSize:500;\n var _pgLoading=false;\n\n function updateFilterBtns(){\n var filterDiv=el.querySelector('.ndt-filter');if(!filterDiv)return;\n var activeFilter='all';\n var activeBtn=filterDiv.querySelector('.ndt-filter-btn.active');\n if(activeBtn)activeFilter=activeBtn.getAttribute('data-ndt-filter')||'all';\n var btnH='<button class=\"ndt-filter-btn'+(activeFilter==='all'?' 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<allReqs.length;ci++){if(allReqs[ci].resourceType===types[ti])cnt++;}\n if(cnt>0)btnH+='<button class=\"ndt-filter-btn'+(activeFilter===types[ti]?' active':'')+'\" data-ndt-filter=\"'+types[ti]+'\">'+typeLabels[types[ti]]+' <span style=\"opacity:.6\">'+cnt+'</span></button>';\n }\n filterDiv.innerHTML=btnH;\n }\n\n function loadNextPage(){\n if(_pgLoading||!_pgTestId)return;\n if(allReqs.length>=_pgTotal)return;\n _pgLoading=true;\n var fetchData=window.__trFetchTestData;\n if(!fetchData){_pgLoading=false;return;}\n fetchData(_pgTestId,'network',_pgPage+1,_pgPageSize,function(err,result){\n _pgLoading=false;\n if(err||!result||!result.items)return;\n _pgPage++;\n for(var i=0;i<result.items.length;i++){\n allReqs.push(result.items[i]);\n if(hasVideo&&baseTime){ndtVideoOffsets.push(ndtVideoOffset(result.items[i],baseTime));}\n }\n _pgTotal=result.total;\n /* Re-apply filter to include new items */\n if(filteredIdxs!==null){\n var filterDiv=el.querySelector('.ndt-filter');\n var activeBtn=filterDiv?filterDiv.querySelector('.ndt-filter-btn.active'):null;\n var ft=activeBtn?activeBtn.getAttribute('data-ndt-filter'):'all';\n var searchEl=el.querySelector('.ndt-search');\n var q=searchEl?searchEl.value.toLowerCase():'';\n if(ft&&ft!=='all'||q){\n filteredIdxs=[];\n for(var fi=0;fi<allReqs.length;fi++){\n var r=allReqs[fi];\n if(ft&&ft!=='all'&&r.resourceType!==ft)continue;\n if(q&&r.url.toLowerCase().indexOf(q)<0)continue;\n filteredIdxs.push(fi);\n }\n }else{filteredIdxs=null;}\n }\n updateFilterBtns();\n paint();\n });\n }\n\n list.addEventListener('scroll',function(){\n if(!rafPending){rafPending=true;requestAnimationFrame(function(){rafPending=false;paint();});}\n /* Infinite scroll: load more when near bottom */\n if(list.scrollTop+list.clientHeight>=list.scrollHeight-200){loadNextPage();}\n },{passive:true});\n\n /* Click handler for rows */\n list.addEventListener('click',function(e){\n var tab=e.target.closest('.ndt-detail-tab');\n if(tab){\n /* Handle tab switch within detail panel — DOM-only, no repaint */\n e.stopPropagation();\n var tgt=tab.getAttribute('data-ndt-target');\n var pane=tab.getAttribute('data-ndt-tab');\n openTab=pane;\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 tab.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 /* Re-measure after tab switch since pane height may differ */\n requestAnimationFrame(function(){\n var detailWrap=spacer.querySelector('.ndt-detail-wrap');\n if(detailWrap){\n var mh=detailWrap.offsetHeight;\n if(mh>0&&Math.abs(mh-lastDetailH)>2){\n lastDetailH=mh;\n /* Adjust layout without full repaint to preserve tab state */\n var vis=getVisible();\n var ovi=-1;\n for(var oi=0;oi<vis.length;oi++){if(vis[oi]===openIdx){ovi=oi;break;}}\n spacer.style.height=(vis.length*NDT_ROW_H+lastDetailH)+'px';\n var rows=spacer.querySelectorAll('.ndt-row');\n for(var rr=0;rr<rows.length;rr++){\n var rrIdx=parseInt(rows[rr].getAttribute('data-ndt-idx'),10);\n for(var rv=0;rv<vis.length;rv++){if(vis[rv]===rrIdx){\n if(rv>ovi){rows[rr].style.top=(rv*NDT_ROW_H+lastDetailH)+'px';}\n break;\n }}\n }\n }\n }\n });\n }\n return;\n }\n var row=e.target.closest('.ndt-row');if(!row)return;\n e.stopPropagation();\n var idx=parseInt(row.getAttribute('data-ndt-idx'),10);\n openIdx=openIdx===idx?-1:idx;\n openTab='general';\n lastDetailH=NDT_DETAIL_H;\n paint();\n });\n\n /* Store filter/search handler on element */\n el.__ndtFilter=function(filterType,searchVal){\n var q=(searchVal||'').toLowerCase();\n if((!filterType||filterType==='all')&&!q){filteredIdxs=null;}\n else{\n filteredIdxs=[];\n for(var i=0;i<allReqs.length;i++){\n var r=allReqs[i];\n if(filterType&&filterType!=='all'&&r.resourceType!==filterType)continue;\n if(q&&r.url.toLowerCase().indexOf(q)<0)continue;\n filteredIdxs.push(i);\n }\n }\n openIdx=-1;\n list.scrollTop=0;\n paint();\n };\n\n /* Video sync: scroll to matching request by time */\n if(ndtVideoOffsets.length){\n el.__videoSync=function(seconds){\n var vis=getVisible();\n var matchVi=-1;\n for(var i=vis.length-1;i>=0;i--){\n if(ndtVideoOffsets[vis[i]]>=0&&ndtVideoOffsets[vis[i]]<=seconds){matchVi=i;break;}\n }\n if(matchVi<0){highlightIdx=-1;paint();return;}\n var newHL=vis[matchVi];\n if(newHL===highlightIdx)return;\n highlightIdx=newHL;\n var syncOpenVi=-1;\n if(openIdx>=0){var svis=getVisible();for(var si=0;si<svis.length;si++){if(svis[si]===openIdx){syncOpenVi=si;break;}}}\n var rowTop=matchVi*NDT_ROW_H+(syncOpenVi>=0&&matchVi>syncOpenVi?lastDetailH:0);\n var vh=list.clientHeight;\n if(rowTop<list.scrollTop||rowTop>list.scrollTop+vh-NDT_ROW_H){\n list.scrollTop=rowTop-vh/2;\n }\n paint();\n };\n }\n\n paint();\n },0);\n return h;\n }\n\n function renderTimelineColumn(steps,networkRequests,consoleLogs,testType,hasVideo,baseTime,pageMeta){\n if(!steps||steps.length===0)return '<div class=\"artifact-empty\">No navigation data</div>';\n var hasReqs=networkRequests&&networkRequests.length>0;\n var hasCLogs=consoleLogs&&consoleLogs.length>0;\n var totalNet=pageMeta&&pageMeta.total?pageMeta.total:(hasReqs?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 if(hasCLogs)h+='<button class=\"seg-btn\" data-seg=\"console\"><svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"4 17 10 11 4 5\"/><line x1=\"12\" y1=\"19\" x2=\"20\" y2=\"19\"/></svg>Console ('+consoleLogs.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'+(totalNet?' ('+totalNet+')':'')+'</button>';\n h+='</div>';\n h+='<div class=\"seg-pane active\" data-seg-pane=\"timeline\">'+renderNavTimeline(steps)+'</div>';\n if(hasCLogs)h+='<div class=\"seg-pane\" data-seg-pane=\"console\">'+renderConsoleColumn(consoleLogs,testType,hasVideo,baseTime)+'</div>';\n h+='<div class=\"seg-pane\" data-seg-pane=\"network\">'+renderNetworkDevTools(networkRequests,hasVideo,baseTime,pageMeta)+'</div>';\n return h;\n }\n\n function renderFailure(failure,filePath,testName,tags){\n if(!failure)return '';\n var sid='s-'+Math.random().toString(36).substr(2,8);\n var cpid='cp-'+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<button class=\"copy-prompt-btn\" id=\"'+cpid+'\"><svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\"/><path d=\"M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1\"/></svg>Copy Prompt</button></div>';\n /* Split the call log off the message. It is the longest part by far and\n almost never what you want first — leaving it expanded is what pushed\n the visual comparison two screens down. */\n var fullMsg=stripAnsi(failure.message||'');\n var logAt=fullMsg.indexOf('Call log:');\n var headMsg=logAt>=0?fullMsg.slice(0,logAt).trim():fullMsg;\n var callLog=logAt>=0?fullMsg.slice(logAt).trim():'';\n h+='<div class=\"failure-message\">'+esc(headMsg)+'</div>';\n if(callLog){\n var lid='cl-'+Math.random().toString(36).substr(2,8);\n var lines=callLog.split(String.fromCharCode(10)).length-1;\n h+='<button class=\"stack-toggle-btn\" data-stack=\"'+lid+'\">Show call log ('+lines+' steps)</button>';\n h+='<div class=\"stack-trace\" id=\"'+lid+'\">'+esc(callLog)+'</div>';\n }\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>';\n /* Wire up Copy Prompt button after render */\n setTimeout(function(){\n var btn=document.getElementById(cpid);\n if(!btn)return;\n btn.addEventListener('click',function(e){\n e.stopPropagation();\n var nl=String.fromCharCode(10);\n var ticks=String.fromCharCode(96,96,96);\n var msg=failure.message?stripAnsi(failure.message):'Unknown error';\n var tname=testName||'Unknown test';\n var tline=(failure.line!==null&&failure.line!==undefined)?String(failure.line):'unknown';\n var ttags=(tags&&tags.length>0)?tags.join(', '):'none';\n var fp=filePath||'unknown';\n /* Escape regex special chars in test name for --grep */\n function escGrep(s){return s.replace(/[.*+?^]/g,'\\\\\\\\$&').replace(/[()]/g,'\\\\\\\\$&');}\n var tnameEsc=escGrep(tname);\n var p='You are a Playwright test repair agent. A test in this codebase has failed. Your job is to fix the code and verify the fix passes.'+nl+nl;\n p+='## Failed Test'+nl+nl;\n p+='- **File:** '+fp+nl;\n p+='- **Test Name:** '+tname+nl;\n p+='- **Line:** '+tline+nl;\n p+='- **Tags:** '+ttags+nl+nl;\n p+='## Error'+nl+nl;\n p+=msg+nl+nl;\n if(failure.code){\n p+='## Failing Code'+nl+nl;\n p+=ticks+'typescript'+nl+stripAnsi(failure.code)+nl+ticks+nl+nl;\n }\n if(failure.stack){\n p+='## Stack Trace'+nl+nl;\n p+=ticks+nl+stripAnsi(failure.stack)+nl+ticks+nl+nl;\n }\n p+='## Your Task'+nl+nl;\n p+='1. Analyze the error and identify the root cause'+nl;\n p+='2. Edit '+ticks+fp+ticks+' to fix the failing assertion or logic '+String.fromCharCode(8212)+' do not delete the test'+nl;\n p+='3. Run the specific test to verify the fix: '+ticks+'npx playwright test '+fp+' --grep \"'+tnameEsc+'\" --reporter=line'+ticks+nl;\n p+='4. If the test passes, run the full file to confirm no regressions: '+ticks+'npx playwright test '+fp+' --reporter=line'+ticks+nl;\n p+='5. Stop only when the test exits with status '+ticks+'passed'+ticks+nl+nl;\n p+='## Constraints'+nl+nl;\n p+='- Only modify '+ticks+fp+ticks+nl;\n p+='- Do not alter shared fixtures, helpers, or '+ticks+'playwright.config.ts'+ticks+nl;\n p+='- Do not skip or remove the test'+nl;\n navigator.clipboard.writeText(p).then(function(){\n btn.innerHTML='<svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M20 6L9 17l-5-5\"/></svg>Copied!';\n btn.classList.add('copied');\n setTimeout(function(){btn.innerHTML='<svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\"/><path d=\"M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1\"/></svg>Copy Prompt';btn.classList.remove('copied');},2000);\n });\n });\n },0);\n 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 /* Response size, derived. ApiCallRecord has no size field — the body is\n what we have, so measure it and say so rather than render an empty cell. */\n function apiBodyBytes(ac){\n var b=ac.responseBody;\n if(b===null||b===undefined)return null;\n var s=(typeof b==='string')?b:JSON.stringify(b);\n if(!s)return 0;\n /* base64 bodies decode to 3 bytes per 4 characters. */\n if(ac.isBinary)return Math.floor(s.length*3/4);\n return s.length;\n }\n\n function renderCookieTable(cookies,emptyLabel){\n if(!cookies||cookies.length===0)return '<div class=\"ndt-body-empty\">'+esc(emptyLabel)+'</div>';\n var h='<div class=\"ck-tbl\">';\n for(var i=0;i<cookies.length;i++){\n var c=cookies[i];\n h+='<span class=\"ck-k\">'+esc(c.name)+'</span>';\n h+='<span class=\"ck-v\">'+esc(c.value);\n if(c.attributes)h+=' <span class=\"ck-attr\">'+esc(c.attributes)+'</span>';\n h+='</span>';\n }\n return h+'</div>';\n }\n\n /* The assertions made against one call. */\n function assertionsForCall(assertions,callId){\n var out=[];\n if(!assertions||!callId)return out;\n for(var i=0;i<assertions.length;i++){\n if(assertions[i].callId===callId)out.push(assertions[i]);\n }\n return out;\n }\n\n function renderApiCallDetail(ac,idx,callAsserts){\n var did='ndt-d-api-'+idx;\n callAsserts=callAsserts||[];\n var reqHeaders=ac.requestHeaders||null;\n var reqBody=ac.requestBody||null;\n var resHeaders=ac.responseHeaders||null;\n var resBody=ac.responseBody||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 var hasCookies=(ac.requestCookies&&ac.requestCookies.length)||(ac.responseCookies&&ac.responseCookies.length);\n if(hasCookies)h+='<button class=\"ndt-detail-tab\" data-ndt-tab=\"cookies\" data-ndt-target=\"'+did+'\">Cookies</button>';\n if(callAsserts.length>0)h+='<button class=\"ndt-detail-tab\" data-ndt-tab=\"asserts\" data-ndt-target=\"'+did+'\">Assertions <span style=\"opacity:.6\">'+callAsserts.length+'</span></button>';\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.responseStatusCode!=null?ac.responseStatusCode:0;\n var st=ac.responseStatusText||'';\n if(ac.error){\n /* The call never got a response. Rendering \"0\" here read as a status. */\n h+='<span class=\"ndt-general-k\">Result</span><span class=\"ndt-general-v ndt-err-note\">Request failed &mdash; '+esc(String(ac.error))+'</span>';\n }else{\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 }\n h+='<span class=\"ndt-general-k\">Response Time</span><span class=\"ndt-general-v\">'+(ac.responseTimeMs!=null?fmtDur(ac.responseTimeMs):'N/A')+'</span>';\n var _bz=apiBodyBytes(ac);\n if(_bz!==null)h+='<span class=\"ndt-general-k\">Response Size</span><span class=\"ndt-general-v\">'+fmtBytes(_bz)+(ac.isBinary?' (binary)':'')+'</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','Request headers not available &mdash; only explicitly passed headers are captured for API tests')+'</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,ac.isBinary,ct,ac.error?'No response \\u2014 the request failed':'No response body captured')+'</div>';\n if(hasCookies){\n h+='<div class=\"ndt-detail-pane\" data-ndt-pane=\"cookies\">';\n h+='<div class=\"ndt-headers-label\">Request</div>'+renderCookieTable(ac.requestCookies,'No cookies sent');\n h+='<div class=\"ndt-headers-label\">Response</div>'+renderCookieTable(ac.responseCookies,'No cookies set');\n h+='</div>';\n }\n if(callAsserts.length>0){\n h+='<div class=\"ndt-detail-pane\" data-ndt-pane=\"asserts\">'+renderCallAssertions(callAsserts)+'</div>';\n }\n h+='</div>';\n return h;\n }\n\n /* The assertions on one call, inside that call's detail pane. The run-level\n section above lists them all; this answers \"what did this call fail?\". */\n function renderCallAssertions(list){\n var h='<div class=\"asec\">';\n for(var i=0;i<list.length;i++){\n var a=list[i],failed=a.status==='failed';\n h+='<div class=\"arow-item'+(failed?' is-failed':'')+'\">';\n h+=failed\n ? '<svg style=\"flex-shrink:0\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#ef4444\" stroke-width=\"2.5\"><path d=\"M18 6L6 18M6 6l12 12\"/></svg>'\n : '<svg style=\"flex-shrink:0\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#22c55e\" stroke-width=\"2.5\"><path d=\"M20 6L9 17l-5-5\"/></svg>';\n h+='<span class=\"atype\">'+esc(String(a.type||'custom'))+'</span>';\n h+='<span class=\"aexpr\">'+esc(typeof assertionExpression==='function'?assertionExpression(a):(a.expression||''))+'</span>';\n h+='</div>';\n if(failed&&typeof fmtAssertValue==='function'){\n h+='<div class=\"adiff\">';\n h+='<span class=\"adiff-i\"><span class=\"adiff-k\">expected</span><span class=\"adiff-v adiff-exp\">'+esc(fmtAssertValue(a.expected))+'</span></span>';\n h+='<span class=\"adiff-i\"><span class=\"adiff-k\">received</span><span class=\"adiff-v adiff-act\">'+esc(fmtAssertValue(a.actual))+'</span></span>';\n h+='</div>';\n }\n }\n return h+'</div>';\n }\n\n function renderApiCallsColumn(apiCalls,assertions){\n if(!apiCalls||apiCalls.length===0)return '<div class=\"artifact-empty\">No API calls captured</div>';\n assertions=assertions||[];\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.responseStatusCode!=null?ac.responseStatusCode:0;\n var isFail=sc>=400||sc===0||!!ac.error;\n var callAsserts=assertionsForCall(assertions,ac.id||ac.callId);\n var aFail=0;\n for(var aj=0;aj<callAsserts.length;aj++)if(callAsserts[aj].status==='failed')aFail++;\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)+'\" title=\"'+(ac.error?esc(String(ac.error)):'')+'\">'+(ac.error?'ERR':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 /* Was: two permanently empty cells. Now the two things a reader of an\n API row actually wants — did its assertions hold, and how big was it. */\n if(callAsserts.length===0){\n h+='<span class=\"ndt-type\"><span class=\"ndt-assert ndt-assert-none\">&mdash;</span></span>';\n }else if(aFail>0){\n h+='<span class=\"ndt-type\"><span class=\"ndt-assert ndt-assert-bad\">'+aFail+' \\u2717</span></span>';\n }else{\n h+='<span class=\"ndt-type\"><span class=\"ndt-assert ndt-assert-ok\">'+callAsserts.length+' \\u2713</span></span>';\n }\n var bz=apiBodyBytes(ac);\n h+='<span class=\"ndt-size\">'+(bz===null?'':fmtBytes(bz))+'</span>';\n h+='<span class=\"ndt-time\">'+(ac.responseTimeMs!=null?fmtDur(ac.responseTimeMs):'')+'</span>';\n h+='</div>';\n h+=renderApiCallDetail(ac,i,callAsserts);\n }\n h+='</div></div>';\n return h;\n }\n\n function ndtApplyFilters(ndt,filterType,searchVal){\n /* Use virtual filter handler if available */\n if(ndt.__ndtFilter){\n var searchInput=ndt.querySelector('.ndt-search');\n var q=(searchVal!==null&&searchVal!==undefined)?searchVal:(searchInput?searchInput.value:'');\n ndt.__ndtFilter(filterType,q);\n return;\n }\n /* Fallback for non-virtual lists (API calls panel) */\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 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:{},visual:{},evidence:{}};\n\n/* Evidence filters: one predicate per chip, so API and mobile runs share the\n mechanism and a chip only appears when something in the run matches it. */\nvar EVIDENCE_FILTERS=[\n {v:'api-error',label:'4xx/5xx seen',test:function(t){\n var m=t.apiStatusMix;\n if(!m)return false;\n return (m['4xx']||0)+(m['5xx']||0)+(m['error']||0)>0;\n }},\n {v:'assert-failed',label:'Failed assertion',test:function(t){return (t.assertionsFailed||0)>0;}},\n {v:'crash',label:'Crash logged',test:function(t){return !!t.hasCrashLog;}},\n {v:'device-error',label:'Device log errors',test:function(t){return (t.deviceLogErrors||0)>0;}},\n {v:'video',label:'Has recording',test:function(t){return !!t.hasVideo||!!(t.artifacts&&t.artifacts.video);}},\n {v:'ai-defect',label:'AI defects',test:function(t){return (t.aiDefectCount||0)>0;}}\n];\n\nfunction _evidenceCounts(){\n var counts={};\n for(var f=0;f<EVIDENCE_FILTERS.length;f++)counts[EVIDENCE_FILTERS[f].v]=0;\n for(var i=0;i<tests.length;i++){\n for(var j=0;j<EVIDENCE_FILTERS.length;j++){\n if(EVIDENCE_FILTERS[j].test(tests[i]))counts[EVIDENCE_FILTERS[j].v]++;\n }\n }\n return counts;\n}\n\n/* Visual counts reach the grid differently in the two report modes: the\n embedded report inlines the comparisons on each test, while the streaming\n index carries only counts so the grid never has to open a detail file.\n Both are read here so the filter works the same either way. */\nfunction _visCount(t){return t.visual?t.visual.length:(t.visualCount||0);}\nfunction _visFailed(t){\n if(t.visual){var n=0;for(var i=0;i<t.visual.length;i++)if(t.visual[i].status==='failed')n++;return n;}\n return t.visualFailedCount||0;\n}\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 var visualCounts={has:0,failed:0};\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 if(_visCount(t)>0){visualCounts.has++;if(_visFailed(t)>0)visualCounts.failed++;}\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 hasVisual=Object.keys(_filterState.visual).length>0;\n var hasEvidenceSel=Object.keys(_filterState.evidence).length>0;\n var hasAny=hasStatus||hasType||hasSpec||hasVisual||hasEvidenceSel||!!searchQuery;\n var hasAdvanced=hasType||hasSpec||hasVisual||hasEvidenceSel;\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 /* Visual section — only when the run actually produced comparisons */\n if(visualCounts.has>0){\n d+='<div class=\"filter-drawer-section\"><span class=\"filter-drawer-section-label\">Visual</span>';\n d+='<div class=\"filter-chips\">';\n var visOpts=[{v:'has',label:'Has visual',cnt:visualCounts.has},{v:'failed',label:'Visual failed',cnt:visualCounts.failed}];\n for(var vi=0;vi<visOpts.length;vi++){\n var vo=visOpts[vi];\n var vcls='filter-chip'+(!!_filterState.visual[vo.v]?' active':'')+(vo.cnt===0?' filter-chip--dimmed':'');\n d+='<button class=\"'+vcls+'\" data-dimension=\"visual\" data-value=\"'+vo.v+'\">'+vo.label+' <span class=\"chip-count\">'+vo.cnt+'</span></button>';\n }\n d+='</div></div>';\n }\n\n /* Evidence section — a chip per predicate that anything in the run meets */\n var evCounts=_evidenceCounts();\n var evAny=false;\n for(var ek in evCounts){if(evCounts.hasOwnProperty(ek)&&evCounts[ek]>0)evAny=true;}\n if(evAny){\n d+='<div class=\"filter-drawer-section\"><span class=\"filter-drawer-section-label\">Evidence</span>';\n d+='<div class=\"filter-chips\">';\n for(var ei=0;ei<EVIDENCE_FILTERS.length;ei++){\n var ef=EVIDENCE_FILTERS[ei],ecnt=evCounts[ef.v];\n if(ecnt===0)continue;\n var ecls='filter-chip'+(!!_filterState.evidence[ef.v]?' active':'');\n d+='<button class=\"'+ecls+'\" data-dimension=\"evidence\" data-value=\"'+ef.v+'\">'+ef.label+' <span class=\"chip-count\">'+ecnt+'</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:{},visual:{},evidence:{}};\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 hasVisual=Object.keys(_filterState.visual).length>0;\n var hasEvidence=Object.keys(_filterState.evidence).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(hasVisual){\n /* OR within the dimension, matching every other filter here. */\n var visOk=(_filterState.visual.has&&_visCount(t)>0)||(_filterState.visual.failed&&_visFailed(t)>0);\n if(!visOk)continue;\n }\n if(hasEvidence){\n var evOk=false;\n for(var ef2=0;ef2<EVIDENCE_FILTERS.length;ef2++){\n var cand=EVIDENCE_FILTERS[ef2];\n if(_filterState.evidence[cand.v]&&cand.test(t)){evOk=true;break;}\n }\n if(!evOk)continue;\n }\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 for(var dim in _filterState){if(_filterState.hasOwnProperty(dim))_filterState[dim]={};}\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){openTest(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 /* Virtual lists handle their own click events */\n var vlist=ndtRow.closest('.ndt-vlist');\n if(vlist)return;\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 /* Console log level filter */\n var consoleFilterBtn=target.closest('.console-filter-btn');\n if(consoleFilterBtn){\n var toolbar=consoleFilterBtn.closest('.console-toolbar');\n if(toolbar){\n var btns=toolbar.querySelectorAll('.console-filter-btn');\n for(var bi=0;bi<btns.length;bi++)btns[bi].classList.remove('active');\n consoleFilterBtn.classList.add('active');\n var filterLevel=consoleFilterBtn.getAttribute('data-console-filter');\n var panel=consoleFilterBtn.closest('.console-panel');\n /* Use virtual filter if available */\n if(panel&&panel.__conFilter){panel.__conFilter(filterLevel);return;}\n var list=panel?panel.querySelector('.console-list'):null;\n if(list){\n var entries=list.querySelectorAll('.console-entry');\n for(var ei=0;ei<entries.length;ei++){\n if(filterLevel==='all'){entries[ei].style.display='';}\n else{entries[ei].style.display=entries[ei].getAttribute('data-console-level')===filterLevel?'':'none';}\n }\n }\n }\n return;\n }\n\n /* Action step click → seek video + highlight */\n var actionStep=target.closest('.action-step.seekable');\n if(actionStep){\n var offset=parseFloat(actionStep.getAttribute('data-offset'));\n if(!isNaN(offset)){\n var video=document.querySelector('.drawer-body video');\n if(video){video.currentTime=offset;video.play();}\n /* Clear previous active and highlight clicked */\n var prev=document.querySelectorAll('.action-active');\n for(var ai=0;ai<prev.length;ai++)prev[ai].classList.remove('action-active');\n actionStep.classList.add('action-active');\n actionStep.classList.add('seeking');\n setTimeout(function(){actionStep.classList.remove('seeking');},400);\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('#detail-back')){closeTest();return;}\n if(target.closest('#detail-prev')){stepTest(-1);return;}\n if(target.closest('#detail-next')){stepTest(1);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();closeTest();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,'&quot;')+'\" alt=\"Screenshot\">';\n var b=document.createElement('button');b.className='lightbox-close';b.innerHTML='&times;';\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 /* ── Video ↔ All Panels Sync ── */\n var _videoSyncCleanup=null;\n function syncActionsWithVideo(){\n /* Clean up previous listener */\n if(_videoSyncCleanup){_videoSyncCleanup();_videoSyncCleanup=null;}\n var video=document.querySelector('.drawer-body video');\n if(!video)return;\n\n /* Action steps sync */\n var steps=document.querySelectorAll('.action-step.seekable');\n var offsets=[];\n for(var i=0;i<steps.length;i++){\n var o=parseFloat(steps[i].getAttribute('data-offset'));\n if(!isNaN(o))offsets.push({el:steps[i],offset:o});\n }\n offsets.sort(function(a,b){return a.offset-b.offset;});\n\n /* Collect console and network panels with video sync */\n var consolePanels=document.querySelectorAll('.console-panel');\n var ndtPanels=document.querySelectorAll('.ndt');\n\n var lastActive=null;\n function onTimeUpdate(){\n var ct=video.currentTime;\n\n /* Sync action steps */\n if(offsets.length){\n var match=null;\n for(var i=offsets.length-1;i>=0;i--){\n if(offsets[i].offset<=ct){match=offsets[i].el;break;}\n }\n if(match!==lastActive){\n if(lastActive)lastActive.classList.remove('action-active');\n if(match){\n match.classList.add('action-active');\n match.scrollIntoView({block:'nearest',behavior:'smooth'});\n }\n lastActive=match;\n }\n }\n\n /* Sync console panels */\n for(var ci=0;ci<consolePanels.length;ci++){\n if(consolePanels[ci].__videoSync)consolePanels[ci].__videoSync(ct);\n }\n\n /* Sync network panels */\n for(var ni=0;ni<ndtPanels.length;ni++){\n if(ndtPanels[ni].__videoSync)ndtPanels[ni].__videoSync(ct);\n }\n }\n video.addEventListener('timeupdate',onTimeUpdate);\n video.addEventListener('seeked',onTimeUpdate);\n _videoSyncCleanup=function(){\n video.removeEventListener('timeupdate',onTimeUpdate);\n video.removeEventListener('seeked',onTimeUpdate);\n if(lastActive)lastActive.classList.remove('action-active');\n };\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 * Client-side JavaScript: Action Steps Rendering.\n * Renders the chronological action steps timeline in the drawer,\n * with category badges, duration, video seek, and nested step support.\n */\n\nexport const JS_ACTIONS = `\n /* ── Category badge helpers ── */\n function actionBadgeClass(cat){\n if(cat==='assertion')return 'action-badge-assertion';\n if(cat==='custom_step')return 'action-badge-custom';\n return 'action-badge-ui';\n }\n function actionBadgeLabel(cat){\n if(cat==='assertion')return 'assert';\n if(cat==='custom_step')return 'step';\n return 'action';\n }\n\n /* ── Render a single action step ── */\n function renderActionItem(a,hasVideo,depth){\n var h='<div class=\"action-step'+(a.status==='failed'?' action-failed':'')+(hasVideo&&a.videoOffset!==null?' seekable':'')+'\" data-offset=\"'+(a.videoOffset!==null?a.videoOffset:'')+'\" style=\"padding-left:'+(12+depth*16)+'px\">';\n h+='<span class=\"action-badge '+actionBadgeClass(a.category)+(a.status==='failed'?' failed':'')+'\">'+actionBadgeLabel(a.category)+'</span>';\n h+='<span class=\"action-title\" title=\"'+esc(a.title)+'\">'+esc(a.title)+'</span>';\n h+='<span class=\"action-duration\">'+fmtDur(a.duration)+'</span>';\n if(hasVideo&&a.videoOffset!==null){\n h+='<span class=\"action-offset\">@ '+a.videoOffset.toFixed(1)+'s</span>';\n }\n h+='</div>';\n if(a.status==='failed'&&a.error){\n h+='<div class=\"action-error\" style=\"padding-left:'+(28+depth*16)+'px\">'+esc(a.error)+'</div>';\n }\n if(a.children&&a.children.length>0){\n for(var c=0;c<a.children.length;c++){\n h+=renderActionItem(a.children[c],hasVideo,depth+1);\n }\n }\n return h;\n }\n\n /* ── Render the full actions column ── */\n function renderActionsColumn(actions,hasVideo){\n if(!actions||actions.length===0)return '';\n var h='<div class=\"actions-header\"><svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M12 20V10M18 20V4M6 20v-4\"/></svg>Actions <span class=\"actions-count\">'+actions.length+'</span></div>';\n h+='<div class=\"actions-timeline\">';\n for(var i=0;i<actions.length;i++){\n if(i>0)h+='<div class=\"action-connector\"></div>';\n h+=renderActionItem(actions[i],hasVideo,0);\n }\n h+='</div>';\n return h;\n }\n`;\n","/**\n * Client-side JavaScript: Console/Log Rendering.\n * Renders browser console logs (E2E) and terminal logs (API) in the drawer.\n */\n\nexport const JS_CONSOLE = `\n /* ── Console Log Level Colors & Labels ── */\n var LOG_COLORS={log:'#9ca3af',warn:'#fbbf24',error:'#f87171',info:'#60a5fa',debug:'#a78bfa',stdout:'#9ca3af',stderr:'#f87171'};\n var LOG_LABELS={log:'LOG',warn:'WARN',error:'ERR',info:'INFO',debug:'DBG',stdout:'OUT',stderr:'ERR'};\n\n function consoleVideoOffset(entry,baseTime){\n if(!baseTime||!entry.timestamp)return -1;\n var t=Date.parse(entry.timestamp);\n if(isNaN(t))return -1;\n var off=(t-baseTime)/1000;\n return off>=0?off:-1;\n }\n\n function renderConsoleLogEntry(entry,hasVideo,baseTime){\n var off=hasVideo?consoleVideoOffset(entry,baseTime):-1;\n var seekable=off>=0;\n var h='<div class=\"console-entry console-level-'+esc(entry.level)+(seekable?' seekable':'')+'\" data-console-level=\"'+esc(entry.level)+'\"'+(seekable?' data-offset=\"'+off.toFixed(2)+'\"':'')+'>';\n h+='<span class=\"console-level-badge\" style=\"color:'+LOG_COLORS[entry.level]+'\">'+LOG_LABELS[entry.level]+'</span>';\n if(seekable){h+='<span class=\"console-offset\">'+off.toFixed(1)+'s</span>';}\n else{h+='<span class=\"console-time\">'+fmtDate(entry.timestamp).split(', ').pop()+'</span>';}\n h+='<span class=\"console-text\">'+esc(stripAnsi(entry.text))+'</span>';\n if(entry.location){var short=entry.location.split('/').pop()||entry.location;h+='<span class=\"console-loc\" title=\"'+esc(entry.location)+'\">'+esc(short)+'</span>';}\n h+='</div>';\n return h;\n }\n\n var CON_ROW_H=24;\n var CON_VBUF=40;\n\n function renderConsoleColumn(logs,testType,hasVideo,baseTime){\n if(!logs||logs.length===0)return '';\n var isApi=testType==='api';\n var label=isApi?'Terminal Logs':'Console';\n\n var counts={};\n for(var i=0;i<logs.length;i++){var l=logs[i].level;counts[l]=(counts[l]||0)+1;}\n\n var uid='clog-'+Math.random().toString(36).substr(2,6);\n var h='<div class=\"console-panel\" id=\"'+uid+'\">';\n h+='<div class=\"console-header\">';\n h+='<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"4 17 10 11 4 5\"/><line x1=\"12\" y1=\"19\" x2=\"20\" y2=\"19\"/></svg>';\n h+=' '+label+' <span class=\"console-count\">'+logs.length+'</span>';\n if(counts.error||counts.stderr){var ec=(counts.error||0)+(counts.stderr||0);h+=' <span class=\"console-error-count\">'+ec+' error'+(ec>1?'s':'')+'</span>';}\n if(counts.warn)h+=' <span class=\"console-warn-count\">'+counts.warn+' warning'+(counts.warn>1?'s':'')+'</span>';\n h+='</div>';\n\n h+='<div class=\"console-toolbar\">';\n h+='<button class=\"console-filter-btn active\" data-console-filter=\"all\">All</button>';\n var levels=isApi?['stdout','stderr']:['log','info','warn','error','debug'];\n for(var li=0;li<levels.length;li++){\n var lv=levels[li];\n if(counts[lv])h+='<button class=\"console-filter-btn\" data-console-filter=\"'+lv+'\">'+LOG_LABELS[lv]+' <span style=\"opacity:.6\">'+counts[lv]+'</span></button>';\n }\n h+='</div>';\n\n /* Pre-compute video offsets for all entries */\n var videoOffsets=[];\n if(hasVideo&&baseTime){\n for(var oi=0;oi<logs.length;oi++){videoOffsets.push(consoleVideoOffset(logs[oi],baseTime));}\n }\n\n /* Use virtualization for large log lists */\n if(logs.length>200){\n h+='<div class=\"console-list console-vlist\" style=\"overflow-y:auto;max-height:400px;position:relative\">';\n h+='<div class=\"console-vspacer\" style=\"height:'+(logs.length*CON_ROW_H)+'px;position:relative\"></div>';\n h+='</div>';\n h+='</div>';\n\n setTimeout(function(){\n var panel=document.getElementById(uid);if(!panel)return;\n var list=panel.querySelector('.console-vlist');if(!list)return;\n var spacer=list.querySelector('.console-vspacer');\n var allLogs=logs;\n var filteredIdxs=null;\n var highlightIdx=-1;\n\n function getVis(){return filteredIdxs||allLogs.map(function(_,i){return i});}\n\n function paint(){\n var vis=getVis();\n spacer.style.height=(vis.length*CON_ROW_H)+'px';\n var st=list.scrollTop;var vh=list.clientHeight;\n var s0=Math.max(0,Math.floor(st/CON_ROW_H)-CON_VBUF);\n var s1=Math.min(vis.length,Math.ceil((st+vh)/CON_ROW_H)+CON_VBUF);\n var out='';\n for(var vi=s0;vi<s1;vi++){\n var ri=vis[vi];var entry=allLogs[ri];\n var off=videoOffsets[ri];\n var seekable=off>=0;\n var isActive=ri===highlightIdx;\n out+='<div class=\"console-entry console-level-'+esc(entry.level)+(seekable?' seekable':'')+(isActive?' console-active':'')+'\" data-console-level=\"'+esc(entry.level)+'\"'+(seekable?' data-offset=\"'+off.toFixed(2)+'\"':'')+' data-vidx=\"'+ri+'\" style=\"position:absolute;top:'+(vi*CON_ROW_H)+'px;left:0;right:0;height:'+CON_ROW_H+'px\">';\n out+='<span class=\"console-level-badge\" style=\"color:'+LOG_COLORS[entry.level]+'\">'+LOG_LABELS[entry.level]+'</span>';\n if(seekable){out+='<span class=\"console-offset\">'+off.toFixed(1)+'s</span>';}\n else{out+='<span class=\"console-time\">'+fmtDate(entry.timestamp).split(', ').pop()+'</span>';}\n out+='<span class=\"console-text\">'+esc(stripAnsi(entry.text))+'</span>';\n if(entry.location){var short=entry.location.split('/').pop()||entry.location;out+='<span class=\"console-loc\" title=\"'+esc(entry.location)+'\">'+esc(short)+'</span>';}\n out+='</div>';\n }\n spacer.innerHTML=out;\n }\n\n var rafP=false;\n list.addEventListener('scroll',function(){if(!rafP){rafP=true;requestAnimationFrame(function(){rafP=false;paint();})}},{passive:true});\n\n /* Click-to-seek for virtual entries */\n list.addEventListener('click',function(e){\n var entry=e.target.closest('.console-entry.seekable');\n if(!entry)return;\n var off=parseFloat(entry.getAttribute('data-offset'));\n if(isNaN(off))return;\n var video=document.querySelector('.drawer-body video');\n if(video){video.currentTime=off;video.play();}\n });\n\n /* Store filter handler */\n panel.__conFilter=function(level){\n if(!level||level==='all'){filteredIdxs=null;}\n else{filteredIdxs=[];for(var i=0;i<allLogs.length;i++){if(allLogs[i].level===level)filteredIdxs.push(i);}}\n list.scrollTop=0;paint();\n };\n\n /* Video sync: scroll to matching entry by time */\n panel.__videoSync=function(seconds){\n if(!videoOffsets.length)return;\n var vis=getVis();\n var matchVi=-1;\n for(var i=vis.length-1;i>=0;i--){\n if(videoOffsets[vis[i]]>=0&&videoOffsets[vis[i]]<=seconds){matchVi=i;break;}\n }\n if(matchVi<0){highlightIdx=-1;paint();return;}\n var newHL=vis[matchVi];\n if(newHL===highlightIdx)return;\n highlightIdx=newHL;\n /* Scroll to row */\n var rowTop=matchVi*CON_ROW_H;\n var vh=list.clientHeight;\n if(rowTop<list.scrollTop||rowTop>list.scrollTop+vh-CON_ROW_H){\n list.scrollTop=rowTop-vh/2;\n }\n paint();\n };\n\n paint();\n },0);\n }else{\n h+='<div class=\"console-list\">';\n for(var i=0;i<logs.length;i++){h+=renderConsoleLogEntry(logs[i],hasVideo,baseTime);}\n h+='</div>';\n h+='</div>';\n\n /* Wire up click-to-seek for non-virtual entries */\n if(hasVideo&&baseTime){\n setTimeout(function(){\n var panel=document.getElementById(uid);if(!panel)return;\n panel.addEventListener('click',function(e){\n var entry=e.target.closest('.console-entry.seekable');\n if(!entry)return;\n var off=parseFloat(entry.getAttribute('data-offset'));\n if(isNaN(off))return;\n var video=document.querySelector('.drawer-body video');\n if(video){video.currentTime=off;video.play();}\n });\n /* Video sync for non-virtual */\n var entries=panel.querySelectorAll('.console-entry.seekable');\n var offsets=[];\n for(var i=0;i<entries.length;i++){\n var off=parseFloat(entries[i].getAttribute('data-offset'));\n if(!isNaN(off))offsets.push({el:entries[i],offset:off});\n }\n if(offsets.length){\n var lastHL=null;\n panel.__videoSync=function(seconds){\n var match=null;\n for(var i=offsets.length-1;i>=0;i--){\n if(offsets[i].offset<=seconds){match=offsets[i].el;break;}\n }\n if(match===lastHL)return;\n if(lastHL)lastHL.classList.remove('console-active');\n if(match){match.classList.add('console-active');match.scrollIntoView({block:'nearest',behavior:'smooth'});}\n lastHL=match;\n };\n }\n },0);\n }\n }\n return h;\n }\n`;\n","/**\n * Client-side JavaScript: Artifact utilities\n *\n * Provides formatBytes utility used by other report modules.\n */\n\nexport const JS_ARTIFACTS = `\n /* ── Artifact Utilities ── */\n function formatBytes(bytes){\n if(bytes===0)return '0 B';\n var k=1024;var sizes=['B','KB','MB','GB'];\n var i=Math.floor(Math.log(bytes)/Math.log(k));\n if(i>=sizes.length)i=sizes.length-1;\n return parseFloat((bytes/Math.pow(k,i)).toFixed(1))+' '+sizes[i];\n }\n`;\n","/**\n * Client-side JavaScript: visual regression section.\n *\n * Renders one card per snapshot comparison, with up to four ways of looking at\n * the same pair of images: side by side, a wipe slider, an onion-skin blend and\n * Playwright's own diff. Which modes appear depends on which images exist — a\n * passing comparison has only a baseline, and a failing one has all four.\n *\n * The markup reuses the report's existing `.seg-ctrl` / `.seg-btn` / `.seg-pane`\n * convention, so the delegated pane-switch handler in `html-js-interactions`\n * drives the tabs with no changes there. Only the two range inputs need a\n * listener of their own, registered at the bottom of this module.\n *\n * ES5-compatible vanilla JS — it is concatenated into the report's single\n * inline script, under a CSP that allows no external resources.\n */\n\nexport const JS_VISUAL = `\n /* ── Visual Regression ── */\n\n function visStatusLabel(status){\n if(status==='new')return 'new baseline';\n if(status==='updated')return 'baseline updated';\n return status;\n }\n\n function visMetricsText(v){\n var parts=[];\n if(typeof v.diffPixels==='number')parts.push(v.diffPixels.toLocaleString()+' px');\n if(typeof v.diffPixelRatio==='number')parts.push((v.diffPixelRatio*100).toFixed(2)+'%');\n if(v.width&&v.height){\n var dims=v.width+'x'+v.height;\n /* Call out a size change explicitly: a comparison can fail purely\n because the render changed shape, and the pixel count alone reads\n like a content change when that happens. */\n if(v.actualWidth&&v.actualHeight&&(v.actualWidth!==v.width||v.actualHeight!==v.height))\n dims+=' -> '+v.actualWidth+'x'+v.actualHeight;\n parts.push(dims);\n }\n return parts.join(' \\\\u00b7 ');\n }\n\n /* Deliberately no loading=\"lazy\" on any image below. Every pane but the\n first is built inside a display:none container, and Chrome does not\n reliably start a lazy load when such an image is later revealed — the\n diff pane came up blank every time. A test has a handful of comparisons\n at most and they are only built when the drawer opens, so eager loading\n is by far the cheaper mistake. */\n function visImg(src,alt){\n return '<img class=\"artifact-col-img\" src=\"'+esc(src)+'\" alt=\"'+esc(alt)+'\">';\n }\n\n function visSideBySide(v){\n var h='<div class=\"vis-pair\">';\n if(v.baseline)h+='<div><div class=\"vis-pane-label\">Baseline</div>'+visImg(v.baseline,'Baseline')+'</div>';\n if(v.actual)h+='<div><div class=\"vis-pane-label\">Actual</div>'+visImg(v.actual,'Actual')+'</div>';\n if(v.previous)h+='<div><div class=\"vis-pane-label\">Previous attempt</div>'+visImg(v.previous,'Previous attempt')+'</div>';\n return h+'</div>';\n }\n\n function visSlider(v,id){\n var h='<div class=\"vis-stack\" data-vis-stack=\"'+id+'\">';\n h+='<img src=\"'+esc(v.baseline)+'\" alt=\"Baseline\">';\n h+='<img class=\"vis-top\" data-vis-wipe=\"'+id+'\" src=\"'+esc(v.actual)+'\" alt=\"Actual\" style=\"clip-path:inset(0 0 0 50%)\">';\n h+='<div class=\"vis-handle\" data-vis-handle=\"'+id+'\" style=\"left:50%\"></div>';\n h+='</div>';\n h+='<div class=\"vis-range-row\"><span>Baseline</span><input type=\"range\" class=\"vis-range\" min=\"0\" max=\"100\" value=\"50\" data-vis-slider=\"'+id+'\" aria-label=\"Wipe between baseline and actual\"><span>Actual</span></div>';\n return h;\n }\n\n function visOnion(v,id){\n var h='<div class=\"vis-stack\">';\n h+='<img src=\"'+esc(v.baseline)+'\" alt=\"Baseline\">';\n h+='<img class=\"vis-top\" data-vis-fade=\"'+id+'\" src=\"'+esc(v.actual)+'\" alt=\"Actual\" style=\"opacity:0.5\">';\n h+='</div>';\n h+='<div class=\"vis-range-row\"><span>Baseline</span><input type=\"range\" class=\"vis-range\" min=\"0\" max=\"100\" value=\"50\" data-vis-onion=\"'+id+'\" aria-label=\"Blend between baseline and actual\"><span>Actual</span></div>';\n return h;\n }\n\n function visModes(v){\n var modes=[];\n if(v.baseline&&v.actual){\n modes.push({key:'side',label:'Side by side'});\n modes.push({key:'wipe',label:'Slider'});\n modes.push({key:'onion',label:'Onion skin'});\n }else if(v.baseline||v.actual){\n modes.push({key:'side',label:'Image'});\n }\n if(v.diff)modes.push({key:'diff',label:'Diff'});\n /* Only when a DOM baseline existed to compare against — an assertion\n written with Playwright's own expect compares pixels only. */\n if(v.domChangeCount!==undefined)modes.push({key:'dom',label:'Elements'});\n return modes;\n }\n\n function visPaneBody(mode,v,id){\n if(mode==='side')return visSideBySide(v);\n if(mode==='wipe')return visSlider(v,id);\n if(mode==='onion')return visOnion(v,id);\n if(mode==='dom')return renderDomPane(v);\n return '<div class=\"vis-diff-wrap\">'+visImg(v.diff,'Pixel diff')+'</div>';\n }\n\n function renderVisualCard(v,idx){\n var id='v'+idx;\n var h='<div class=\"vis-card\">';\n h+='<div class=\"vis-head\"><span class=\"vis-name\">'+esc(v.name)+'</span>';\n h+='<span class=\"vis-badge vb-'+esc(v.status)+'\">'+esc(visStatusLabel(v.status))+'</span>';\n if(v.domChangeCount)h+='<span class=\"vis-badge vb-dom\">'+v.domChangeCount+' element'+(v.domChangeCount===1?'':'s')+'</span>';\n var metrics=visMetricsText(v);\n if(metrics)h+='<span class=\"vis-metrics\">'+esc(metrics)+'</span>';\n h+='</div>';\n\n var modes=visModes(v);\n if(modes.length===0){\n h+='<div class=\"vis-note\">No images were captured for this comparison.</div></div>';\n return h;\n }\n\n if(v.status==='new')h+='<div class=\"vis-note\">No baseline existed, so this render was written as one. Nothing was compared.</div>';\n else if(v.status==='updated')h+='<div class=\"vis-note\">Run with --update-snapshots: the baseline was rewritten rather than compared.</div>';\n\n if(modes.length>1){\n h+='<div class=\"seg-ctrl\" data-seg-group=\"visual-'+id+'\">';\n for(var m=0;m<modes.length;m++){\n h+='<button class=\"seg-btn'+(m===0?' active':'')+'\" data-seg=\"'+id+'-'+modes[m].key+'\">'+esc(modes[m].label)+'</button>';\n }\n h+='</div>';\n }\n for(var p=0;p<modes.length;p++){\n h+='<div class=\"seg-pane'+(p===0?' active':'')+'\" data-seg-pane=\"'+id+'-'+modes[p].key+'\">'+visPaneBody(modes[p].key,v,id)+'</div>';\n }\n return h+'</div>';\n }\n\n function renderVisualSection(visual){\n if(!visual||visual.length===0)return '';\n var failed=0;\n for(var f=0;f<visual.length;f++)if(visual[f].status==='failed')failed++;\n var h='<div class=\"vis-header\"><svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/><path d=\"M12 3v18\"/></svg>Visual comparisons <span class=\"vis-count\">'+visual.length+'</span>';\n /* A separator, because two bare adjacent numbers read as one. */\n if(failed>0)h+='<span class=\"vis-count vis-count-fail\">· '+failed+' failed</span>';\n h+='</div><div class=\"vis-list\">';\n for(var i=0;i<visual.length;i++)h+=renderVisualCard(visual[i],i);\n return h+'</div>';\n }\n\n /* Slider and onion-skin are driven by range inputs, which fire 'input'\n rather than 'click' — the delegated click handler cannot see them. One\n document-level listener covers every card, including cards rendered into\n the drawer long after this runs. */\n document.addEventListener('input',function(e){\n var el=e.target;\n if(!el||!el.getAttribute)return;\n\n var wipeId=el.getAttribute('data-vis-slider');\n if(wipeId){\n var card=el.closest('.vis-card');\n if(!card)return;\n var top=card.querySelector('[data-vis-wipe=\"'+wipeId+'\"]');\n var handle=card.querySelector('[data-vis-handle=\"'+wipeId+'\"]');\n if(top)top.style.clipPath='inset(0 0 0 '+el.value+'%)';\n if(handle)handle.style.left=el.value+'%';\n return;\n }\n\n var fadeId=el.getAttribute('data-vis-onion');\n if(fadeId){\n var ocard=el.closest('.vis-card');\n if(!ocard)return;\n var otop=ocard.querySelector('[data-vis-fade=\"'+fadeId+'\"]');\n if(otop)otop.style.opacity=String(el.value/100);\n }\n });\n`;\n","/**\n * Client-side JavaScript: the Elements pane.\n *\n * Lists the elements that differ between the two renders, and draws a box over\n * the screenshot for whichever one the reader is looking at. This is what turns\n * \"80,858 pixels changed\" into \"this card gained a 24px margin\".\n *\n * Boxes are positioned as percentages of the comparison's capture box, which is\n * how the platform's existing region overlay does it — no measuring, no resize\n * handling, correct at any device pixel ratio or rendered size.\n *\n * ES5-compatible vanilla JS, concatenated into the report's single inline\n * script under a CSP that permits no external resources.\n */\n\nexport const JS_DOM = `\n /* ── DOM comparison ── */\n\n /* Rank-cycled so a row and its box always share a colour. */\n var DOM_TONES = ['#03b79c','#f59e0b','#60a5fa','#e879f9','#ef4444','#a3e635'];\n function domTone(rank){return DOM_TONES[(rank-1)%DOM_TONES.length];}\n\n function domKindLabel(kind){\n if(kind==='restyled')return 'restyled';\n if(kind==='text')return 'text';\n if(kind==='resized')return 'resized';\n return kind;\n }\n\n /* Percentages of the capture box: the image may be rendered at any size. */\n function domBoxStyle(rect,box){\n if(!rect||!box||!box[2]||!box[3])return null;\n var left=((rect[0]-box[0])/box[2])*100;\n var top=((rect[1]-box[1])/box[3])*100;\n var width=(rect[2]/box[2])*100;\n var height=(rect[3]/box[3])*100;\n /* An element scrolled out of the captured region has nothing to point at. */\n if(left>100||top>100||left+width<0||top+height<0)return null;\n return 'left:'+left.toFixed(3)+'%;top:'+top.toFixed(3)+'%;width:'+width.toFixed(3)+'%;height:'+height.toFixed(3)+'%';\n }\n\n function renderDomOverlay(v){\n var h='<div class=\"dom-ov\">';\n for(var i=0;i<v.domChanges.length;i++){\n var c=v.domChanges[i];\n /* A removed element has no box in this render; show where it used to be. */\n var style=domBoxStyle(c.rect||c.baselineRect,v.domCaptureBox);\n if(!style)continue;\n h+='<div class=\"dom-box\" data-dom-box=\"'+c.rank+'\" style=\"'+style+';border-color:'+domTone(c.rank)+'\"></div>';\n }\n return h+'</div>';\n }\n\n function renderDomRow(c){\n var h='<div class=\"dom-row\" data-dom-row=\"'+c.rank+'\">';\n h+='<span class=\"dom-swatch\" style=\"background:'+domTone(c.rank)+'\"></span>';\n h+='<span class=\"dom-label\">'+esc(c.label)+'</span>';\n for(var k=0;k<c.kinds.length;k++){\n h+='<span class=\"dom-kind dk-'+esc(c.kinds[k])+'\">'+esc(domKindLabel(c.kinds[k]))+'</span>';\n }\n h+='<span class=\"dom-area\">'+(c.areaPct*100).toFixed(1)+'%</span>';\n h+='</div>';\n\n var detail='';\n for(var a=0;a<(c.attrs||[]).length;a++){\n var at=c.attrs[a];\n detail+='<div class=\"dom-prop dom-prop-attr\"><span class=\"dom-prop-name\">'+esc(at.prop)+'</span>'+\n '<span class=\"dom-from\">'+esc(at.from)+'</span>'+\n '<span class=\"dom-arrow\">&rarr;</span>'+\n '<span class=\"dom-to\">'+esc(at.to)+'</span></div>';\n }\n for(var s=0;s<(c.styles||[]).length;s++){\n var st=c.styles[s];\n detail+='<div class=\"dom-prop\"><span class=\"dom-prop-name\">'+esc(st.prop)+'</span>'+\n '<span class=\"dom-from\">'+esc(st.from)+'</span>'+\n '<span class=\"dom-arrow\">&rarr;</span>'+\n '<span class=\"dom-to\">'+esc(st.to)+'</span></div>';\n }\n if(c.text){\n detail+='<div class=\"dom-prop\"><span class=\"dom-prop-name\">text</span>'+\n '<span class=\"dom-from\">'+esc(c.text.from)+'</span>'+\n '<span class=\"dom-arrow\">&rarr;</span>'+\n '<span class=\"dom-to\">'+esc(c.text.to)+'</span></div>';\n }\n if(c.baselineRect&&c.rect){\n var moved=(c.baselineRect[0]!==c.rect[0]||c.baselineRect[1]!==c.rect[1]);\n var sized=(c.baselineRect[2]!==c.rect[2]||c.baselineRect[3]!==c.rect[3]);\n if(moved||sized){\n detail+='<div class=\"dom-prop\"><span class=\"dom-prop-name\">rect</span>'+\n '<span class=\"dom-from\">'+c.baselineRect.join(', ')+'</span>'+\n '<span class=\"dom-arrow\">&rarr;</span>'+\n '<span class=\"dom-to\">'+c.rect.join(', ')+'</span></div>';\n }\n }\n if(detail)h+='<div class=\"dom-detail\">'+detail+'</div>';\n return h;\n }\n\n function renderDomPane(v){\n var changes=v.domChanges||[];\n if(changes.length===0){\n return '<div class=\"vis-note\">The DOM matched its baseline exactly \\\\u2014 every element in the same place, with the same styles and text.</div>';\n }\n\n var roots=[],shifted=[];\n for(var i=0;i<changes.length;i++){\n if(changes[i].consequence)shifted.push(changes[i]);else roots.push(changes[i]);\n }\n\n var h='';\n var image=v.actual||v.baseline;\n if(image){\n h+='<div class=\"dom-figure\">';\n h+='<img class=\"dom-img\" src=\"'+esc(image)+'\" alt=\"Changed elements\">';\n h+=renderDomOverlay(v);\n h+='</div>';\n }\n\n h+='<div class=\"dom-summary\">';\n h+='<strong>'+(v.domChangeCount||changes.length)+'</strong> element'+((v.domChangeCount||changes.length)===1?'':'s')+' differ';\n if(shifted.length>0){\n /* Say what we actually know: these only moved. Not that the one above\n moved them — the diff does not prove causation and must not imply it. */\n h+=' \\\\u00b7 <strong>'+roots.length+'</strong> changed, <strong>'+shifted.length+'</strong> only shifted position';\n }\n if(v.domChangeCount&&v.domChangeCount>changes.length){\n h+=' \\\\u00b7 showing the top '+changes.length;\n }\n if(v.domTruncated)h+=' <span class=\"dom-truncated\">capture truncated</span>';\n h+='</div>';\n\n h+='<div class=\"dom-list\">';\n for(var r=0;r<roots.length;r++)h+=renderDomRow(roots[r]);\n if(shifted.length>0){\n h+='<div class=\"dom-group-label\">Moved or resized only</div>';\n for(var m=0;m<shifted.length;m++)h+=renderDomRow(shifted[m]);\n }\n h+='</div>';\n return h;\n }\n\n /* Hover a row, highlight its box. Delegated so it covers cards that are\n rendered into the drawer long after this runs. */\n function domSetActive(card,rank,on){\n var box=card.querySelector('[data-dom-box=\"'+rank+'\"]');\n if(box)box.classList.toggle('hi',on);\n var row=card.querySelector('[data-dom-row=\"'+rank+'\"]');\n if(row)row.classList.toggle('hi',on);\n }\n\n document.addEventListener('mouseover',function(e){\n var row=e.target.closest?e.target.closest('.dom-row'):null;\n if(!row)return;\n var card=row.closest('.vis-card');\n if(card)domSetActive(card,row.getAttribute('data-dom-row'),true);\n });\n\n document.addEventListener('mouseout',function(e){\n var row=e.target.closest?e.target.closest('.dom-row'):null;\n if(!row)return;\n var card=row.closest('.vis-card');\n if(card&&!row.classList.contains('pinned'))domSetActive(card,row.getAttribute('data-dom-row'),false);\n });\n`;\n","/**\n * Client-side JavaScript for the redesigned report shell: the verdict band,\n * the needs-attention block, and the detail section rail.\n *\n * These replace `renderSummary` and add two new surfaces; the test grid and\n * the drawer keep their existing renderers and are only enriched in place.\n *\n * ES5-compatible vanilla JS, concatenated into the report's single inline\n * script under a CSP that permits no external resources.\n */\n\nexport const JS_LAYOUT = `\n /* ── Verdict band ── */\n\n /* Both report modes hand us a summary; streaming's carries fewer fields. */\n function vNum(v){return typeof v==='number'?v:0;}\n\n function renderVerdictBar(s){\n var order=[\n {k:'passed',n:vNum(s.passed)},{k:'failed',n:vNum(s.failed)},\n {k:'flaky',n:vNum(s.flaky)},{k:'timedout',n:vNum(s.timedout)},\n {k:'skipped',n:vNum(s.skipped)}\n ];\n var total=0;for(var i=0;i<order.length;i++)total+=order[i].n;\n if(total===0)return '<div class=\"verdict-bar\"><span class=\"verdict-bar-empty\"></span></div>';\n var h='<div class=\"verdict-bar\">';\n for(var j=0;j<order.length;j++){\n if(order[j].n===0)continue;\n h+='<span class=\"vb-'+order[j].k+'\" style=\"flex:'+order[j].n+'\"></span>';\n }\n return h+'</div>';\n }\n\n function renderVerdictHeadline(s){\n var failed=vNum(s.failed),total=vNum(s.total),flaky=vNum(s.flaky);\n if(failed>0)return failed+' of '+total+' test'+(total===1?'':'s')+' failed';\n if(flaky>0)return flaky+' of '+total+' test'+(total===1?'':'s')+' passed on retry';\n if(total===0)return 'No tests ran';\n return 'All '+total+' test'+(total===1?'':'s')+' passed';\n }\n\n /* Where the failures are, when they are all in one place — saves a scan. */\n function failureLocation(){\n var files={};var n=0;\n for(var i=0;i<tests.length;i++){\n var t=tests[i];\n if(t.status!=='failed'&&t.status!=='timedout')continue;\n if(!files[t.filePath]){files[t.filePath]=1;n++;}\n }\n if(n!==1)return null;\n for(var f in files){if(files.hasOwnProperty(f))return f;}\n return null;\n }\n\n function renderSummary(){\n var s=data.summary||{};\n var failed=vNum(s.failed),flaky=vNum(s.flaky);\n var cls=failed>0?'v-failed':(flaky>0?'':'v-passed');\n\n var h='<div class=\"verdict\"><div class=\"verdict-main '+cls+'\">';\n\n h+='<div class=\"verdict-head\"><div class=\"verdict-title\">'+esc(renderVerdictHeadline(s))+'</div>';\n var where=failed>0?failureLocation():null;\n if(where)h+='<div class=\"verdict-where\">all in <code>'+esc(where.split('/').pop())+'</code></div>';\n h+='<div class=\"verdict-dur\"><svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 7v5l3 2\"/></svg>'+fmtDur(data.totalDuration||0)+'</div>';\n h+='</div>';\n\n h+=renderVerdictBar(s);\n\n /* Non-zero statuses get a real count; the rest collapse to one grey line\n rather than each taking a sixth of the width. */\n var kinds=[\n {k:'passed',l:'passed',c:'#22c55e'},{k:'failed',l:'failed',c:'#ef4444'},\n {k:'flaky',l:'flaky',c:'#f59e0b'},{k:'timedout',l:'timed out',c:'#f97316'},\n {k:'skipped',l:'skipped',c:'#6b7280'}\n ];\n var counts='',zeros=[];\n for(var m=0;m<kinds.length;m++){\n var kk=kinds[m],n=vNum(s[kk.k]);\n if(n>0){\n counts+='<div class=\"vc\"><span class=\"vc-dot\" style=\"background:'+kk.c+'\"></span><span class=\"vc-n\">'+n+'</span><span class=\"vc-l\">'+kk.l+'</span></div>';\n }else{\n zeros.push('0 '+kk.l);\n }\n }\n h+='<div class=\"verdict-counts\">'+counts;\n if(zeros.length>0)h+='<div class=\"verdict-zeros\">'+esc(zeros.join(' \\\\u00b7 '))+'</div>';\n h+='</div>';\n\n h+='</div>';\n\n /* What this run actually captured — only tiles with something in them. */\n var tiles=[];\n if(vNum(s.visualChecks)>0)tiles.push({n:vNum(s.visualChecks),l:'visual checks',cls:''});\n if(vNum(s.visualDomChanges)>0)tiles.push({n:vNum(s.visualDomChanges),l:'DOM changes',cls:' vs-dom'});\n if(vNum(s.totalActionSteps)>0)tiles.push({n:vNum(s.totalActionSteps),l:'actions',cls:''});\n if(vNum(s.totalApiCalls)>0)tiles.push({n:vNum(s.totalApiCalls),l:'API calls',cls:''});\n if(vNum(s.totalNavigations)>0)tiles.push({n:vNum(s.totalNavigations),l:'navigations',cls:''});\n\n if(tiles.length>0){\n h+='<div class=\"verdict-side\"><div class=\"verdict-side-label\">Captured this run</div><div class=\"verdict-side-grid\">';\n for(var q=0;q<Math.min(tiles.length,4);q++){\n h+='<div><div class=\"vs-n'+tiles[q].cls+'\">'+tiles[q].n.toLocaleString()+'</div><div class=\"vs-l\">'+esc(tiles[q].l)+'</div></div>';\n }\n h+='</div></div>';\n }\n\n h+='</div>';\n summaryEl.innerHTML=h;\n }\n\n /* ── Needs attention ── */\n\n /* The first line of the error, which is the part that says what happened.\n Playwright's colour codes are stripped by the reporter before this. */\n function causeLine(t){\n if(t.firstFailedAssertion&&typeof assertionSentence==='function'){\n return '<em>'+assertionSentence(t.firstFailedAssertion)+'</em>';\n }\n if(t.assertionsFailed>0&&typeof apiFailureSentence==='function'&&t.apiAssertions){\n var sentence=apiFailureSentence(t.apiAssertions);\n if(sentence)return '<em>'+sentence+'</em>';\n }\n if(evVisualFailed(t)>0&&t.errorMessage){\n var px=t.errorMessage.match(/([\\\\d,]+) pixels \\\\(ratio ([\\\\d.]+)/);\n if(px)return '<em>'+esc(px[1])+' px differ</em>';\n }\n if(!t.errorMessage)return '';\n var first=String(t.errorMessage).split('\\\\n')[0].trim();\n if(first.length>150)first=first.slice(0,150)+'\\\\u2026';\n return '<em>'+esc(first)+'</em>';\n }\n\n function evidenceChips(t){\n var h='';\n /* These accessors read both report shapes — see html-js-render. */\n var _vis=evVisualCount(t),_visFail=evVisualFailed(t),_dom=evDomChanges(t),_net=evNetworkCount(t);\n if(_vis>0){\n h+='<span class=\"nchip'+(_visFail>0?' nchip-visual':'')+'\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/><path d=\"M12 3v18\"/></svg>'+(_visFail>0?'Visual diff':'Visual')+'</span>';\n }\n if(_dom>0){\n h+='<span class=\"nchip nchip-dom\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M8 3H5a2 2 0 00-2 2v3M16 3h3a2 2 0 012 2v3M8 21H5a2 2 0 01-2-2v-3M16 21h3a2 2 0 002-2v-3\"/></svg>'+_dom+' element'+(_dom===1?'':'s')+'</span>';\n }\n if(evHasArtifacts(t)){\n h+='<span class=\"nchip\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polygon points=\"5 3 19 12 5 21\"/></svg>Video</span>';\n }\n if(_net>0){\n h+='<span class=\"nchip\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M3 12h4l3 8 4-16 3 8h4\"/></svg>'+_net+' requests</span>';\n }\n if(t.apiCallCount>0){\n h+='<span class=\"nchip\">'+t.apiCallCount+' API call'+(t.apiCallCount===1?'':'s')+'</span>';\n }\n if(t.assertionsFailed>0){\n h+='<span class=\"nchip nchip-visual\">'+t.assertionsFailed+' failed assertion'+(t.assertionsFailed===1?'':'s')+'</span>';\n }else if(t.assertionsPassed>0){\n h+='<span class=\"nchip\">'+t.assertionsPassed+' assertion'+(t.assertionsPassed===1?'':'s')+'</span>';\n }\n if(typeof mobileEvidenceChips==='function')h+=mobileEvidenceChips(t);\n return h;\n }\n\n function renderNeedsAttention(){\n var el=document.getElementById('needs-attention');\n if(!el)return;\n\n var bad=[];\n for(var i=0;i<tests.length;i++){\n var st=tests[i].status;\n if(st==='failed'||st==='timedout'||st==='flaky')bad.push(tests[i]);\n }\n if(bad.length===0){el.innerHTML='';return;}\n\n /* Hard failures first, then flakes; biggest problems at the top. */\n bad.sort(function(a,b){\n var rank=function(x){return x.status==='flaky'?1:0;};\n return rank(a)-rank(b);\n });\n\n var h='<div class=\"needs\"><div class=\"needs-head\"><span class=\"needs-title\">Needs attention</span><span class=\"needs-count\">'+bad.length+'</span></div><div class=\"needs-list\">';\n\n for(var j=0;j<Math.min(bad.length,8);j++){\n var t=bad[j];\n h+='<div class=\"needs-card'+(t.status==='flaky'?' nc-flaky':'')+'\" data-testid=\"'+esc(t.testId)+'\">';\n\n h+='<div class=\"needs-body\">';\n h+='<div class=\"needs-row1\"><span class=\"needs-name\">'+esc(t.title)+'</span>';\n if(t.testType&&t.testType!=='unknown')h+='<span class=\"trb trb-type\">'+esc(t.testType)+'</span>';\n if(t.status==='flaky')h+='<span class=\"trb trb-flaky\">passed on retry</span>';\n h+='<span class=\"needs-file\">'+esc(t.filePath)+'</span></div>';\n\n var cause=causeLine(t);\n if(cause)h+='<div class=\"needs-cause\">'+cause+'</div>';\n\n var chips=evidenceChips(t);\n if(chips)h+='<div class=\"needs-chips\">'+chips+'</div>';\n h+='</div>';\n\n /* Show the failure, do not only describe it. */\n var _diff=evDiffPath(t);\n if(_diff){\n h+='<div class=\"needs-thumb\"><img src=\"'+esc(_diff)+'\" alt=\"Pixel diff\"><div class=\"needs-thumb-cap\">pixel diff</div></div>';\n }\n\n h+='<div class=\"needs-right\"><div class=\"needs-dur\">'+fmtDur(t.duration)+'</div>';\n h+='<div class=\"needs-open\">Open<svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><path d=\"M9 18l6-6-6-6\"/></svg></div></div>';\n\n h+='</div>';\n }\n\n if(bad.length>8)h+='<div class=\"needs-thumb-cap\" style=\"text-align:left;padding:4px 2px\">and '+(bad.length-8)+' more below</div>';\n\n el.innerHTML=h+'</div></div>';\n }\n\n /* ── Detail section rail ── */\n\n var RAIL_SECTIONS=[\n {id:'failure',label:'Why it failed',fail:true},\n {id:'visual',label:'Visual'},\n {id:'dom',label:'Elements',dom:true},\n {id:'assertions',label:'Assertions'},\n {id:'artifacts',label:'Video & trace'},\n {id:'actions',label:'Actions'},\n {id:'timeline',label:'Timeline'},\n {id:'api',label:'API calls'},\n {id:'console',label:'Console'},\n {id:'network',label:'Network'}\n ];\n\n function railIcon(id){\n if(id==='failure')return '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 8v4M12 16h.01\"/></svg>';\n if(id==='visual')return '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/><path d=\"M12 3v18\"/></svg>';\n if(id==='dom')return '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M8 3H5a2 2 0 00-2 2v3M16 3h3a2 2 0 012 2v3M8 21H5a2 2 0 01-2-2v-3M16 21h3a2 2 0 002-2v-3\"/></svg>';\n if(id==='artifacts')return '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polygon points=\"5 3 19 12 5 21\"/></svg>';\n if(id==='actions')return '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M12 20V10M18 20V4M6 20v-4\"/></svg>';\n if(id==='assertions')return '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M20 6L9 17l-5-5\"/></svg>';\n if(id==='console')return '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M4 17l6-6-6-6M12 19h8\"/></svg>';\n return '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M3 12h4l3 8 4-16 3 8h4\"/></svg>';\n }\n\n /* Built from what the drawer actually rendered, so the rail can never\n offer a jump to a section that is not there. */\n function renderDrawerRail(present,t){\n var h='<div class=\"rail-group\"><div class=\"rail-label\">Evidence</div>';\n var first=true;\n for(var i=0;i<RAIL_SECTIONS.length;i++){\n var sec=RAIL_SECTIONS[i];\n var info=present[sec.id];\n if(!info)continue;\n h+='<button class=\"rail-item'+(sec.fail?' rail-fail':'')+(first?' active':'')+'\" data-rail=\"'+sec.id+'\">';\n h+=railIcon(sec.id);\n h+='<span class=\"rail-name\">'+esc(sec.label)+'</span>';\n if(info.n)h+='<span class=\"rail-n'+(sec.dom?' rail-n-dom':'')+'\">'+info.n+'</span>';\n h+='</button>';\n first=false;\n }\n h+='</div>';\n\n h+='<div class=\"rail-meta\"><div class=\"rail-label\">Test</div><div class=\"rail-meta-rows\">';\n if(t.testId)h+='<div class=\"rail-meta-row\"><span class=\"rail-meta-k\">ID</span><span class=\"rail-meta-v mono\">'+esc(String(t.testId).substring(0,12))+'</span></div>';\n if(t.suiteName)h+='<div class=\"rail-meta-row\"><span class=\"rail-meta-k\">Suite</span><span class=\"rail-meta-v\">'+esc(t.suiteName)+'</span></div>';\n h+='<div class=\"rail-meta-row\"><span class=\"rail-meta-k\">Retry</span><span class=\"rail-meta-v\">'+(t.retryCount>0?t.retryCount+'':'none')+'</span></div>';\n h+='</div></div>';\n return h;\n }\n\n /* Scroll the page so a section's top is just under the viewport top.\n A smooth scrollIntoView was silently dropped here: the rail\n item is a <button>, and a smooth scroll requested from inside its click\n handler never started — the section stayed off-screen and the rail entry\n did nothing. Instant scrolling from a computed offset always lands, and\n smooth is attempted first with a check that it actually moved. */\n function jumpToSection(el){\n if(!el)return;\n var pageY=window.scrollY!==undefined?window.scrollY:(window.pageYOffset||0);\n var y=Math.max(0,el.getBoundingClientRect().top+pageY-12);\n try{\n window.scrollTo({top:y,behavior:'smooth'});\n }catch(e){\n window.scrollTo(0,y);\n }\n setTimeout(function(){\n var now=window.scrollY!==undefined?window.scrollY:(window.pageYOffset||0);\n if(Math.abs(now-y)>40)window.scrollTo(0,y);\n },350);\n }\n\n /* Jump, rather than scroll past everything above it. */\n document.addEventListener('click',function(e){\n var btn=e.target.closest?e.target.closest('.rail-item'):null;\n if(!btn)return;\n var id=btn.getAttribute('data-rail');\n\n /* Elements is not a section of its own — it is a pane inside Visual. Jump\n to Visual and switch the pane, so the rail entry lands where it says. */\n var sectionId=id==='dom'?'visual':id;\n var target=document.querySelector('[data-section=\"'+sectionId+'\"]');\n if(id==='dom'&&target){\n var btns=target.querySelectorAll('.seg-btn');\n for(var b2=0;b2<btns.length;b2++){\n if(btns[b2].textContent==='Elements'){btns[b2].click();break;}\n }\n }\n jumpToSection(target);\n var all=document.querySelectorAll('.rail-item');\n for(var i=0;i<all.length;i++)all[i].classList.remove('active');\n btn.classList.add('active');\n });\n\n /* A failing test opens on its failure, not on the top of a long page. */\n document.addEventListener('click',function(e){\n var card=e.target.closest?e.target.closest('.needs-card'):null;\n if(!card)return;\n openTest(card.getAttribute('data-testid'));\n });\n`;\n\n/**\n * Routing for the detail view.\n *\n * The detail is a page, not an overlay. It gets its own history entry, so the\n * browser Back button works and a link to a failing test opens that test.\n *\n * Hash routing rather than `pushState`: the embedded report is a single file\n * people open straight off disk, and `pushState` is not permitted on `file://`.\n * A hash works in both that case and behind the report server.\n */\nexport const JS_ROUTER = `\n var overviewViewEl=document.getElementById('view-overview');\n var detailViewEl=document.getElementById('view-detail');\n var detailNavEl=document.getElementById('detail-nav');\n var _baseTitle=document.title;\n var _overviewScroll=0;\n var _currentTestId=null;\n /* Set while we are the ones changing the URL, so our own write does not\n bounce back through the router as if the user had pressed Back. */\n var _suppressRoute=false;\n\n function testHash(id){return '#test/'+encodeURIComponent(id);}\n\n function parseTestHash(){\n var h=String(location.hash||'');\n if(h.indexOf('#test/')!==0)return null;\n try{return decodeURIComponent(h.slice(6));}catch(e){return h.slice(6);}\n }\n\n /* Switch the view FIRST, then record it in the URL.\n Driving the switch off the hashchange event made the whole navigation\n hostage to an event that is not always delivered — the view simply never\n changed. The URL still updates, so deep links and Back keep working, but\n nothing user-visible depends on the event arriving. */\n function setHashQuietly(value){\n try{\n _suppressRoute=true;\n if(history&&history.replaceState){\n history.pushState(null,'',value||location.pathname+location.search);\n }else{\n location.hash=value||'';\n }\n }catch(e){\n /* pushState is refused on file:// — the hash still works there. */\n try{location.hash=value||'';}catch(e2){/* no URL update; the view is right */}\n }\n setTimeout(function(){_suppressRoute=false;},0);\n }\n\n /* Whether the detail view we are showing came from a push of ours, and so\n can be closed by unwinding history instead of pushing again. */\n var _pushedDetailEntry=false;\n\n function openTest(testId){\n if(!testId||!testMap[testId])return;\n showTest(testId);\n setHashQuietly(testHash(testId));\n _pushedDetailEntry=true;\n }\n\n function closeTest(){\n if(!_currentTestId)return;\n /* Leave the detail entry behind rather than stacking another one on top of\n it. setHashQuietly pushes, so closing this way left #test/<id> in\n history *behind* the overview — the browser Back button then replayed it\n and re-opened the very test the reader had just closed.\n history.back() unwinds our own push; routeFromHash handles the\n resulting popstate and switches the view. */\n if(_pushedDetailEntry&&history&&history.back){\n _pushedDetailEntry=false;\n history.back();\n return;\n }\n showOverview();\n setHashQuietly('');\n }\n\n /* Prev/next walk the CURRENTLY FILTERED list, so stepping through a\n \"Failed\" filter visits the failures and nothing else. */\n function testNeighbours(testId){\n var list=getFilteredTests();\n for(var i=0;i<list.length;i++){\n if(list[i].testId===testId)return {list:list,idx:i};\n }\n return {list:list,idx:-1};\n }\n\n function stepTest(delta){\n if(!_currentTestId)return;\n var n=testNeighbours(_currentTestId);\n if(n.idx<0)return;\n var next=n.list[n.idx+delta];\n if(next)openTest(next.testId);\n }\n\n function renderDetailNav(testId){\n var t=testMap[testId]||{};\n var n=testNeighbours(testId);\n var pos=n.idx>=0?(n.idx+1)+' of '+n.list.length:'';\n\n var h='<button class=\"detail-back\" id=\"detail-back\"><svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M15 18l-6-6 6-6\"/></svg>Back to results</button>';\n if(t.filePath)h+='<span class=\"detail-crumb\">'+esc(t.filePath)+'</span>';\n\n h+='<div class=\"detail-steps\">';\n var noPrev=n.idx<=0;\n var noNext=n.idx<0||n.idx>=n.list.length-1;\n h+='<button class=\"detail-step'+(noPrev?' is-off':'')+'\" id=\"detail-prev\" title=\"Previous test\"'+(noPrev?' disabled':'')+'><svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M15 18l-6-6 6-6\"/></svg></button>';\n if(pos)h+='<span class=\"detail-pos\">'+esc(pos)+'</span>';\n h+='<button class=\"detail-step'+(noNext?' is-off':'')+'\" id=\"detail-next\" title=\"Next test\"'+(noNext?' disabled':'')+'><svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M9 18l6-6-6-6\"/></svg></button>';\n h+='</div>';\n\n detailNavEl.innerHTML=h;\n }\n\n function showOverview(){\n _currentTestId=null;\n var v=drawerBodyEl.querySelector('video');\n if(v)v.pause();\n detailViewEl.hidden=true;\n overviewViewEl.hidden=false;\n document.title=_baseTitle;\n /* Put the reader back where they were in the list, not at the top. */\n window.scrollTo(0,_overviewScroll);\n }\n\n function showTest(testId){\n if(_currentTestId===null)_overviewScroll=window.scrollY||window.pageYOffset||0;\n _currentTestId=testId;\n overviewViewEl.hidden=true;\n detailViewEl.hidden=false;\n window.scrollTo(0,0);\n var t=testMap[testId];\n document.title=(t&&t.title?t.title+' \\u2014 ':'')+_baseTitle;\n renderDetailNav(testId);\n loadAndRenderTest(testId);\n }\n\n /* Back/forward, and the initial load of a pasted link. Idempotent: a\n re-entrant call for the view already showing does nothing. */\n function routeFromHash(){\n if(_suppressRoute)return;\n var id=parseTestHash();\n if(id&&testMap[id]){\n if(_currentTestId!==id)showTest(id);\n }else if(_currentTestId!==null){\n showOverview();\n }\n }\n\n window.addEventListener('hashchange',routeFromHash);\n window.addEventListener('popstate',routeFromHash);\n`;\n","/**\n * Client-side JavaScript for the API surfaces.\n *\n * Three things the report captured and never showed: the run's own API\n * breakdowns (eight of the nine stats reached only the terminal), the\n * assertions (captured, uploaded, documented — and rendered nowhere), and the\n * per-test evidence a grid row needs to be worth scanning.\n *\n * ES5-compatible vanilla JS, concatenated into the report's single inline\n * script under a CSP that permits no external resources.\n */\n\nexport const JS_API = `\n var API_METHOD_COLOR={GET:'#60a5fa',POST:'#34d399',PUT:'#fbbf24',PATCH:'#a78bfa',DELETE:'#f87171',HEAD:'#6b7280',OPTIONS:'#6b7280'};\n var API_STATUS_ORDER=['2xx','3xx','4xx','5xx','error'];\n var API_STATUS_COLOR={'2xx':'#22c55e','3xx':'#60a5fa','4xx':'#fb923c','5xx':'#ef4444','error':'#ef4444'};\n\n function apiMethodColor(m){return API_METHOD_COLOR[String(m||'').toUpperCase()]||'#8b949e';}\n\n /* A value from an assertion. These are arbitrary JSON, so keep them short\n enough to sit on one line and let the reader open the call for the rest. */\n function fmtAssertValue(v){\n if(v===undefined)return 'undefined';\n if(v===null)return 'null';\n var s;\n if(typeof v==='string'){s=v;}\n else{try{s=JSON.stringify(v);}catch(e){s=String(v);}}\n if(s===undefined||s===null)s=String(v);\n if(s.length>120)s=s.slice(0,120)+'\\\\u2026';\n return s;\n }\n\n function statusMixTotal(mix){\n if(!mix)return 0;\n var n=0;\n for(var i=0;i<API_STATUS_ORDER.length;i++)n+=(mix[API_STATUS_ORDER[i]]||0);\n return n;\n }\n\n /* ── Run-level API band ── */\n\n function renderApiMethodCard(byMethod,total){\n var keys=[];\n for(var k in byMethod){if(byMethod.hasOwnProperty(k)&&byMethod[k]>0)keys.push(k);}\n if(keys.length===0)return '';\n keys.sort(function(a,b){return byMethod[b]-byMethod[a];});\n var max=byMethod[keys[0]]||1;\n var h='<div class=\"tcard\"><div class=\"tcard-h\">By method<span class=\"tcard-sub\">'+total.toLocaleString()+' calls</span></div><div style=\"display:flex;flex-direction:column;gap:8px\">';\n for(var i=0;i<keys.length;i++){\n var m=keys[i],c=apiMethodColor(m),pct=Math.max(2,Math.round((byMethod[m]/max)*100));\n h+='<div class=\"mrow\"><span class=\"mrow-k\" style=\"color:'+c+'\">'+esc(m)+'</span>';\n h+='<span class=\"mrow-track\"><span class=\"mrow-fill\" style=\"width:'+pct+'%;background:'+c+'\"></span></span>';\n h+='<span class=\"mrow-n\">'+byMethod[m].toLocaleString()+'</span></div>';\n }\n return h+'</div></div>';\n }\n\n function renderApiStatusCard(mix){\n var total=statusMixTotal(mix);\n if(total===0)return '';\n var h='<div class=\"tcard\"><div class=\"tcard-h\">By status</div><div class=\"sbar\">';\n for(var i=0;i<API_STATUS_ORDER.length;i++){\n var k=API_STATUS_ORDER[i],n=mix[k]||0;\n if(n===0)continue;\n h+='<span style=\"flex:'+n+';background:'+API_STATUS_COLOR[k]+'\"></span>';\n }\n h+='</div><div class=\"sgrid\">';\n for(var j=0;j<API_STATUS_ORDER.length;j++){\n var kk=API_STATUS_ORDER[j],nn=mix[kk]||0;\n h+='<div class=\"scell\"><span class=\"spill sp-'+kk+'\">'+esc(kk)+'</span>';\n h+='<span class=\"scell-n'+(nn===0?' is-zero':'')+'\">'+nn.toLocaleString()+'</span></div>';\n }\n return h+'</div></div>';\n }\n\n function renderAssertionsCard(s){\n var total=vNum(s.totalAssertions);\n if(total===0)return '';\n var pass=vNum(s.passedAssertions),fail=vNum(s.failedAssertions);\n var h='<div class=\"tcard\"><div class=\"tcard-h\">Assertions</div>';\n h+='<div class=\"tbig\"><span class=\"tbig-n\">'+total.toLocaleString()+'</span><span class=\"tbig-l\">total</span></div>';\n h+='<div class=\"tsplit\">';\n h+='<span class=\"tsplit-i tsplit-ok\"><svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><path d=\"M20 6L9 17l-5-5\"/></svg>'+pass.toLocaleString()+'</span>';\n h+='<span class=\"tsplit-i '+(fail>0?'tsplit-bad':'')+'\" style=\"'+(fail>0?'':'color:var(--fg-2)')+'\"><svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><path d=\"M18 6L6 18M6 6l12 12\"/></svg>'+fail.toLocaleString()+'</span>';\n h+='</div></div>';\n return h;\n }\n\n function renderApiTimingCard(rt,uniqueUrls){\n if(!rt)return '';\n var h='<div class=\"tcard\"><div class=\"tcard-h\">Response time</div><div class=\"tpair\">';\n h+='<div class=\"tpair-i\"><span class=\"tpair-n\">'+Math.round(rt.avg)+'<small>ms</small></span><span class=\"tpair-l\">average</span></div>';\n h+='<div class=\"tpair-i\"><span class=\"tpair-n\">'+Math.round(rt.p95)+'<small>ms</small></span><span class=\"tpair-l\">p95</span></div>';\n h+='<div class=\"tpair-i\"><span class=\"tpair-n\">'+Math.round(rt.max)+'<small>ms</small></span><span class=\"tpair-l\">slowest</span></div>';\n if(uniqueUrls>0)h+='<div class=\"tpair-i\"><span class=\"tpair-n\">'+uniqueUrls.toLocaleString()+'</span><span class=\"tpair-l\">endpoints</span></div>';\n return h+'</div></div>';\n }\n\n /* The band only exists when the run actually produced the evidence it\n describes — a pure-UI run renders nothing here rather than four zeroes. */\n function renderTypeBand(){\n var el=document.getElementById('type-band');\n if(!el)return;\n var s=data.summary||{};\n var cards=[];\n\n if(vNum(s.totalApiCalls)>0){\n var mc=renderApiMethodCard(s.apiCallsByMethod||{},vNum(s.totalApiCalls));\n if(mc)cards.push(mc);\n var sc=renderApiStatusCard(s.apiCallsByStatusRange||{});\n if(sc)cards.push(sc);\n var tc=renderApiTimingCard(s.apiResponseTime,vNum(s.uniqueApiUrls));\n if(tc)cards.push(tc);\n }\n var ac=renderAssertionsCard(s);\n if(ac)cards.push(ac);\n\n if(typeof renderMobileCards==='function'){\n var mob=renderMobileCards(s);\n for(var mi=0;mi<mob.length;mi++)cards.push(mob[mi]);\n }\n\n if(cards.length===0){el.innerHTML='';return;}\n var cls=cards.length>=3?'tband-3':'tband-2';\n /* Four cards wrap to two rows of two rather than shrinking to slivers. */\n if(cards.length===4)cls='tband-2';\n el.innerHTML='<div class=\"tband '+cls+'\">'+cards.join('')+'</div>';\n }\n\n /* ── Per-row evidence ── */\n\n /* Status chips plus a single pass/fail mark. A row has roughly 110px for\n this, so it shows the shape of the calls, not the calls themselves. */\n function apiRowEvidence(t){\n if(!t)return '';\n var mix=t.apiStatusMix,h='';\n if(mix&&statusMixTotal(mix)>0){\n for(var i=0;i<API_STATUS_ORDER.length;i++){\n var k=API_STATUS_ORDER[i],n=mix[k]||0;\n if(n===0)continue;\n h+='<span class=\"achip sp-'+k+'\" title=\"'+n+' '+esc(k)+' response'+(n===1?'':'s')+'\">'+n+'</span>';\n }\n }else if(t.apiCallCount>0){\n h+='<span class=\"achip\" style=\"background:rgba(255,255,255,.04);color:var(--fg-1)\">'+t.apiCallCount+'</span>';\n }\n var pass=t.assertionsPassed||0,fail=t.assertionsFailed||0;\n if(pass>0||fail>0){\n if(fail>0){\n h+='<span class=\"amark amark-bad\" title=\"'+fail+' assertion'+(fail===1?'':'s')+' failed\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><path d=\"M18 6L6 18M6 6l12 12\"/></svg></span>';\n }else{\n h+='<span class=\"amark amark-ok\" title=\"'+pass+' assertion'+(pass===1?'':'s')+' passed\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><path d=\"M20 6L9 17l-5-5\"/></svg></span>';\n }\n }\n return h?'<span class=\"arow\">'+h+'</span>':'';\n }\n\n /* ── Detail: the assertions section ── */\n\n function findApiCall(apiCalls,callId){\n if(!apiCalls||!callId)return null;\n for(var i=0;i<apiCalls.length;i++){\n var c=apiCalls[i];\n if(c.id===callId||c.callId===callId)return c;\n }\n return null;\n }\n\n function assertionExpression(a){\n if(a.expression)return a.expression;\n /* No expression was captured; say what the assertion checked instead of\n leaving the row blank. */\n return 'expect('+String(a.type||'value')+') \\\\u2192 '+fmtAssertValue(a.expected);\n }\n\n function renderAssertionsSection(assertions,apiCalls){\n if(!assertions||assertions.length===0)return '';\n var pass=0,fail=0,i;\n for(i=0;i<assertions.length;i++){\n if(assertions[i].status==='failed')fail++;else pass++;\n }\n\n var h='<div class=\"apisec\"><div class=\"apisec-head\">';\n h+='<span class=\"apisec-title\">Assertions</span>';\n if(pass>0)h+='<span class=\"apisec-badge ab-ok\">'+pass+' passed</span>';\n if(fail>0)h+='<span class=\"apisec-badge ab-bad\">'+fail+' failed</span>';\n h+='</div><div class=\"asec\">';\n\n /* Failures first — the reason anyone opened this page. */\n var ordered=assertions.slice().sort(function(a,b){\n return (a.status==='failed'?0:1)-(b.status==='failed'?0:1);\n });\n\n for(i=0;i<ordered.length;i++){\n var a=ordered[i],failed=a.status==='failed';\n h+='<div class=\"arow-item'+(failed?' is-failed':'')+'\">';\n h+=failed\n ? '<svg style=\"flex-shrink:0\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#ef4444\" stroke-width=\"2.5\"><path d=\"M18 6L6 18M6 6l12 12\"/></svg>'\n : '<svg style=\"flex-shrink:0\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#22c55e\" stroke-width=\"2.5\"><path d=\"M20 6L9 17l-5-5\"/></svg>';\n h+='<span class=\"atype\">'+esc(String(a.type||'custom'))+'</span>';\n h+='<span class=\"aexpr\">'+esc(assertionExpression(a))+'</span>';\n if(a.location&&a.location.line)h+='<span class=\"aloc\">:'+a.location.line+'</span>';\n h+='</div>';\n\n if(failed){\n h+='<div class=\"adiff\">';\n h+='<span class=\"adiff-i\"><span class=\"adiff-k\">expected</span><span class=\"adiff-v adiff-exp\">'+esc(fmtAssertValue(a.expected))+'</span></span>';\n h+='<span class=\"adiff-i\"><span class=\"adiff-k\">received</span><span class=\"adiff-v adiff-act\">'+esc(fmtAssertValue(a.actual))+'</span></span>';\n h+='</div>';\n var call=findApiCall(apiCalls,a.callId);\n if(call){\n var code=call.responseStatusCode!=null?call.responseStatusCode:(call.response&&call.response.status);\n h+='<div class=\"acall\">on '+esc(String(call.method||''))+' '+esc(urlPath(String(call.url||'')))+(code?' \\\\u2192 '+code:'')+'</div>';\n }\n }\n }\n return h+'</div></div>';\n }\n\n /* The same sentence, built from the flattened summary the index carries —\n so the run overview reads identically in embedded and streaming mode. */\n function assertionSentence(fa){\n if(!fa)return '';\n return 'A <code>'+esc(String(fa.type||'custom'))+'</code> assertion expected <code>'+\n esc(String(fa.expected))+'</code> but received <code>'+esc(String(fa.actual))+'</code>.';\n }\n\n /* The headline for an API failure: the assertion, not the matcher wrapper. */\n function apiFailureSentence(assertions){\n if(!assertions||assertions.length===0)return '';\n for(var i=0;i<assertions.length;i++){\n var a=assertions[i];\n if(a.status!=='failed')continue;\n return 'A <code>'+esc(String(a.type||'custom'))+'</code> assertion expected <code>'+\n esc(fmtAssertValue(a.expected))+'</code> but received <code>'+\n esc(fmtAssertValue(a.actual))+'</code>.';\n }\n return '';\n }\n`;\n","/**\n * Styles for the visual regression section of the HTML report.\n *\n * Kept out of `html-css.ts` deliberately — that file is already past the\n * 500-line ceiling in Constitution Principle II, so growing it further would\n * make a bad situation worse.\n *\n * The comparison modes lean on two layered images of identical size: the\n * baseline sits in normal flow and sets the box, the candidate is absolutely\n * positioned on top of it, and a single slider drives either a `clip-path`\n * wipe or an opacity blend. No canvas, no measurement, nothing to keep in sync\n * on resize.\n */\n\nexport const CSS_VISUAL = `\n.vis-header{font-size:11px;font-weight:700;color:var(--fg-1);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px;display:flex;align-items:center;gap:6px}\n.vis-header svg{opacity:.6}\n.vis-count{font-weight:400;color:var(--fg-2);font-size:10px}\n.vis-count-fail{color:#ef4444}\n.vis-list{display:flex;flex-direction:column;gap:20px}\n.vis-card{border:1px solid var(--bd);border-radius:10px;padding:14px;background:var(--bg-2);min-width:0}\n.vis-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:10px}\n.vis-name{font-size:12px;font-weight:600;color:var(--fg-1);word-break:break-all}\n.vis-badge{font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;text-transform:uppercase;letter-spacing:.04em;white-space:nowrap}\n.vis-badge.vb-passed{background:rgba(3,183,156,0.14);color:#2dd4a8}\n.vis-badge.vb-failed{background:rgba(239,68,68,0.14);color:#ef4444}\n.vis-badge.vb-new{background:rgba(59,130,246,0.14);color:#60a5fa}\n.vis-badge.vb-updated{background:rgba(234,179,8,0.14);color:#eab308}\n.vis-badge.vb-dom{background:rgba(232,121,249,0.14);color:#e879f9}\n.vis-metrics{font-size:11px;color:var(--fg-2);margin-left:auto;white-space:nowrap;font-variant-numeric:tabular-nums}\n.vis-note{font-size:11px;color:var(--fg-2);font-style:italic;padding:6px 0}\n\n/* Side-by-side: wraps to a single column when the drawer is narrow. */\n.vis-pair{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px}\n.vis-pane-label{font-size:10px;font-weight:600;color:var(--fg-2);text-transform:uppercase;letter-spacing:.04em;margin-bottom:6px}\n\n/* Layered comparison surface shared by the slider and onion-skin modes. */\n.vis-stack{position:relative;line-height:0;border:1px solid var(--bd);border-radius:8px;overflow:hidden;background:var(--bg)}\n.vis-stack img{display:block;width:100%;height:auto}\n.vis-stack .vis-top{position:absolute;top:0;left:0;width:100%;height:100%;object-fit:contain}\n.vis-handle{position:absolute;top:0;bottom:0;width:2px;background:#03b79c;pointer-events:none;box-shadow:0 0 0 1px rgba(0,0,0,0.35)}\n.vis-range{width:100%;margin-top:10px;accent-color:#03b79c;cursor:ew-resize}\n.vis-range-row{display:flex;align-items:center;gap:10px;font-size:10px;color:var(--fg-2);text-transform:uppercase;letter-spacing:.04em}\n.vis-range-row .vis-range{margin-top:0}\n\n/* A checkerboard behind the diff so fully transparent regions stay legible. */\n.vis-diff-wrap{background-image:linear-gradient(45deg,var(--bd-xs) 25%,transparent 25%),linear-gradient(-45deg,var(--bd-xs) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,var(--bd-xs) 75%),linear-gradient(-45deg,transparent 75%,var(--bd-xs) 75%);background-size:16px 16px;background-position:0 0,0 8px,8px -8px,-8px 0;border-radius:8px}\n`;\n\n/**\n * Styles for the Elements pane — the DOM comparison.\n *\n * The overlay mirrors the platform's existing region overlay: an absolutely\n * positioned layer over a `position:relative` image, with each box placed in\n * percentages so nothing needs measuring or re-measuring on resize.\n */\nexport const CSS_DOM = `\n.dom-figure{position:relative;line-height:0;border:1px solid var(--bd);border-radius:8px;overflow:hidden;margin-bottom:12px}\n.dom-img{display:block;width:100%;height:auto}\n.dom-ov{position:absolute;top:0;left:0;right:0;bottom:0;pointer-events:none}\n.dom-box{position:absolute;border:2px solid #03b79c;border-radius:2px;opacity:.55;transition:opacity .12s,box-shadow .12s}\n.dom-box.hi{opacity:1;box-shadow:0 0 0 2px rgba(0,0,0,.45),0 0 12px rgba(255,255,255,.25)}\n\n.dom-summary{font-size:11px;color:var(--fg-2);margin-bottom:10px}\n.dom-summary strong{color:var(--fg-1);font-variant-numeric:tabular-nums}\n.dom-truncated{display:inline-block;margin-left:6px;padding:1px 6px;border-radius:999px;background:rgba(234,179,8,.14);color:#eab308;font-size:10px;font-weight:600}\n\n.dom-list{display:flex;flex-direction:column;gap:2px}\n.dom-group-label{font-size:10px;font-weight:600;color:var(--fg-2);text-transform:uppercase;letter-spacing:.04em;margin:12px 0 4px}\n.dom-row{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:6px;cursor:default;flex-wrap:wrap}\n.dom-row:hover,.dom-row.hi{background:var(--bd-xs)}\n.dom-swatch{width:8px;height:8px;border-radius:2px;flex:none}\n.dom-label{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--fg-1);word-break:break-all}\n.dom-kind{font-size:9px;font-weight:600;padding:1px 6px;border-radius:999px;text-transform:uppercase;letter-spacing:.04em;background:var(--bd-xs);color:var(--fg-2)}\n.dom-kind.dk-restyled{background:rgba(232,121,249,.14);color:#e879f9}\n.dom-kind.dk-text{background:rgba(96,165,250,.14);color:#60a5fa}\n.dom-kind.dk-attrs{background:rgba(251,146,60,.14);color:#fb923c}\n.dom-kind.dk-added{background:rgba(3,183,156,.14);color:#2dd4a8}\n.dom-kind.dk-removed{background:rgba(239,68,68,.14);color:#ef4444}\n.dom-area{margin-left:auto;font-size:10px;color:var(--fg-2);font-variant-numeric:tabular-nums}\n\n.dom-detail{padding:0 8px 8px 24px;display:flex;flex-direction:column;gap:3px}\n.dom-prop{display:flex;align-items:baseline;gap:8px;font-size:10px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;flex-wrap:wrap}\n.dom-prop-name{color:var(--fg-2);min-width:130px}\n.dom-prop-attr .dom-prop-name{color:#fb923c;font-weight:600}\n.dom-from{color:#ef4444;word-break:break-all}\n.dom-arrow{color:var(--fg-2)}\n.dom-to{color:#2dd4a8;word-break:break-all}\n`;\n","/**\n * Styles for the redesigned report layout: the verdict band, the needs-\n * attention block, the enriched test row and the detail section rail.\n *\n * A separate file because `html-css.ts` is already 569 lines, past the\n * 500-line ceiling in Constitution Principle II. Everything here uses the\n * tokens defined there — no new colours, radii or type sizes are introduced.\n */\n\nexport const CSS_LAYOUT = `\n/* ── Wider shell ──\n 960px was sized for a list. The row now carries evidence and timing, and\n the detail panel shows two screenshots side by side; both were cramped. */\n.wrap{max-width:1400px;padding:28px 40px 64px}\n\n/* ── Verdict band (replaces the six equal stat chips) ── */\n.verdict{display:flex;gap:16px;align-items:stretch;margin-bottom:22px}\n.verdict-main{flex:1;min-width:0;background:var(--bg-1);border:1px solid var(--bd);border-radius:12px;padding:18px 22px;display:flex;flex-direction:column;gap:14px}\n.verdict-main.v-failed{border-color:rgba(239,68,68,0.22)}\n.verdict-main.v-passed{border-color:rgba(34,197,94,0.2)}\n.verdict-head{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap}\n.verdict-title{font-size:21px;font-weight:700;letter-spacing:-0.01em;color:var(--fg)}\n.verdict-where{font-size:12px;color:var(--fg-1)}\n.verdict-where code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:var(--fg)}\n.verdict-dur{margin-left:auto;display:flex;align-items:center;gap:6px;font-size:12px;color:var(--fg-1);white-space:nowrap}\n\n/* Proportion bar: one glance at the shape of the run. */\n.verdict-bar{display:flex;height:8px;gap:2px}\n.verdict-bar span{border-radius:999px;min-width:3px}\n.vb-passed{background:#22c55e}.vb-failed{background:#ef4444}\n.vb-flaky{background:#f59e0b}.vb-skipped{background:#6b7280}.vb-timedout{background:#f97316}\n.verdict-bar-empty{flex:1;background:var(--bd-m);border-radius:999px}\n\n.verdict-counts{display:flex;align-items:center;gap:20px;flex-wrap:wrap}\n.vc{display:flex;align-items:center;gap:7px}\n.vc-dot{width:9px;height:9px;border-radius:50%;flex-shrink:0}\n.vc-n{font-size:14px;font-weight:700;color:var(--fg);font-variant-numeric:tabular-nums}\n.vc-l{font-size:12px;color:var(--fg-1)}\n/* Statuses with nothing in them stop taking a sixth of the width. */\n.verdict-zeros{margin-left:auto;font-size:11px;color:var(--fg-2)}\n\n.verdict-side{width:268px;flex-shrink:0;background:var(--bg-1);border:1px solid var(--bd);border-radius:12px;padding:18px 20px;display:flex;flex-direction:column;gap:12px}\n.verdict-side-label{font-size:10px;font-weight:600;color:var(--fg-2);letter-spacing:.06em;text-transform:uppercase}\n.verdict-side-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px 10px}\n.vs-n{font-size:17px;font-weight:700;color:var(--fg);font-variant-numeric:tabular-nums}\n.vs-n.vs-dom{color:#e879f9}\n.vs-l{font-size:10px;color:var(--fg-1)}\n\n/* ── Needs attention ── */\n.needs{display:flex;flex-direction:column;gap:10px;margin-bottom:22px}\n.needs-head{display:flex;align-items:center;gap:8px;padding:0 2px}\n.needs-title{font-size:11px;font-weight:700;color:var(--fg);letter-spacing:.06em;text-transform:uppercase}\n.needs-count{font-size:10px;font-weight:700;color:#ef4444;background:rgba(239,68,68,0.12);padding:1px 7px;border-radius:4px}\n.needs-list{display:flex;flex-direction:column;gap:8px}\n.needs-card{background:var(--bg-1);border:1px solid rgba(239,68,68,0.2);border-left:3px solid #ef4444;border-radius:10px;padding:14px 18px;display:flex;gap:16px;align-items:flex-start;cursor:pointer;transition:border-color .15s,background .1s}\n.needs-card:hover{background:var(--hvr);border-color:rgba(239,68,68,0.4)}\n.needs-card.nc-flaky{border-color:rgba(245,158,11,0.2);border-left-color:#f59e0b}\n.needs-card.nc-flaky:hover{border-color:rgba(245,158,11,0.4)}\n.needs-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:8px}\n.needs-row1{display:flex;align-items:center;gap:10px;flex-wrap:wrap}\n.needs-name{font-size:14px;font-weight:600;color:var(--fg)}\n.needs-file{font-size:11px;color:var(--fg-2);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n.needs-cause{font-size:12px;color:var(--fg-1);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;line-height:1.6;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.needs-cause em{font-style:normal;color:var(--fg-err)}\n.needs-chips{display:flex;gap:6px;flex-wrap:wrap;align-items:center}\n.needs-thumb{width:148px;flex-shrink:0;display:flex;flex-direction:column;gap:5px}\n.needs-thumb img{width:100%;height:92px;object-fit:cover;object-position:top center;border-radius:7px;border:1px solid rgba(239,68,68,0.35);background:var(--bg)}\n.needs-thumb-cap{font-size:9px;color:var(--fg-2);text-align:center}\n.needs-right{display:flex;flex-direction:column;align-items:flex-end;gap:8px;flex-shrink:0}\n.needs-dur{font-size:13px;font-weight:700;color:var(--fg-1);font-variant-numeric:tabular-nums}\n.needs-open{display:flex;align-items:center;gap:4px;font-size:11px;font-weight:600;color:#2dd4a8}\n\n/* Pill chips used in the needs cards. */\n.nchip{display:flex;align-items:center;gap:4px;font-size:10px;font-weight:500;color:var(--fg-1);background:var(--bg-2);border:1px solid var(--bd);padding:2px 8px;border-radius:999px}\n.nchip-visual{color:#2dd4a8;background:rgba(3,183,156,0.1);border-color:rgba(3,183,156,0.22);font-weight:600}\n.nchip-dom{color:#e879f9;background:rgba(232,121,249,0.1);border-color:rgba(232,121,249,0.2);font-weight:600}\n\n/* ── Enriched test row ──\n Same height as before. Two regions added: what was captured, and how long\n it took relative to the slowest test in the run. */\n.trow-evidence{display:flex;gap:4px;align-items:center;flex-shrink:0}\n.tev{width:18px;height:18px;border-radius:4px;background:var(--bg-2);display:flex;align-items:center;justify-content:center;color:var(--fg-1);flex-shrink:0}\n.tev-visual{background:rgba(3,183,156,0.12);color:#2dd4a8}\n.tev-dom{background:rgba(232,121,249,0.12);color:#e879f9}\n.tev-err{background:rgba(239,68,68,0.12);color:#ef4444}\n.trow-time{width:120px;flex-shrink:0;display:flex;align-items:center;gap:8px}\n.trow-bar{flex:1;height:4px;border-radius:999px;background:var(--bd-m);overflow:hidden}\n.trow-bar span{display:block;height:100%;border-radius:999px}\n.tb-passed{background:#22c55e}.tb-failed{background:#ef4444}\n.tb-flaky{background:#f59e0b}.tb-skipped{background:#6b7280}.tb-timedout{background:#f97316}\n.test-row-dur{font-variant-numeric:tabular-nums}\n/* The title and its badges sit on one line now that the row has columns. */\n.test-row-info.tri-inline{flex-direction:row;align-items:center;gap:10px}\n.test-row-info.tri-inline .test-row-badges{flex-shrink:0}\n\n/* ── Detail panel ──\n Was 50vw, which left the comparison about 330px a side and a dead half-\n screen of grid behind it. */\n.drawer{width:min(1240px,94vw);max-width:94vw}\n.drawer-layout{display:flex;gap:28px;align-items:flex-start}\n.drawer-rail{width:206px;flex-shrink:0;position:sticky;top:0;display:flex;flex-direction:column;gap:18px}\n.drawer-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:24px}\n.rail-group{display:flex;flex-direction:column;gap:2px}\n.rail-label{font-size:10px;font-weight:600;color:var(--fg-2);letter-spacing:.06em;text-transform:uppercase;padding:0 10px 7px}\n.rail-item{display:flex;align-items:center;gap:9px;padding:8px 10px;border-radius:7px;color:var(--fg-1);cursor:pointer;transition:background .12s,color .12s;border:none;background:transparent;font-family:inherit;text-align:left;width:100%}\n.rail-item:hover{background:var(--bd-xs);color:var(--fg)}\n.rail-item.active{background:rgba(3,183,156,0.12);color:#2dd4a8}\n.rail-item.active .rail-name{font-weight:600}\n.rail-item.rail-fail{color:var(--fg-err)}\n.rail-item.rail-fail.active{background:rgba(239,68,68,0.1);color:var(--fg-err)}\n.rail-name{font-size:12.5px;font-weight:500;flex:1;min-width:0}\n.rail-n{font-size:10px;font-weight:700;background:var(--bd-xs);padding:1px 6px;border-radius:4px;font-variant-numeric:tabular-nums}\n.rail-item.active .rail-n{background:rgba(3,183,156,0.18)}\n.rail-n.rail-n-dom{color:#e879f9;background:rgba(232,121,249,0.12)}\n.rail-meta{border-top:1px solid var(--bd);padding-top:16px;display:flex;flex-direction:column;gap:9px}\n.rail-meta-rows{padding:0 10px;display:flex;flex-direction:column;gap:7px;font-size:11px}\n.rail-meta-row{display:flex;gap:8px}\n.rail-meta-k{color:var(--fg-2);width:44px;flex-shrink:0}\n.rail-meta-v{color:var(--fg-1);min-width:0;overflow:hidden;text-overflow:ellipsis}\n.rail-meta-v.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n\n/* Scroll-margin so a rail jump does not tuck the heading under the bar. */\n.drawer-section{scroll-margin-top:12px}\n\n@media (max-width:1100px){\n .drawer{width:96vw;max-width:96vw}\n .drawer-layout{flex-direction:column;gap:18px}\n .drawer-rail{width:100%;position:static;flex-direction:row;flex-wrap:wrap;gap:6px}\n .rail-group{flex-direction:row;flex-wrap:wrap;gap:4px}\n .rail-label,.rail-meta{display:none}\n .rail-item{width:auto}\n .verdict{flex-direction:column}\n .verdict-side{width:100%}\n}\n`;\n\n/**\n * Styles for the detail page.\n *\n * The detail used to be a fixed overlay; it is now a view in the flow, so the\n * page scrolls rather than a panel inside it. The `.drawer-*` class names are\n * kept because four modules query them (`.drawer-body video`, the section\n * wrappers) — the names are historical, the layout is not.\n */\nexport const CSS_DETAIL_PAGE = `\n/* The overlay is gone. Anything still carrying .drawer must not float. */\n.drawer,.drawer-backdrop{display:none}\n\n.detail-page{display:flex;flex-direction:column;gap:20px}\n#view-detail[hidden],#view-overview[hidden]{display:none}\n\n.detail-nav{display:flex;align-items:center;gap:14px;flex-wrap:wrap;padding-bottom:14px;border-bottom:1px solid var(--bd)}\n.detail-back{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:600;color:var(--fg-1);background:transparent;border:1px solid var(--bd-m);border-radius:8px;padding:6px 12px 6px 9px;cursor:pointer;font-family:inherit;transition:background .15s,color .15s,border-color .15s}\n.detail-back:hover{background:var(--hvr);color:var(--fg);border-color:var(--bd-l)}\n.detail-crumb{font-size:11px;color:var(--fg-2);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n\n.detail-steps{margin-left:auto;display:inline-flex;align-items:center;border:1px solid var(--bd-m);border-radius:8px;overflow:hidden}\n.detail-step{display:flex;align-items:center;justify-content:center;width:32px;height:30px;background:transparent;border:none;color:var(--fg-1);cursor:pointer;font-family:inherit;transition:background .15s,color .15s}\n.detail-step:hover:not(.is-off){background:var(--hvr);color:var(--fg)}\n.detail-step.is-off{color:var(--fg-2);cursor:default}\n.detail-pos{font-size:11px;font-weight:600;color:var(--fg-1);padding:0 10px;border-left:1px solid var(--bd-m);border-right:1px solid var(--bd-m);line-height:30px;font-variant-numeric:tabular-nums}\n\n/* The page scrolls now, so the old panel body must not scroll inside it. */\n#view-detail .drawer-body{flex:none;overflow:visible;padding:0}\n.detail-page .detail-rail,.detail-page .drawer-rail{top:16px}\n`;\n","/**\n * Styles for the API surfaces: the run-level API band on the overview, the\n * per-row API evidence in the grid, and the assertions section in the detail\n * page.\n *\n * Tokens are the report's own (`html-css.ts`); nothing here introduces a new\n * colour that is not already in the palette.\n */\n\nexport const CSS_API = `\n/* ── Run-level type band ── */\n.tband{display:grid;gap:16px;margin-bottom:20px}\n.tband-3{grid-template-columns:1.1fr 1.1fr .9fr}\n.tband-2{grid-template-columns:1fr 1fr}\n.tcard{background:var(--bg-1);border:1px solid rgba(255,255,255,.06);border-radius:12px;padding:16px 20px;display:flex;flex-direction:column;gap:12px;min-width:0}\n.tcard-h{font-size:10px;font-weight:600;color:var(--fg-2);letter-spacing:.06em;text-transform:uppercase;display:flex;align-items:center;gap:8px}\n.tcard-h .tcard-sub{margin-left:auto;font-weight:500;letter-spacing:0;text-transform:none;font-size:10px}\n\n/* Method distribution */\n.mrow{display:flex;align-items:center;gap:10px}\n.mrow-k{width:54px;font-size:11px;font-weight:700;font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace)}\n.mrow-track{display:block;flex:1;height:6px;border-radius:999px;background:rgba(255,255,255,.06);overflow:hidden}\n.mrow-fill{display:block;height:100%;border-radius:999px;min-width:3px}\n.mrow-n{font-size:11px;color:var(--fg-1);width:34px;text-align:right}\n\n/* Status distribution */\n.sbar{display:flex;height:8px;gap:2px}\n.sbar>span{border-radius:999px}\n.sgrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px 12px}\n.scell{display:flex;align-items:center;gap:8px}\n.spill{font-size:10px;font-weight:700;padding:1px 7px;border-radius:4px}\n.scell-n{font-size:12px;font-weight:700}\n.scell-n.is-zero{font-weight:400;color:var(--fg-2)}\n.sp-2xx{background:rgba(34,197,94,.12);color:#22c55e}\n.sp-3xx{background:rgba(96,165,250,.12);color:#60a5fa}\n.sp-4xx{background:rgba(251,146,60,.12);color:#fb923c}\n.sp-5xx{background:rgba(239,68,68,.12);color:#ef4444}\n.sp-error{background:rgba(239,68,68,.12);color:#ef4444}\n\n/* Big-number card (assertions, timings) */\n.tbig{display:flex;align-items:baseline;gap:8px}\n.tbig-n{font-size:28px;font-weight:800;line-height:1}\n.tbig-l{font-size:11px;color:var(--fg-1)}\n.tsplit{display:flex;gap:14px;flex-wrap:wrap}\n.tsplit-i{display:flex;align-items:center;gap:6px;font-size:13px;font-weight:700}\n.tsplit-ok{color:#22c55e}\n.tsplit-bad{color:#ef4444}\n.tpair{display:flex;gap:18px;flex-wrap:wrap}\n.tpair-i{display:flex;flex-direction:column;gap:2px}\n.tpair-n{font-size:17px;font-weight:700}\n.tpair-n small{font-size:11px;font-weight:500;color:var(--fg-1)}\n.tpair-l{font-size:10px;color:var(--fg-1)}\n\n/* ── Row evidence in the grid ── */\n.arow{display:flex;gap:5px;align-items:center;flex-shrink:0}\n.achip{font-size:10px;font-weight:700;padding:1px 6px;border-radius:4px;font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace)}\n.amark{width:18px;height:18px;border-radius:4px;display:flex;align-items:center;justify-content:center;flex-shrink:0}\n.amark-ok{background:rgba(34,197,94,.12);color:#22c55e}\n.amark-bad{background:rgba(239,68,68,.12);color:#ef4444}\n\n/* ── Assertions section (detail page) ── */\n.apisec{border:1px solid var(--bd);border-radius:11px;background:var(--bg-1);overflow:hidden}\n.apisec-head{display:flex;align-items:center;gap:10px;padding:12px 20px;border-bottom:1px solid rgba(255,255,255,.06)}\n.apisec-title{font-size:12px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}\n.apisec-badge{font-size:10px;font-weight:700;padding:1px 7px;border-radius:4px}\n.apisec-badge.ab-ok{background:rgba(34,197,94,.12);color:#22c55e}\n.apisec-badge.ab-bad{background:rgba(239,68,68,.12);color:#ef4444}\n.asec{display:flex;flex-direction:column}\n.arow-item{display:flex;align-items:center;gap:12px;padding:10px 20px;border-bottom:1px solid rgba(255,255,255,.04)}\n.arow-item:last-child{border-bottom:none}\n.arow-item.is-failed{background:rgba(239,68,68,.04)}\n.atype{font-size:9px;font-weight:700;padding:1px 6px;border-radius:999px;letter-spacing:.04em;text-transform:uppercase;flex-shrink:0;background:rgba(255,255,255,.04);color:var(--fg-1)}\n.arow-item.is-failed .atype{background:rgba(239,68,68,.14);color:#ef4444}\n.aexpr{font-size:11.5px;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace)}\n.aloc{font-size:10px;color:var(--fg-2);flex-shrink:0;font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace)}\n.adiff{display:flex;gap:22px;flex-wrap:wrap;padding:0 20px 12px 52px}\n.adiff-i{display:flex;gap:8px;align-items:baseline}\n.adiff-k{font-size:10px;color:var(--fg-2);width:58px}\n.adiff-v{font-size:11px;font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace);word-break:break-all}\n.adiff-exp{color:#2dd4a8}\n.adiff-act{color:#ef4444}\n.acall{font-size:10px;color:var(--fg-2);padding:0 20px 10px 52px;font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace)}\n\n/* ── API call row: the two columns that used to render empty ── */\n.ndt-assert{display:inline-flex;align-items:center;justify-content:center;width:100%;font-size:10px;font-weight:700}\n.ndt-assert-ok{color:#22c55e}\n.ndt-assert-bad{color:#ef4444}\n.ndt-assert-none{color:var(--fg-2);font-weight:400}\n.ndt-err-note{color:#ef4444;font-size:11px}\n\n/* Trace pane — a link out, plus how to open the thing */\n.trace-open{display:flex;flex-direction:column;gap:10px;padding:18px;background:var(--bg-2);border:1px solid var(--bd);border-radius:10px}\n.trace-link{display:inline-flex;align-items:center;gap:8px;align-self:flex-start;font-size:12.5px;font-weight:600;color:#2dd4a8;text-decoration:none;border:1px solid rgba(3,183,156,.3);background:rgba(3,183,156,.08);padding:8px 14px;border-radius:8px}\n.trace-link:hover{background:rgba(3,183,156,.14)}\n.trace-hint{font-size:11.5px;color:var(--fg-1);line-height:1.6}\n.trace-hint code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;background:var(--bg-code);padding:1px 5px;border-radius:4px;color:var(--fg)}\n\n/* Cookie table */\n.ndt-headers-label{font-size:9px;font-weight:700;color:var(--fg-2);letter-spacing:.06em;text-transform:uppercase;padding:10px 14px 0}\n.ck-tbl{display:grid;grid-template-columns:auto 1fr;gap:6px 16px;padding:12px 14px;font-size:11.5px}\n.ck-k{color:var(--fg-1);font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace)}\n.ck-v{color:var(--fg);word-break:break-all;font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace)}\n.ck-attr{color:var(--fg-2);font-size:10px}\n\n@media (max-width:1100px){\n .tband-3,.tband-2{grid-template-columns:1fr}\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';\nimport { JS_ACTIONS } from './html-js-actions.js';\nimport { JS_CONSOLE } from './html-js-console.js';\nimport { JS_ARTIFACTS } from './html-js-artifacts.js';\nimport { JS_VISUAL } from './html-js-visual.js';\nimport { JS_DOM } from './html-js-dom.js';\nimport { JS_LAYOUT, JS_ROUTER } from './html-js-layout.js';\nimport { JS_API } from './html-js-api.js';\nimport { CSS_VISUAL, CSS_DOM } from './html-css-visual.js';\nimport { CSS_LAYOUT, CSS_DETAIL_PAGE } from './html-css-layout.js';\nimport { CSS_API } from './html-css-api.js';\n\nconst JS = `\n(function(){\n var data=JSON.parse(document.getElementById('report-data').textContent);\n ${JS_RENDER}\n ${JS_ACTIONS}\n ${JS_CONSOLE}\n ${JS_NETWORK}\n ${JS_FILTERS}\n ${JS_ARTIFACTS}\n ${JS_DOM}\n ${JS_VISUAL}\n ${JS_INTERACTIONS}\n ${JS_LAYOUT}\n ${JS_API}\n ${JS_ROUTER}\n applyTheme();\n renderRunMeta();\n renderSummary();\n renderTypeBand();\n initFilters();\n renderFilterBar();\n renderTestGrid();\n renderNeedsAttention();\n routeFromHash();\n var _lo=document.getElementById('loading-overlay');if(_lo)_lo.remove();\n})();`;\n\nexport function renderHtmlDocument(reportJson: string, serverPort?: number | null, manifestJson?: string | null): 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 const safeManifestJson = manifestJson ? manifestJson.replace(/<\\//g, '<\\\\/') : null;\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: file: 'self'; media-src blob: data: file: 'self'; connect-src 'self' http://127.0.0.1:*;\">\n<title>TestRelic AI Analytics Report</title>\n${serverPort ? `<meta name=\"artifact-server-port\" content=\"${serverPort}\">` : ''}\n<link rel=\"icon\" type=\"image/svg+xml\" href=\"data:image/svg+xml;base64,${Buffer.from(LOGO_SVG_RAW).toString('base64')}\">\n<style>${CSS}${CSS_VISUAL}${CSS_DOM}${CSS_LAYOUT}${CSS_DETAIL_PAGE}${CSS_API}</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=\"view-overview\">\n <div id=\"summary-strip\"></div>\n <div id=\"type-band\"></div>\n <div id=\"needs-attention\"></div>\n <div id=\"filter-bar\" class=\"filter-bar\"></div>\n <div id=\"test-grid\"></div>\n </div>\n <div id=\"view-detail\" class=\"detail-page\" hidden>\n <div class=\"detail-nav\" id=\"detail-nav\"></div>\n <div class=\"drawer-body\" id=\"drawer-body\"></div>\n </div>\n</div>\n<div class=\"loading-overlay\" id=\"loading-overlay\"><div class=\"loading-spinner\"></div><div class=\"loading-text\">Loading report...</div></div>\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\">&times;</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${safeManifestJson ? `<script id=\"artifact-manifest-data\" type=\"application/json\">${safeManifestJson}</script>` : ''}\n<script>${JS}</script>\n</body>\n</html>`;\n}\n\n/**\n * Render a streaming-mode HTML document.\n * Embeds only the summary and compact test index — detail data is fetched from the server.\n */\nexport function renderStreamingHtmlDocument(\n summaryJson: string,\n indexJson: string,\n serverPort?: number | null,\n manifestJson?: string | null,\n): string {\n const safeSummary = summaryJson.replace(/<\\//g, '<\\\\/');\n // Use indexJson directly — caller provides pre-compacted index (from index-compact.json)\n // to avoid expensive parse-reserialize of the full index for large suites.\n const safeIndex = indexJson.replace(/<\\//g, '<\\\\/');\n const safeManifest = manifestJson ? manifestJson.replace(/<\\//g, '<\\\\/') : null;\n\n // Build a minimal report-data object that the existing JS modules can consume.\n // The timeline is empty — details are served on-demand from the server.\n // Parse summary to extract top-level fields for the report header\n let summaryObj: Record<string, unknown> = {};\n try { summaryObj = JSON.parse(summaryJson); } catch { /* use defaults */ }\n const streamingData = `{\n \"schemaVersion\":\"2.0\",\n \"reportMode\":\"streaming\",\n \"serverPort\":${serverPort ?? 'null'},\n \"summary\":${safeSummary},\n \"index\":${safeIndex},\n \"timeline\":[],\n \"testRunId\":${JSON.stringify(String(summaryObj.testRunId ?? ''))},\n \"startedAt\":${JSON.stringify(String(summaryObj.startedAt ?? ''))},\n \"completedAt\":${JSON.stringify(String(summaryObj.completedAt ?? ''))},\n \"totalDuration\":${Number(summaryObj.totalDuration) || 0},\n \"ci\":${summaryObj.ci ? JSON.stringify(summaryObj.ci).replace(/<\\//g, '<\\\\/') : 'null'},\n \"metadata\":${summaryObj.metadata ? JSON.stringify(summaryObj.metadata).replace(/<\\//g, '<\\\\/') : 'null'},\n \"shardRunIds\":${summaryObj.shardRunIds ? JSON.stringify(summaryObj.shardRunIds).replace(/<\\//g, '<\\\\/') : 'null'}\n }`;\n\n const JS_STREAMING = `\n(function(){\n var data=JSON.parse(document.getElementById('report-data').textContent);\n var _serverPort=data.serverPort||null;\n var _serverAvailable=false;\n var _detailCache={};\n var _detailCacheKeysS=[];\n var _detailCacheMaxS=50;\n var _testIndex=data.index||[];\n\n /* Resolve the API base URL: if served from http, use same origin; otherwise use explicit port */\n function getApiBase(){\n if(window.location.protocol==='http:'||window.location.protocol==='https:'){\n return window.location.origin;\n }\n if(_serverPort){return 'http://127.0.0.1:'+_serverPort;}\n return null;\n }\n var _apiBase=getApiBase();\n\n /* Check server availability */\n function checkServer(cb){\n if(!_apiBase){cb(false);return;}\n fetch(_apiBase+'/api/health',{method:'GET'}).then(function(r){\n _serverAvailable=r.ok;cb(r.ok);\n }).catch(function(){_serverAvailable=false;cb(false);});\n }\n\n /* Bounded detail cache (LRU, max 50 entries) */\n function sCacheDetail(testId,d){\n if(_detailCache[testId]){\n var ki=_detailCacheKeysS.indexOf(testId);\n if(ki>-1)_detailCacheKeysS.splice(ki,1);\n }else if(_detailCacheKeysS.length>=_detailCacheMaxS){\n var evict=_detailCacheKeysS.shift();\n delete _detailCache[evict];\n }\n _detailCache[testId]=d;\n _detailCacheKeysS.push(testId);\n }\n\n /* Fetch test detail metadata from server */\n function fetchTestDetail(testId,cb){\n if(_detailCache[testId]){cb(null,_detailCache[testId]);return;}\n if(!_serverAvailable||!_apiBase){cb('Server unavailable');return;}\n fetch(_apiBase+'/api/tests/'+testId).then(function(r){\n if(!r.ok)throw new Error('HTTP '+r.status);\n return r.json();\n }).then(function(d){\n sCacheDetail(testId,d);cb(null,d);\n }).catch(function(e){cb(e.message||'Failed to fetch');});\n }\n\n /* Fetch paginated heavy data (network, console, api-calls) */\n function fetchTestData(testId,dataType,page,pageSize,cb){\n if(!_serverAvailable||!_apiBase){cb('Server unavailable');return;}\n var url=_apiBase+'/api/tests/'+testId+'/'+dataType+'?page='+page+'&pageSize='+pageSize;\n fetch(url).then(function(r){\n if(!r.ok)throw new Error('HTTP '+r.status);\n return r.json();\n }).then(function(d){cb(null,d);}).catch(function(e){cb(e.message||'Failed to fetch');});\n }\n window.__trFetchTestData=fetchTestData;\n\n /* Make fetchTestDetail and index available globally for other modules */\n window.__trStreamingFetchDetail=fetchTestDetail;\n window.__trTestIndex=_testIndex;\n window.__trServerPort=_serverPort;\n window.__trIsStreaming=true;\n\n ${JS_RENDER}\n ${JS_ACTIONS}\n ${JS_CONSOLE}\n ${JS_NETWORK}\n ${JS_FILTERS}\n ${JS_ARTIFACTS}\n ${JS_DOM}\n ${JS_VISUAL}\n ${JS_INTERACTIONS}\n ${JS_LAYOUT}\n ${JS_API}\n ${JS_ROUTER}\n applyTheme();\n renderRunMeta();\n renderSummary();\n renderTypeBand();\n initFilters();\n renderFilterBar();\n renderTestGrid();\n renderNeedsAttention();\n /* Route only once the health check has answered. Routing first meant a\n pasted link straight to a test always rendered \"Server unavailable\" —\n the fetch ran while _serverAvailable was still false. */\n checkServer(function(ok){routeFromHash();});\n var _lo=document.getElementById('loading-overlay');if(_lo)_lo.remove();\n})();`;\n\n return `<!-- TestRelic AI Analytics Report — streaming mode -->\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: file: 'self'; media-src blob: data: file: 'self'; connect-src 'self' http://127.0.0.1:*;\">\n<title>TestRelic AI Analytics Report</title>\n${serverPort ? `<meta name=\"report-server-port\" content=\"${serverPort}\">` : ''}\n${serverPort ? `<meta name=\"artifact-server-port\" content=\"${serverPort}\">` : ''}\n<link rel=\"icon\" type=\"image/svg+xml\" href=\"data:image/svg+xml;base64,${Buffer.from(LOGO_SVG_RAW).toString('base64')}\">\n<style>${CSS}${CSS_VISUAL}${CSS_DOM}${CSS_LAYOUT}${CSS_DETAIL_PAGE}${CSS_API}</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=\"view-overview\">\n <div id=\"summary-strip\"></div>\n <div id=\"type-band\"></div>\n <div id=\"needs-attention\"></div>\n <div id=\"filter-bar\" class=\"filter-bar\"></div>\n <div id=\"test-grid\"></div>\n </div>\n <div id=\"view-detail\" class=\"detail-page\" hidden>\n <div class=\"detail-nav\" id=\"detail-nav\"></div>\n <div class=\"drawer-body\" id=\"drawer-body\"></div>\n </div>\n</div>\n<div class=\"loading-overlay\" id=\"loading-overlay\"><div class=\"loading-spinner\"></div><div class=\"loading-text\">Loading report...</div></div>\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\">&times;</button>\n </div>\n <div class=\"filter-drawer-body\" id=\"filter-drawer-body\"></div>\n</aside>\n<script id=\"report-data\" type=\"application/json\">${streamingData}</script>\n${safeManifest ? `<script id=\"artifact-manifest-data\" type=\"application/json\">${safeManifest}</script>` : ''}\n<script>${JS_STREAMING}</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 * Uses `execFile` with an argv array (no shell) so a report path containing\n * shell metacharacters can never be interpreted as a command.\n */\n\nimport { execFile } from 'node:child_process';\n\nexport function openInBrowser(filePath: string): void {\n try {\n const platform = process.platform;\n let file: string;\n let args: string[];\n\n if (platform === 'darwin') {\n file = 'open';\n args = [filePath];\n } else if (platform === 'win32') {\n // `start` is a cmd builtin, so it must be invoked via cmd.exe. The path\n // is passed as a discrete argv entry (not concatenated into a shell\n // string); the empty \"\" is the window-title placeholder `start` expects.\n file = 'cmd';\n args = ['/c', 'start', '', filePath];\n } else {\n file = 'xdg-open';\n args = [filePath];\n }\n\n execFile(file, args, (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 * JSONL (JSON Lines) streaming writer and reader.\n *\n * Writer: Appends one JSON object per line to a file using a persistent fd.\n * Used in worker processes to stream network requests, console logs,\n * and API calls to disk instead of accumulating in memory.\n *\n * Reader: Reads a specific page of lines from a JSONL file without loading\n * the entire file into memory.\n */\n\nimport { openSync, writeSync, closeSync, unlinkSync, mkdirSync, createReadStream } from 'node:fs';\nimport { join } from 'node:path';\nimport { tmpdir } from 'node:os';\nimport { randomUUID } from 'node:crypto';\nimport { createInterface } from 'node:readline';\n\nconst TESTRELIC_TMP_DIR = join(tmpdir(), 'testrelic-data');\n\n/**\n * Append-only JSONL file writer.\n *\n * Keeps a file descriptor open for the lifetime of the writer.\n * Each `append()` call writes one JSON-serialized line synchronously,\n * so data hits disk immediately and is eligible for GC.\n */\nexport class JsonlFileWriter {\n private readonly filePath: string;\n private readonly fd: number;\n private count = 0;\n private closed = false;\n\n constructor(prefix: string) {\n mkdirSync(TESTRELIC_TMP_DIR, { recursive: true });\n this.filePath = join(TESTRELIC_TMP_DIR, `${prefix}-${randomUUID().substring(0, 8)}.jsonl`);\n this.fd = openSync(this.filePath, 'w');\n }\n\n /** Serialize `obj` as a single JSON line and write to disk. */\n append(obj: unknown): void {\n if (this.closed) return;\n const line = JSON.stringify(obj) + '\\n';\n writeSync(this.fd, line);\n this.count++;\n }\n\n /** Absolute path to the JSONL file. */\n getPath(): string {\n return this.filePath;\n }\n\n /** Number of lines written so far. */\n getCount(): number {\n return this.count;\n }\n\n /** Close the file descriptor. Must be called before the file is moved. */\n close(): void {\n if (this.closed) return;\n this.closed = true;\n try {\n closeSync(this.fd);\n } catch {\n // Ignore close errors (fd may already be closed)\n }\n }\n\n /** Remove the temporary file from disk. */\n cleanup(): void {\n try {\n unlinkSync(this.filePath);\n } catch {\n // File may already be moved/deleted\n }\n }\n}\n\n/**\n * Read a page of items from a JSONL file.\n *\n * Uses `readline` to stream through the file line by line.\n * Only parses lines within the requested page range.\n * Skips lines before the page start without parsing them.\n *\n * @param filePath — Absolute path to the JSONL file\n * @param page — 1-based page number\n * @param pageSize — Number of items per page\n * @param knownTotal — If provided, avoids counting remaining lines after the page\n */\nexport async function readJsonlPage<T>(\n filePath: string,\n page: number,\n pageSize: number,\n knownTotal?: number,\n): Promise<{ items: T[]; total: number; page: number; pageSize: number; totalPages: number }> {\n const skip = (page - 1) * pageSize;\n const items: T[] = [];\n let lineCount = 0;\n\n const rl = createInterface({\n input: createReadStream(filePath, { encoding: 'utf-8' }),\n crlfDelay: Infinity,\n });\n\n for await (const line of rl) {\n if (line.length === 0) continue;\n if (lineCount >= skip && items.length < pageSize) {\n try {\n items.push(JSON.parse(line) as T);\n } catch {\n // Skip malformed lines\n }\n }\n lineCount++;\n // Early exit if we have all items and know the total\n if (items.length >= pageSize && knownTotal !== undefined) {\n break;\n }\n }\n\n const total = knownTotal ?? lineCount;\n const totalPages = Math.max(1, Math.ceil(total / pageSize));\n return { items, total, page, pageSize, totalPages };\n}\n","/**\n * Report Server Routes — HTTP route handlers for the report server.\n *\n * Each handler reads data from the streaming report directory on disk\n * and returns JSON responses. Pagination, filtering, search, and sort\n * are supported for the test index endpoint.\n */\n\nimport { readFileSync, existsSync, readdirSync, statSync, rmSync, createReadStream } from 'node:fs';\nimport { join, extname } from 'node:path';\nimport type { IncomingMessage, ServerResponse, Server } from 'node:http';\nimport type { TestIndexEntry, StreamingReportSummary, TestDetailData } from '@testrelic/core';\nimport { readJsonlPage } from './jsonl-stream.js';\n\nconst SAFE_ID_PATTERN = /^[a-f0-9]{12}$/;\nconst TIMESTAMP_PATTERN = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}-\\d{2}-\\d{2}(-\\d+)?$/;\nconst MAX_PAGE_SIZE = 500;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nexport function sendJson(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(body));\n}\n\nexport function setCorsHeaders(res: ServerResponse): void {\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'GET, DELETE, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type');\n}\n\nfunction readJsonFile<T>(filePath: string): T | null {\n try {\n if (!existsSync(filePath)) return null;\n return JSON.parse(readFileSync(filePath, 'utf-8')) as T;\n } catch {\n return null;\n }\n}\n\nfunction getDirSize(dirPath: string): number {\n let size = 0;\n try {\n const entries = readdirSync(dirPath, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = join(dirPath, entry.name);\n if (entry.isFile()) {\n size += statSync(fullPath).size;\n } else if (entry.isDirectory()) {\n size += getDirSize(fullPath);\n }\n }\n } catch { /* skip */ }\n return size;\n}\n\n// ---------------------------------------------------------------------------\n// Route Handlers\n// ---------------------------------------------------------------------------\n\n/** GET /api/health */\nexport function handleHealth(\n _req: IncomingMessage,\n res: ServerResponse,\n reportDir: string,\n startTime: number,\n): void {\n const index = readJsonFile<TestIndexEntry[]>(join(reportDir, 'index.json'));\n sendJson(res, 200, {\n status: 'ok',\n reportMode: 'streaming',\n testCount: index?.length ?? 0,\n uptime: Math.floor((Date.now() - startTime) / 1000),\n });\n}\n\n/** GET /api/summary */\nexport function handleSummary(\n _req: IncomingMessage,\n res: ServerResponse,\n reportDir: string,\n): void {\n const summary = readJsonFile<StreamingReportSummary>(join(reportDir, 'summary.json'));\n if (!summary) {\n sendJson(res, 404, { error: 'Summary not found' });\n return;\n }\n sendJson(res, 200, summary);\n}\n\n/** GET /api/tests — paginated, filterable, searchable, sortable test index. */\nexport function handleTests(\n req: IncomingMessage,\n res: ServerResponse,\n reportDir: string,\n): void {\n const index = readJsonFile<TestIndexEntry[]>(join(reportDir, 'index.json'));\n if (!index) {\n sendJson(res, 404, { error: 'Test index not found' });\n return;\n }\n\n const url = new URL(req.url ?? '/', `http://${req.headers.host}`);\n const params = url.searchParams;\n\n // Parse query parameters\n const page = Math.max(1, parseInt(params.get('page') ?? '1', 10) || 1);\n const pageSize = Math.min(MAX_PAGE_SIZE, Math.max(1, parseInt(params.get('pageSize') ?? '100', 10) || 100));\n const statusFilter = params.get('status')?.split(',').filter(Boolean) ?? null;\n const fileFilter = params.get('file') ?? null;\n const searchQuery = params.get('search')?.toLowerCase() ?? null;\n const tagFilter = params.get('tag')?.split(',').filter(Boolean) ?? null;\n const sortField = params.get('sort') ?? 'file';\n const sortOrder = params.get('order') === 'desc' ? -1 : 1;\n\n // Filter\n let filtered = index;\n if (statusFilter && statusFilter.length > 0) {\n filtered = filtered.filter((t) => statusFilter.includes(t.status));\n }\n if (fileFilter) {\n filtered = filtered.filter((t) => t.filePath === fileFilter || t.filePath.startsWith(fileFilter + '/'));\n }\n if (searchQuery) {\n filtered = filtered.filter((t) =>\n t.title.toLowerCase().includes(searchQuery) ||\n t.filePath.toLowerCase().includes(searchQuery),\n );\n }\n if (tagFilter && tagFilter.length > 0) {\n filtered = filtered.filter((t) => tagFilter.some((tag) => t.tags.includes(tag)));\n }\n\n // Sort\n filtered = [...filtered].sort((a, b) => {\n let cmp = 0;\n switch (sortField) {\n case 'duration': cmp = a.duration - b.duration; break;\n case 'status': cmp = a.status.localeCompare(b.status); break;\n case 'title': cmp = a.title.localeCompare(b.title); break;\n case 'file':\n default: cmp = a.filePath.localeCompare(b.filePath); break;\n }\n return cmp * sortOrder;\n });\n\n // Paginate\n const totalItems = filtered.length;\n const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));\n const startIdx = (page - 1) * pageSize;\n const tests = filtered.slice(startIdx, startIdx + pageSize);\n\n sendJson(res, 200, {\n tests,\n pagination: { page, pageSize, totalItems, totalPages },\n filters: {\n status: statusFilter,\n file: fileFilter,\n search: searchQuery,\n tag: tagFilter,\n },\n });\n}\n\n/** GET /api/tests/:id — metadata for a single test (lightweight). */\nexport function handleTestDetail(\n _req: IncomingMessage,\n res: ServerResponse,\n reportDir: string,\n testId: string,\n): void {\n // Path traversal protection\n if (!SAFE_ID_PATTERN.test(testId)) {\n sendJson(res, 400, { error: 'Invalid test ID format' });\n return;\n }\n\n // v3 directory format: tests/<testId>/meta.json\n const metaPath = join(reportDir, 'tests', testId, 'meta.json');\n // Fallback: v2 single-file format: tests/<testId>.json\n const legacyPath = join(reportDir, 'tests', `${testId}.json`);\n\n const filePath = existsSync(metaPath) ? metaPath : existsSync(legacyPath) ? legacyPath : null;\n if (!filePath) {\n sendJson(res, 404, { error: `Test not found: ${testId}` });\n return;\n }\n\n try {\n const data = readFileSync(filePath, 'utf-8');\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(data);\n } catch (err) {\n sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\n/** GET /api/tests/:id/network — paginated network requests from JSONL. */\nexport async function handleTestDataFile(\n req: IncomingMessage,\n res: ServerResponse,\n reportDir: string,\n testId: string,\n dataType: 'network' | 'console' | 'api-calls',\n): Promise<void> {\n if (!SAFE_ID_PATTERN.test(testId)) {\n sendJson(res, 400, { error: 'Invalid test ID format' });\n return;\n }\n\n const fileNames: Record<string, string> = {\n 'network': 'network.jsonl',\n 'console': 'console.jsonl',\n 'api-calls': 'api-calls.jsonl',\n };\n\n const jsonlPath = join(reportDir, 'tests', testId, fileNames[dataType]);\n if (!existsSync(jsonlPath)) {\n sendJson(res, 200, { items: [], total: 0, page: 1, pageSize: 50, totalPages: 0 });\n return;\n }\n\n try {\n const url = new URL(req.url ?? '/', `http://${req.headers.host}`);\n const params = url.searchParams;\n const page = Math.max(1, parseInt(params.get('page') ?? '1', 10) || 1);\n const pageSize = Math.min(MAX_PAGE_SIZE, Math.max(1, parseInt(params.get('pageSize') ?? '50', 10) || 50));\n\n // Read known total from meta.json if available\n let knownTotal: number | undefined;\n const metaPath = join(reportDir, 'tests', testId, 'meta.json');\n if (existsSync(metaPath)) {\n try {\n const meta = JSON.parse(readFileSync(metaPath, 'utf-8')) as TestDetailData;\n switch (dataType) {\n case 'network': knownTotal = meta.networkRequestsCount; break;\n case 'console': knownTotal = meta.consoleLogsCount; break;\n case 'api-calls': knownTotal = meta.apiCallsCount; break;\n }\n } catch { /* use line counting fallback */ }\n }\n\n const result = await readJsonlPage(jsonlPath, page, pageSize, knownTotal);\n sendJson(res, 200, result);\n } catch (err) {\n sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\n/** GET /api/files — per-file test aggregation. */\nexport function handleFiles(\n _req: IncomingMessage,\n res: ServerResponse,\n reportDir: string,\n): void {\n const index = readJsonFile<TestIndexEntry[]>(join(reportDir, 'index.json'));\n if (!index) {\n sendJson(res, 404, { error: 'Test index not found' });\n return;\n }\n\n const fileMap = new Map<string, { total: number; passed: number; failed: number; flaky: number; skipped: number; timedOut: number }>();\n for (const t of index) {\n if (t.isRetry) continue; // Only count final attempts\n let stats = fileMap.get(t.filePath);\n if (!stats) {\n stats = { total: 0, passed: 0, failed: 0, flaky: 0, skipped: 0, timedOut: 0 };\n fileMap.set(t.filePath, stats);\n }\n stats.total++;\n switch (t.status) {\n case 'passed': stats.passed++; break;\n case 'failed': stats.failed++; break;\n case 'flaky': stats.flaky++; break;\n case 'skipped': stats.skipped++; break;\n case 'timedout': stats.timedOut++; break;\n }\n }\n\n const files = Array.from(fileMap.entries())\n .map(([filePath, s]) => ({ filePath, ...s }))\n .sort((a, b) => a.filePath.localeCompare(b.filePath));\n\n sendJson(res, 200, { files });\n}\n\n/** GET /api/artifacts — list artifact runs. */\nexport function handleArtifactsList(\n _req: IncomingMessage,\n res: ServerResponse,\n artifactsDir: string,\n): void {\n if (!existsSync(artifactsDir)) {\n sendJson(res, 200, { runs: [], totalSizeBytes: 0 });\n return;\n }\n try {\n const runs: Array<{ folderName: string; totalSizeBytes: number; testCount: number }> = [];\n let totalSizeBytes = 0;\n const entries = readdirSync(artifactsDir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory() || !TIMESTAMP_PATTERN.test(entry.name)) continue;\n const runDir = join(artifactsDir, entry.name);\n const size = getDirSize(runDir);\n const testDirs = readdirSync(runDir, { withFileTypes: true }).filter((e) => e.isDirectory());\n runs.push({ folderName: entry.name, totalSizeBytes: size, testCount: testDirs.length });\n totalSizeBytes += size;\n }\n runs.sort((a, b) => b.folderName.localeCompare(a.folderName));\n sendJson(res, 200, { runs, totalSizeBytes });\n } catch (err) {\n sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\n/** DELETE /api/artifacts — delete all artifact runs. */\nexport function handleArtifactsDeleteAll(\n _req: IncomingMessage,\n res: ServerResponse,\n artifactsDir: string,\n): void {\n try {\n let deletedCount = 0;\n let freedBytes = 0;\n if (existsSync(artifactsDir)) {\n const entries = readdirSync(artifactsDir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory() || !TIMESTAMP_PATTERN.test(entry.name)) continue;\n const runDir = join(artifactsDir, entry.name);\n const size = getDirSize(runDir);\n rmSync(runDir, { recursive: true, force: true });\n freedBytes += size;\n deletedCount++;\n }\n }\n sendJson(res, 200, { deletedCount, freedBytes });\n } catch (err) {\n sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\n/** DELETE /api/artifacts/:folderName — delete a single artifact run. */\nexport function handleArtifactDelete(\n _req: IncomingMessage,\n res: ServerResponse,\n artifactsDir: string,\n folderName: string,\n): void {\n if (!TIMESTAMP_PATTERN.test(folderName)) {\n sendJson(res, 400, { error: 'Invalid folder name' });\n return;\n }\n const runDir = join(artifactsDir, folderName);\n try {\n const stat = statSync(runDir);\n if (!stat.isDirectory()) {\n sendJson(res, 404, { error: 'Not found' });\n return;\n }\n } catch {\n sendJson(res, 404, { error: 'Not found' });\n return;\n }\n try {\n const freedBytes = getDirSize(runDir);\n rmSync(runDir, { recursive: true, force: true });\n sendJson(res, 200, { deleted: folderName, freedBytes });\n } catch (err) {\n sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\n/** POST /api/shutdown — gracefully shut down the server. */\nexport function handleShutdown(\n _req: IncomingMessage,\n res: ServerResponse,\n server: Server,\n): void {\n sendJson(res, 200, { status: 'shutting_down' });\n server.close();\n}\n\n/**\n * Serve a static file from disk, honouring a byte `Range`.\n *\n * A `<video>` element asks for `Range: bytes=0-` before it will play. This\n * used to answer every request with a flat `200` and the whole body, which\n * Chrome treats as a protocol violation for media: the element sat in\n * NETWORK_LOADING with readyState 0, no error event, and a permanent spinner.\n * Recordings were captured, copied and linked correctly — and never played.\n *\n * Files are streamed rather than read whole: a screencast is routinely tens of\n * megabytes and there is no reason to hold one in memory per request.\n */\nexport function serveStaticFile(\n req: IncomingMessage,\n res: ServerResponse,\n filePath: string,\n): void {\n if (!existsSync(filePath)) {\n sendJson(res, 404, { error: 'File not found' });\n return;\n }\n try {\n const size = statSync(filePath).size;\n const ext = extname(filePath).toLowerCase();\n const contentType = MIME_TYPES[ext] ?? 'application/octet-stream';\n const range = parseByteRange(req.headers.range, size);\n\n if (range === 'unsatisfiable') {\n res.writeHead(416, { 'Content-Range': `bytes */${size}`, 'Accept-Ranges': 'bytes' });\n res.end();\n return;\n }\n\n if (range) {\n res.writeHead(206, {\n 'Content-Type': contentType,\n 'Content-Length': range.end - range.start + 1,\n 'Content-Range': `bytes ${range.start}-${range.end}/${size}`,\n 'Accept-Ranges': 'bytes',\n });\n pipeOrFail(createReadStream(filePath, { start: range.start, end: range.end }), res);\n return;\n }\n\n res.writeHead(200, {\n 'Content-Type': contentType,\n 'Content-Length': size,\n 'Accept-Ranges': 'bytes',\n });\n pipeOrFail(createReadStream(filePath), res);\n } catch (err) {\n sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\n/**\n * Pipe a read stream to the response, surviving a failure mid-stream.\n *\n * The previous implementation read the whole file synchronously inside a\n * `try`, so any read error became a 500. Streaming moves the failure to an\n * async `'error'` event, and an unhandled one on a stream is rethrown by Node\n * as an uncaught exception — which kills the `serve` process and every other\n * tab viewing the report. It is reachable: a video whose file is deleted by the\n * run-delete endpoint mid-request, or a `.webm` still locked by another\n * process on Windows.\n *\n * Headers are already sent by this point, so there is no status left to send;\n * the honest outcome is to abort the response and keep the server up.\n */\nfunction pipeOrFail(stream: ReturnType<typeof createReadStream>, res: ServerResponse): void {\n stream.on('error', () => {\n stream.destroy();\n res.destroy();\n });\n res.on('close', () => { stream.destroy(); });\n stream.pipe(res);\n}\n\n/**\n * Parse a single-range `Range` header against a known file size.\n *\n * Returns null when there is nothing to honour (absent, or a form we do not\n * serve such as a multi-range request), and 'unsatisfiable' when the range\n * falls outside the file so the caller can answer 416.\n */\nexport function parseByteRange(\n header: string | undefined,\n size: number,\n): { start: number; end: number } | 'unsatisfiable' | null {\n if (!header) return null;\n const match = /^bytes=(\\d*)-(\\d*)$/.exec(header.trim());\n if (!match) return null;\n\n const [, rawStart, rawEnd] = match;\n if (rawStart === '' && rawEnd === '') return null;\n\n let start: number;\n let end: number;\n if (rawStart === '') {\n // A suffix range: the last N bytes.\n const suffix = Number(rawEnd);\n if (suffix <= 0) return 'unsatisfiable';\n start = Math.max(0, size - suffix);\n end = size - 1;\n } else {\n start = Number(rawStart);\n end = rawEnd === '' ? size - 1 : Math.min(Number(rawEnd), size - 1);\n }\n\n if (!Number.isFinite(start) || !Number.isFinite(end)) return null;\n if (size === 0 || start >= size || start > end) return 'unsatisfiable';\n return { start, end };\n}\n\nconst MIME_TYPES: Record<string, string> = {\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n '.webm': 'video/webm',\n '.mp4': 'video/mp4',\n '.json': 'application/json',\n '.html': 'text/html',\n '.css': 'text/css',\n '.js': 'text/javascript',\n '.svg': 'image/svg+xml',\n};\n","/**\n * ReportServer — unified HTTP server for serving streaming reports and artifacts.\n *\n * Binds to 127.0.0.1 only. Auto-selects a port starting at 9323.\n * 30-minute inactivity timeout with auto-shutdown.\n */\n\nimport { createServer, type Server, type IncomingMessage, type ServerResponse } from 'node:http';\nimport { join, dirname } from 'node:path';\nimport { existsSync, readdirSync } from 'node:fs';\nimport {\n setCorsHeaders,\n sendJson,\n handleHealth,\n handleSummary,\n handleTests,\n handleTestDetail,\n handleTestDataFile,\n handleFiles,\n handleArtifactsList,\n handleArtifactsDeleteAll,\n handleArtifactDelete,\n handleShutdown,\n serveStaticFile,\n} from './report-server-routes.js';\n\nconst DEFAULT_PORT = 9323;\nconst MAX_PORT_ATTEMPTS = 10;\nconst INACTIVITY_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes\n\nexport interface ReportServerOptions {\n port?: number;\n htmlPath?: string;\n}\n\nexport interface ReportServerHandle {\n port: number;\n dispose: () => Promise<void>;\n}\n\nexport function startReportServer(\n reportDir: string,\n options?: ReportServerOptions,\n): Promise<ReportServerHandle> {\n return new Promise((resolve, reject) => {\n const startPort = options?.port ?? DEFAULT_PORT;\n const startTime = Date.now();\n let inactivityTimer: ReturnType<typeof setTimeout>;\n let currentAttempt = 0;\n\n // Determine artifacts directory — check both locations\n const artifactsDir = existsSync(join(reportDir, 'artifacts'))\n ? join(reportDir, 'artifacts')\n : existsSync(join(reportDir, '..', 'artifacts'))\n ? join(reportDir, '..', 'artifacts')\n : join(reportDir, 'artifacts');\n\n function resetTimer(): void {\n clearTimeout(inactivityTimer);\n inactivityTimer = setTimeout(() => {\n server.close();\n }, INACTIVITY_TIMEOUT_MS);\n }\n\n // Resolve the HTML report path — explicit option, or find it in the parent dir\n let htmlReportPath = options?.htmlPath ?? null;\n if (!htmlReportPath) {\n const parentDir = dirname(reportDir);\n try {\n const htmlFile = readdirSync(parentDir).find(f => f.endsWith('.html'));\n if (htmlFile) htmlReportPath = join(parentDir, htmlFile);\n } catch { /* ignore */ }\n }\n\n const server: Server = createServer((req: IncomingMessage, res: ServerResponse) => {\n resetTimer();\n setCorsHeaders(res);\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n let pathname: string;\n try {\n const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);\n pathname = url.pathname;\n } catch {\n sendJson(res, 400, { error: 'Invalid URL' });\n return;\n }\n\n // --- Serve HTML report at root ---\n if (req.method === 'GET' && (pathname === '/' || pathname === '/index.html')) {\n if (htmlReportPath && existsSync(htmlReportPath)) {\n serveStaticFile(req, res, htmlReportPath);\n return;\n }\n sendJson(res, 404, { error: 'HTML report not found' });\n return;\n }\n\n // --- Report API routes ---\n if (req.method === 'GET' && pathname === '/api/health') {\n handleHealth(req, res, reportDir, startTime);\n return;\n }\n if (req.method === 'GET' && pathname === '/api/summary') {\n handleSummary(req, res, reportDir);\n return;\n }\n if (req.method === 'GET' && pathname === '/api/tests') {\n handleTests(req, res, reportDir);\n return;\n }\n if (req.method === 'GET' && pathname === '/api/files') {\n handleFiles(req, res, reportDir);\n return;\n }\n\n // GET /api/tests/:id/network, /api/tests/:id/console, /api/tests/:id/api-calls\n const testDataMatch = pathname.match(/^\\/api\\/tests\\/([a-f0-9]+)\\/(network|console|api-calls)$/);\n if (req.method === 'GET' && testDataMatch) {\n handleTestDataFile(req, res, reportDir, testDataMatch[1], testDataMatch[2] as 'network' | 'console' | 'api-calls');\n return;\n }\n\n // GET /api/tests/:id\n const testMatch = pathname.match(/^\\/api\\/tests\\/([a-f0-9]+)$/);\n if (req.method === 'GET' && testMatch) {\n handleTestDetail(req, res, reportDir, testMatch[1]);\n return;\n }\n\n // --- Artifact routes ---\n if (req.method === 'GET' && pathname === '/api/artifacts') {\n handleArtifactsList(req, res, artifactsDir);\n return;\n }\n if (req.method === 'DELETE' && pathname === '/api/artifacts') {\n handleArtifactsDeleteAll(req, res, artifactsDir);\n return;\n }\n const deleteMatch = pathname.match(/^\\/api\\/artifacts\\/(.+)$/);\n if (req.method === 'DELETE' && deleteMatch) {\n handleArtifactDelete(req, res, artifactsDir, decodeURIComponent(deleteMatch[1]));\n return;\n }\n\n // --- Static artifact files ---\n if (req.method === 'GET' && pathname.startsWith('/artifacts/')) {\n const relPath = decodeURIComponent(pathname.slice('/artifacts/'.length));\n // Path traversal protection\n if (relPath.includes('..') || relPath.includes('\\0')) {\n sendJson(res, 400, { error: 'Invalid path' });\n return;\n }\n serveStaticFile(req, res, join(artifactsDir, relPath));\n return;\n }\n\n // --- Shutdown ---\n if (req.method === 'POST' && pathname === '/api/shutdown') {\n handleShutdown(req, res, server);\n return;\n }\n\n sendJson(res, 404, { error: 'Not found' });\n });\n\n function tryListen(port: number): void {\n const errorHandler = (err: NodeJS.ErrnoException): void => {\n if (err.code === 'EADDRINUSE' && currentAttempt < MAX_PORT_ATTEMPTS) {\n currentAttempt++;\n tryListen(port + 1);\n } else {\n reject(err);\n }\n };\n server.once('error', errorHandler);\n\n server.listen(port, '127.0.0.1', () => {\n // Remove the listen-phase error handler to avoid stale handlers\n server.removeListener('error', errorHandler);\n const addr = server.address();\n if (!addr || typeof addr === 'string') {\n reject(new Error('Failed to get server address'));\n return;\n }\n resetTimer();\n resolve({\n port: addr.port,\n dispose: () => new Promise<void>((res) => {\n clearTimeout(inactivityTimer);\n server.close(() => res());\n }),\n });\n });\n }\n\n tryListen(startPort);\n });\n}\n","/**\n * ArtifactServer — DEPRECATED.\n *\n * Artifact serving is now handled by the unified report server (report-server.ts).\n * This file is kept for backward compatibility only.\n *\n * @deprecated Use `startReportServer()` from `./report-server.js` instead.\n */\n\nimport { startReportServer } from './report-server.js';\nimport type { ReportServerHandle } from './report-server.js';\n\ninterface ArtifactServerHandle {\n port: number;\n dispose: () => Promise<void>;\n}\n\n/**\n * @deprecated Use `startReportServer()` from `./report-server.js` instead.\n */\nexport async function startArtifactServer(artifactsDir: string): Promise<ArtifactServerHandle> {\n const handle: ReportServerHandle = await startReportServer(artifactsDir);\n return { port: handle.port, dispose: handle.dispose };\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, existsSync, readFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { spawn } from 'node:child_process';\nimport type { TestRunReport, ArtifactRunManifest } from '@testrelic/core';\nimport type { ResolvedConfig } from './config.js';\nimport { renderHtmlDocument, renderStreamingHtmlDocument } from './html-template.js';\nimport { openInBrowser } from './browser-open.js';\nimport { startArtifactServer } from './artifact-server.js';\nimport { startReportServer } from './report-server.js';\nimport type { ReportServerHandle } from './report-server.js';\n\n// ---------------------------------------------------------------------------\n// Utility Functions\n// ---------------------------------------------------------------------------\n\nconst HTML_ESCAPE_MAP: Record<string, string> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#39;',\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, serverPort?: number | null, manifest?: ArtifactRunManifest | null): string {\n const reportJson = JSON.stringify(report);\n const manifestJson = manifest ? JSON.stringify(manifest) : null;\n return renderHtmlDocument(reportJson, serverPort, manifestJson);\n}\n\nexport async function writeHtmlReport(report: TestRunReport, config: ResolvedConfig, manifest?: ArtifactRunManifest | null): Promise<void> {\n try {\n let serverPort: number | null = null;\n let serverHandle: ReportServerHandle | null = null;\n const isStreamingMode = config.reportMode === 'streaming' || (\n config.reportMode === 'auto' && report.timeline.length === 0 && report.summary.total >= config.streamingThreshold\n );\n\n // Step 1: Write HTML first (server port not needed — JS auto-detects via window.location)\n let html: string;\n const outputPath = config.htmlReportPath;\n const htmlDir = dirname(outputPath);\n mkdirSync(htmlDir, { recursive: true });\n\n let summaryJson = '';\n let indexJson = '[]';\n let manifestJson: string | null = null;\n\n if (isStreamingMode) {\n // The streaming HTML template reads the run-header fields (run id, dates,\n // duration, ci, metadata, shard ids) off the summary object it receives.\n // report.summary only carries the pass/fail counts, so enrich it with the\n // report's top-level run metadata — otherwise the header renders\n // \"Run —\", \"Invalid Date\", and \"Duration 0ms\".\n summaryJson = JSON.stringify({\n ...report.summary,\n testRunId: report.testRunId,\n startedAt: report.startedAt,\n completedAt: report.completedAt,\n totalDuration: report.totalDuration,\n ci: report.ci,\n metadata: report.metadata,\n shardRunIds: report.shardRunIds,\n });\n const outputDir = dirname(config.outputPath);\n const reportDir = join(outputDir, '.testrelic-report');\n try {\n // Prefer pre-built compact index (avoids expensive parse-reserialize)\n const compactPath = join(reportDir, 'index-compact.json');\n const fullPath = join(reportDir, 'index.json');\n if (existsSync(compactPath)) {\n indexJson = readFileSync(compactPath, 'utf-8');\n } else if (existsSync(fullPath)) {\n indexJson = readFileSync(fullPath, 'utf-8');\n }\n } catch { /* use empty */ }\n manifestJson = manifest ? JSON.stringify(manifest) : null;\n html = renderStreamingHtmlDocument(summaryJson, indexJson, null, manifestJson);\n } else {\n html = generateHtmlReport(report, null, manifest);\n }\n\n const tmpPath = outputPath + '.tmp';\n writeFileSync(tmpPath, html, 'utf-8');\n renameSync(tmpPath, outputPath);\n\n // Step 2: Start server\n if (isStreamingMode) {\n // Streaming mode: start a report server (needed for test detail fetching)\n if (report.ci === null) {\n const outputDir = dirname(config.outputPath);\n const reportDir = join(outputDir, '.testrelic-report');\n const htmlPath = resolve(outputPath);\n try {\n // Shut down any stale servers first to avoid port conflicts\n await shutdownExistingServers();\n\n // Try spawning a detached server (survives after Playwright exits)\n serverPort = await spawnDetachedServer(reportDir);\n\n // Fallback: start in-process server if detached spawn failed\n if (!serverPort) {\n serverHandle = await startReportServer(reportDir, { htmlPath });\n serverPort = serverHandle.port;\n // Keep Node alive so the server doesn't die with Playwright\n const keepAlive = setInterval(() => {}, 60_000);\n const cleanup = (): void => {\n clearInterval(keepAlive);\n serverHandle?.dispose();\n };\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n // Auto-shutdown after 30 minutes\n setTimeout(cleanup, 30 * 60 * 1000).unref();\n }\n\n if (serverPort) {\n process.stderr.write(\n `\\n Report server: http://127.0.0.1:${serverPort}\\n`,\n );\n // Rewrite HTML with embedded server port so file:// opens can still connect\n try {\n const updatedHtml = renderStreamingHtmlDocument(summaryJson, indexJson, serverPort, manifestJson);\n const tmpRewrite = outputPath + '.tmp';\n writeFileSync(tmpRewrite, updatedHtml, 'utf-8');\n renameSync(tmpRewrite, outputPath);\n } catch { /* non-fatal */ }\n }\n } catch {\n // Server start failure is non-fatal\n }\n }\n } else {\n // Embedded mode: start an artifact server if applicable.\n //\n // NOTE: nothing in the page talks to this server. The HTML is rendered\n // above with serverPort null and never re-rendered (streaming rewrites\n // itself once the port is known; this branch does not), so the\n // `artifact-server-port` meta tag is never emitted — and no client\n // module reads that tag or rewrites an artifact URL in any case.\n // Artifacts load as relative file:// paths, which is why the CSP has to\n // permit the file: scheme. Left running because a user can still fetch\n // artifacts from it by hand; it is a candidate for removal.\n if (config.openReport && report.ci === null && config.includeArtifacts) {\n const outputDir = dirname(config.outputPath);\n const artifactsDir = join(outputDir, 'artifacts');\n if (existsSync(artifactsDir)) {\n try {\n const server = await startArtifactServer(artifactsDir);\n serverPort = server.port;\n process.on('exit', () => { server.dispose(); });\n } catch {\n // Server start failure is non-fatal\n }\n }\n }\n }\n\n // Step 3: Auto-open in browser if configured and not in CI\n if (config.openReport && report.ci === null) {\n if (isStreamingMode && serverPort) {\n openInBrowser(`http://127.0.0.1:${serverPort}`);\n } else {\n const absolutePath = resolve(outputPath);\n openInBrowser(absolutePath);\n }\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// ---------------------------------------------------------------------------\n// Detached Server Helpers\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_PORT = 9323;\nconst MAX_PORT_ATTEMPTS = 10;\n\n/** Check if a report server is already running on the default port range. */\nasync function detectExistingServer(): Promise<number | null> {\n for (let port = DEFAULT_PORT; port < DEFAULT_PORT + MAX_PORT_ATTEMPTS; port++) {\n try {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 500);\n const resp = await fetch(`http://127.0.0.1:${port}/api/health`, { signal: controller.signal });\n clearTimeout(timer);\n if (resp.ok) return port;\n } catch {\n // Not running on this port\n }\n }\n return null;\n}\n\n/** Shut down any existing report servers on the default port range. */\nasync function shutdownExistingServers(): Promise<void> {\n for (let port = DEFAULT_PORT; port < DEFAULT_PORT + MAX_PORT_ATTEMPTS; port++) {\n try {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 1000);\n await fetch(`http://127.0.0.1:${port}/api/shutdown`, { method: 'POST', signal: controller.signal });\n clearTimeout(timer);\n } catch {\n // Not running on this port — skip\n }\n }\n // Brief pause to let ports release\n await new Promise(r => setTimeout(r, 300));\n}\n\n/** Spawn a detached report server process that survives after Playwright exits. */\nasync function spawnDetachedServer(reportDir: string): Promise<number | null> {\n // Find the CLI entry point — try multiple locations\n const candidates = [\n join(__dirname, 'cli.cjs'),\n join(__dirname, 'cli.js'),\n join(__dirname, '..', 'dist', 'cli.cjs'),\n join(__dirname, '..', 'dist', 'cli.js'),\n ];\n const cliPath = candidates.find(p => existsSync(p));\n if (!cliPath) {\n // Fallback: run in-process server (will die with Playwright)\n const handle = await startReportServer(reportDir);\n process.on('exit', () => { handle?.dispose(); });\n return handle.port;\n }\n\n return new Promise<number | null>((resolvePort) => {\n const child = spawn(process.execPath, [cliPath, 'serve', reportDir], {\n detached: true,\n stdio: ['ignore', 'ignore', 'pipe'],\n env: { ...process.env },\n });\n\n let stderr = '';\n let resolved = false;\n const timeout = setTimeout(() => {\n if (!resolved) {\n resolved = true;\n child.stderr?.removeAllListeners();\n // Server didn't announce its port in time — try detecting it\n detectExistingServer().then(resolvePort);\n }\n }, 5000);\n\n child.stderr?.on('data', (chunk: Buffer) => {\n stderr += chunk.toString();\n // Look for \"Report server running at: http://127.0.0.1:XXXX\"\n const match = stderr.match(/127\\.0\\.0\\.1:(\\d+)/);\n if (match && !resolved) {\n resolved = true;\n clearTimeout(timeout);\n child.stderr?.removeAllListeners();\n child.unref();\n resolvePort(Number(match[1]));\n }\n });\n\n child.on('error', () => {\n if (!resolved) {\n resolved = true;\n clearTimeout(timeout);\n resolvePort(null);\n }\n });\n\n child.on('exit', () => {\n if (!resolved) {\n resolved = true;\n clearTimeout(timeout);\n resolvePort(null);\n }\n });\n\n // Detach the child so it survives after Playwright exits\n child.unref();\n });\n}\n","/**\n * Numbers for a visual comparison, recovered without decoding an image.\n *\n * Playwright compares images internally and keeps the result to itself: the\n * differing-pixel count exists only as prose inside the failure message, and\n * the image dimensions only inside the PNG. Rather than decode a PNG (which\n * would mean a dependency, and Principle V says no), both are read back — the\n * count from the message Playwright already formats, the dimensions from the\n * 8 bytes of header every PNG carries.\n */\n\n/** PNG magic number: \\x89 P N G \\r \\n \\x1a \\n */\nconst PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);\n\n/** Byte offsets of width and height inside the IHDR chunk of a PNG. */\nconst IHDR_WIDTH_OFFSET = 16;\nconst IHDR_HEIGHT_OFFSET = 20;\n\n/** Enough bytes to cover the signature, the chunk header and both dimensions. */\nexport const PNG_HEADER_BYTES = 24;\n\nexport interface ImageDimensions {\n readonly width: number;\n readonly height: number;\n}\n\n/**\n * Read a PNG's pixel dimensions from its IHDR chunk.\n *\n * Only the first 24 bytes are consulted, so the caller can hand over a partial\n * read of a large file. Returns `null` for anything that is not a PNG — JPEG\n * baselines are legal in Playwright and simply go un-measured.\n */\nexport function readPngDimensions(header: Buffer): ImageDimensions | null {\n if (header.length < PNG_HEADER_BYTES) return null;\n if (!header.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)) return null;\n\n const width = header.readUInt32BE(IHDR_WIDTH_OFFSET);\n const height = header.readUInt32BE(IHDR_HEIGHT_OFFSET);\n\n // A zero dimension is not a valid PNG, and would make a ratio divide by zero.\n if (width === 0 || height === 0) return null;\n\n return { width, height };\n}\n\nexport interface ParsedDiffMessage {\n /** Differing pixel count, from `\"N pixels (ratio …) are different.\"` */\n readonly diffPixels?: number;\n /** Baseline dimensions, present only when the two images differ in size. */\n readonly expectedWidth?: number;\n readonly expectedHeight?: number;\n /** Actual dimensions, present only when the two images differ in size. */\n readonly actualWidth?: number;\n readonly actualHeight?: number;\n /** The snapshot name Playwright echoed, when the assertion supplied one. */\n readonly snapshotName?: string;\n}\n\n/**\n * `123 pixels (ratio 0.02 of all image pixels) are different.`\n *\n * The ratio in that sentence is rounded up to two decimals, so it is read past\n * deliberately — the caller recomputes it from the count and the dimensions.\n */\nconst PIXELS_DIFFERENT_RE = /(\\d+) pixels \\(ratio [\\d.]+ of all image pixels\\) are different/;\n\n/** `Expected an image 800px by 600px, received 800px by 640px.` */\nconst SIZE_MISMATCH_RE = /Expected an image (\\d+)px by (\\d+)px, received (\\d+)px by (\\d+)px/;\n\n/**\n * Whether a failure message is a size mismatch.\n *\n * The comparator cannot draw a diff across differing dimensions, so it\n * attaches expected + actual and throws instead. Callers that would otherwise\n * infer \"no baseline existed\" from a missing diff need this to tell the two\n * apart — without it a real failure was reported as a freshly written\n * baseline.\n */\nexport function isSizeMismatchFailure(message: string | null | undefined): boolean {\n if (!message) return false;\n return SIZE_MISMATCH_RE.test(message);\n}\n\n/** ` Snapshot: home.png` — only emitted when the assertion named the snapshot. */\nconst SNAPSHOT_NAME_RE = /^\\s*Snapshot:\\s*(.+?)\\s*$/m;\n\n/**\n * Pull the comparison numbers out of a Playwright snapshot failure message.\n *\n * Every field is optional and a malformed message yields an empty object: a\n * report that is missing a pixel count is a far better outcome than a reporter\n * that throws while assembling one.\n */\nexport function parseDiffMessage(message: string | null | undefined): ParsedDiffMessage {\n if (!message) return {};\n\n const result: {\n diffPixels?: number;\n expectedWidth?: number;\n expectedHeight?: number;\n actualWidth?: number;\n actualHeight?: number;\n snapshotName?: string;\n } = {};\n\n const pixels = PIXELS_DIFFERENT_RE.exec(message);\n if (pixels) {\n const count = Number.parseInt(pixels[1] as string, 10);\n if (Number.isFinite(count)) result.diffPixels = count;\n }\n\n const sizes = SIZE_MISMATCH_RE.exec(message);\n if (sizes) {\n const [, ew, eh, aw, ah] = sizes;\n const parsed = [ew, eh, aw, ah].map((v) => Number.parseInt(v as string, 10));\n if (parsed.every((v) => Number.isFinite(v))) {\n result.expectedWidth = parsed[0];\n result.expectedHeight = parsed[1];\n result.actualWidth = parsed[2];\n result.actualHeight = parsed[3];\n }\n }\n\n const name = SNAPSHOT_NAME_RE.exec(message);\n if (name) result.snapshotName = name[1];\n\n return result;\n}\n\n/**\n * Differing pixels as a fraction of the image, at full precision.\n *\n * Returns `undefined` rather than a wrong number when either input is missing,\n * so a consumer can tell \"no measurement\" apart from \"measured zero\".\n */\nexport function computeDiffRatio(\n diffPixels: number | undefined,\n dimensions: ImageDimensions | null | undefined,\n): number | undefined {\n if (diffPixels === undefined || !dimensions) return undefined;\n const total = dimensions.width * dimensions.height;\n if (total <= 0) return undefined;\n return diffPixels / total;\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 * Uses async copies with a concurrency limiter to avoid blocking the\n * event loop and exhausting file descriptors on large suites.\n */\n\nimport { mkdirSync, existsSync, openSync, readSync, closeSync } from 'node:fs';\nimport { copyFile } from 'node:fs/promises';\nimport { join, extname } from 'node:path';\nimport { createHash } from 'node:crypto';\nimport type { TestArtifacts, VisualComparison } from '@testrelic/core';\nimport type { VisualCandidate } from './visual-capture.js';\nimport { readPngDimensions, computeDiffRatio, PNG_HEADER_BYTES } from './visual-metrics.js';\n\ninterface Attachment {\n name: string;\n contentType: string;\n path?: string;\n body?: Buffer;\n}\n\n// ---------------------------------------------------------------------------\n// Async copy queue with concurrency limiter\n// ---------------------------------------------------------------------------\n\nconst COPY_CONCURRENCY = 20;\nlet activeCopies = 0;\nconst pendingCopies: Array<() => void> = [];\n\n/** Queue a file copy with bounded concurrency to prevent fd exhaustion. */\nasync function enqueueCopy(src: string, dest: string): Promise<boolean> {\n if (activeCopies >= COPY_CONCURRENCY) {\n await new Promise<void>(resolve => pendingCopies.push(resolve));\n }\n activeCopies++;\n try {\n await copyFile(src, dest);\n return true;\n } catch {\n return false;\n } finally {\n activeCopies--;\n if (pendingCopies.length > 0) pendingCopies.shift()!();\n }\n}\n\n// Tracks all in-flight copy promises so onEnd can await them\nconst inflightCopies: Promise<void>[] = [];\n\n/** Wait for all queued artifact copies to complete. Call from reporter onEnd. */\nexport async function flushPendingArtifactCopies(): Promise<void> {\n if (inflightCopies.length === 0) return;\n await Promise.allSettled(inflightCopies);\n inflightCopies.length = 0;\n}\n\n// ---------------------------------------------------------------------------\n// Timestamp folder naming\n// ---------------------------------------------------------------------------\n\n/**\n * Generate a timestamp-based folder name for the current test run.\n * Format: YYYY-MM-DDTHH-mm-ss (filesystem-safe, sorts chronologically).\n *\n * If the target directory already exists (sub-second collision),\n * appends a numeric suffix: `-1`, `-2`, etc.\n */\nexport function generateTimestampFolderName(artifactsDir: string): string {\n const now = new Date();\n const pad = (n: number): string => String(n).padStart(2, '0');\n const base = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}T${pad(now.getHours())}-${pad(now.getMinutes())}-${pad(now.getSeconds())}`;\n\n if (!existsSync(join(artifactsDir, base))) {\n return base;\n }\n\n // Collision: append numeric suffix\n let suffix = 1;\n while (existsSync(join(artifactsDir, `${base}-${suffix}`))) {\n suffix++;\n }\n return `${base}-${suffix}`;\n}\n\n// ---------------------------------------------------------------------------\n// Folder name sanitization\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/** Characters of the testId kept as the folder-name disambiguator. */\nconst ID_SUFFIX_LENGTH = 8;\n\n/**\n * Build the per-test artifact folder name.\n *\n * The sanitized leaf title alone is NOT unique: the same test title can appear\n * in several spec files, in several `describe` blocks, and once per Playwright\n * project (chromium/firefox/webkit). All of those would sanitize to the same\n * folder and race each other writing `video.webm` / `screenshot.png`, leaving\n * every colliding test pointing at one file — so distinct testIds ended up\n * uploading byte-identical artifacts.\n *\n * `testId` (the reporter's stable sha256 of filePath::suiteName::fullTitle)\n * disambiguates them. It is appended as a short suffix so the folder stays\n * human-readable while being unique per test.\n *\n * @param testTitle Test title used for the readable part of the folder name.\n * @param retryCount Playwright retry index (0 for the first attempt).\n * @param testId Stable per-test identifier. When omitted the legacy\n * title-only naming is used (kept for callers that have no id).\n */\nexport function buildArtifactFolderName(\n testTitle: string,\n retryCount: number,\n testId?: string,\n): string {\n let folderName = sanitizeFolderName(testTitle);\n\n const suffix = testId ? sanitizeFolderName(testId).substring(0, ID_SUFFIX_LENGTH) : '';\n if (suffix) {\n folderName += `--${suffix}`;\n }\n\n if (retryCount > 0) {\n folderName += `--retry-${retryCount}`;\n }\n\n return folderName;\n}\n\n// ---------------------------------------------------------------------------\n// Artifact copy (non-blocking)\n// ---------------------------------------------------------------------------\n\n/**\n * Queue artifact copies from Playwright's temp locations to structured output folders.\n * Returns paths immediately (optimistic) — copies happen asynchronously.\n * Call `flushPendingArtifactCopies()` in onEnd to ensure all copies complete.\n *\n * @param testId Stable per-test id used to disambiguate the destination folder\n * when two tests share a leaf title (different spec file,\n * different describe block, or different Playwright project).\n * Without it those tests collide on one folder and overwrite each\n * other's `video.webm` / `screenshot.png`.\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 runTimestamp?: string,\n testId?: 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 const trace = attachments.find(\n (a) => a.name === 'trace' && a.path,\n );\n\n if (!screenshot && !video && !trace) {\n return null;\n }\n\n const folderName = buildArtifactFolderName(testTitle, retryCount, testId);\n\n // When runTimestamp is provided, nest under timestamp folder:\n // artifacts/<runTimestamp>/<folderName>/\n // Otherwise fall back to legacy flat structure:\n // artifacts/<folderName>/\n const artifactSegments = runTimestamp\n ? ['artifacts', runTimestamp, folderName]\n : ['artifacts', folderName];\n const artifactDir = join(outputDir, ...artifactSegments);\n const relativeParts = artifactSegments;\n const result: { screenshot?: string; video?: string; trace?: 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 if (existsSync(screenshot.path)) {\n const ext = extname(screenshot.path) || '.png';\n const destName = `screenshot${ext}`;\n const dest = join(artifactDir, destName);\n // Fire-and-forget async copy; tracked for flush in onEnd\n inflightCopies.push(enqueueCopy(screenshot.path, dest).then(() => {}));\n result.screenshot = `${relativeParts.join('/')}/${destName}`;\n }\n }\n\n if (video?.path) {\n if (existsSync(video.path)) {\n const ext = extname(video.path) || '.webm';\n const destName = `video${ext}`;\n const dest = join(artifactDir, destName);\n inflightCopies.push(enqueueCopy(video.path, dest).then(() => {}));\n result.video = `${relativeParts.join('/')}/${destName}`;\n }\n }\n\n if (trace?.path) {\n if (existsSync(trace.path)) {\n // Playwright always writes `trace.zip`; keep the extension it gave us\n // rather than assuming, the way the two above do.\n const ext = extname(trace.path) || '.zip';\n const destName = `trace${ext}`;\n const dest = join(artifactDir, destName);\n inflightCopies.push(enqueueCopy(trace.path, dest).then(() => {}));\n result.trace = `${relativeParts.join('/')}/${destName}`;\n }\n }\n\n if (!result.screenshot && !result.video && !result.trace) {\n return null;\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Visual baseline artifacts\n// ---------------------------------------------------------------------------\n\n/** Subdirectory holding every visual comparison for one test attempt. */\nconst VISUAL_DIR = 'visual';\n\n/** Characters of the stem hash appended to keep sanitized names unique. */\nconst STEM_HASH_LENGTH = 6;\n\n/**\n * Directory name for one snapshot inside a test's `visual/` folder.\n *\n * The snapshot name comes from test code and can contain path separators\n * (`toHaveScreenshot(['checkout', 'cart.png'])` is legal), so it is flattened\n * to a single segment rather than nested — that makes directory traversal\n * structurally impossible instead of merely guarded against. Flattening can\n * collide two different snapshots onto one name, so a short digest of the\n * original stem is appended to keep them apart.\n */\nexport function buildVisualFolderName(stem: string): string {\n const hash = createHash('sha256').update(stem).digest('hex').substring(0, STEM_HASH_LENGTH);\n return `${sanitizeFolderName(stem)}--${hash}`;\n}\n\n/** Read a PNG's dimensions without loading the whole file. */\nfunction readDimensions(filePath: string): { width: number; height: number } | null {\n let fd: number | null = null;\n try {\n fd = openSync(filePath, 'r');\n const header = Buffer.alloc(PNG_HEADER_BYTES);\n const bytesRead = readSync(fd, header, 0, PNG_HEADER_BYTES, 0);\n return readPngDimensions(header.subarray(0, bytesRead));\n } catch {\n return null;\n } finally {\n if (fd !== null) {\n try {\n closeSync(fd);\n } catch {\n // Nothing useful to do if the descriptor will not close.\n }\n }\n }\n}\n\n/** The four images a comparison can carry, and what each is called on disk. */\nconst VISUAL_ROLES = [\n { key: 'expectedPath', dest: 'baseline', field: 'baseline' },\n { key: 'actualPath', dest: 'actual', field: 'actual' },\n { key: 'diffPath', dest: 'diff', field: 'diff' },\n { key: 'previousPath', dest: 'previous', field: 'previous' },\n] as const;\n\n/** The DOM snapshots, which live in the same folder as the images they explain. */\nconst DOM_ROLES = [\n { key: 'baselineDomPath', dest: 'baseline.dom.json', field: 'baselineDom' },\n { key: 'actualDomPath', dest: 'actual.dom.json', field: 'actualDom' },\n] as const;\n\n/**\n * Queue copies for every image of one comparison and return its report paths.\n *\n * Mirrors `copyArtifacts`: paths come back immediately and the copies drain\n * through the same bounded queue, flushed by `flushPendingArtifactCopies()`.\n */\nfunction copyOneComparison(\n candidate: VisualCandidate,\n visualDir: string,\n relativePrefix: string,\n): VisualComparison | null {\n const folder = buildVisualFolderName(candidate.name);\n const destDir = join(visualDir, folder);\n\n try {\n mkdirSync(destDir, { recursive: true });\n } catch {\n return null;\n }\n\n const paths: Record<string, string> = {};\n let copiedAny = false;\n\n for (const role of VISUAL_ROLES) {\n const src = candidate[role.key];\n if (!src || !existsSync(src)) continue;\n\n const ext = extname(src) || '.png';\n const destName = `${role.dest}${ext}`;\n inflightCopies.push(enqueueCopy(src, join(destDir, destName)).then(() => {}));\n paths[role.field] = `${relativePrefix}/${VISUAL_DIR}/${folder}/${destName}`;\n copiedAny = true;\n }\n\n // DOM snapshots are optional: present only where the assertion was written\n // with the SDK's own `expect` and a baseline already existed to compare to.\n const dom = candidate.dom;\n if (dom) {\n for (const role of DOM_ROLES) {\n const src = dom[role.key];\n if (!src || !existsSync(src)) continue;\n inflightCopies.push(enqueueCopy(src, join(destDir, role.dest)).then(() => {}));\n paths[role.field] = `${relativePrefix}/${VISUAL_DIR}/${folder}/${role.dest}`;\n }\n }\n\n if (!copiedAny) return null;\n\n // Measured from the baseline where there is one: the ratio has to be a\n // fraction of the image the comparison was judged against, and on a size\n // mismatch the actual is a different size entirely.\n const measured = candidate.expectedPath\n ? readDimensions(candidate.expectedPath)\n : candidate.actualPath\n ? readDimensions(candidate.actualPath)\n : null;\n const ratio = computeDiffRatio(candidate.diffPixels, measured);\n\n return {\n name: candidate.name,\n status: candidate.status,\n source: candidate.source,\n ...paths,\n ...(measured ? { width: measured.width, height: measured.height } : {}),\n ...(candidate.actualWidth !== undefined ? { actualWidth: candidate.actualWidth } : {}),\n ...(candidate.actualHeight !== undefined ? { actualHeight: candidate.actualHeight } : {}),\n ...(candidate.diffPixels !== undefined ? { diffPixels: candidate.diffPixels } : {}),\n ...(ratio !== undefined ? { diffPixelRatio: ratio } : {}),\n ...(dom\n ? {\n domChanges: dom.changes,\n domChangeCount: dom.total,\n domRootCauses: dom.rootCauses,\n domTruncated: dom.truncated,\n domCaptureBox: dom.captureBox,\n }\n : {}),\n };\n}\n\n/**\n * Copy every visual comparison's images into the test's artifact folder.\n *\n * Lands alongside `screenshot.png` / `video.webm` rather than in a tree of its\n * own so that one test attempt stays one directory — the artifact manifest,\n * the report server's `/artifacts/*` route and the run-delete endpoint all\n * keep working unchanged.\n *\n * @returns One entry per comparison whose images were found, or null if none.\n */\nexport function copyVisualArtifacts(\n candidates: readonly VisualCandidate[],\n testTitle: string,\n retryCount: number,\n outputDir: string,\n runTimestamp?: string,\n testId?: string,\n): VisualComparison[] | null {\n if (candidates.length === 0) return null;\n\n const folderName = buildArtifactFolderName(testTitle, retryCount, testId);\n const artifactSegments = runTimestamp\n ? ['artifacts', runTimestamp, folderName]\n : ['artifacts', folderName];\n const visualDir = join(outputDir, ...artifactSegments, VISUAL_DIR);\n const relativePrefix = artifactSegments.join('/');\n\n try {\n mkdirSync(visualDir, { recursive: true });\n } catch {\n return null;\n }\n\n const results: VisualComparison[] = [];\n for (const candidate of candidates) {\n const copied = copyOneComparison(candidate, visualDir, relativePrefix);\n if (copied) results.push(copied);\n }\n\n return results.length > 0 ? results : null;\n}\n","/**\n * The `expect` layer that attaches DOM comparison to a visual assertion.\n *\n * Wraps `toHaveScreenshot` and `toMatchVisualBaseline` so that both capture a\n * DOM snapshot, compare it against a committed baseline, and record which\n * elements differ. The pixel comparison is untouched — this runs either side of\n * it and never changes its verdict.\n *\n * Composed as a layer rather than folded into `assertion-tracker.ts`: that\n * module records API assertions, and giving it visual concerns too would give\n * it two jobs. It also returns early for anything without an API call id, which\n * is exactly why `expect(page).toHaveScreenshot()` reaches raw Playwright today.\n */\n\nimport { basename, extname } from 'node:path';\nimport { writeFileSync } from 'node:fs';\nimport { existsSync } from 'node:fs';\nimport type { DomChange } from '@testrelic/core';\nimport {\n captureDom,\n domBaselinePath,\n readDomBaseline,\n writeDomBaseline,\n serializeSnapshot,\n type BoundedTarget,\n type EvaluateTarget,\n} from './dom-snapshot.js';\nimport { diffDomSnapshots } from './dom-diff.js';\nimport { supportsSnapshotPathOptions, warnSnapshotPathUnsupported } from './snapshot-path-support.js';\nimport type { DomSnapshot } from './dom-capture.js';\n\n/** Attachment carrying the DOM comparison from the worker to the reporter. */\nexport const VISUAL_DOM_ATTACHMENT = 'testrelic-visual-dom';\n\n/** One comparison's DOM result, as it crosses the process boundary. */\nexport interface VisualDomRecord {\n readonly stem: string;\n readonly changes: readonly DomChange[];\n readonly total: number;\n /** Of the total, how many changed rather than merely shifted. */\n readonly rootCauses: number;\n readonly truncated: boolean;\n readonly captureBox: readonly [number, number, number, number];\n /** Absolute paths for the reporter to copy into the run's artifacts. */\n readonly baselineDomPath?: string;\n readonly actualDomPath?: string;\n}\n\nconst VISUAL_MATCHERS = new Set(['toHaveScreenshot', 'toMatchVisualBaseline']);\n\ninterface MinimalTestInfo {\n outputPath(name: string): string;\n attachments: ReadonlyArray<{ name: string }>;\n snapshotPath(name: string, options: { kind: 'screenshot' }): string;\n attach(name: string, options: { body?: Buffer; contentType?: string }): Promise<void>;\n config: { updateSnapshots?: unknown };\n}\n\ninterface LocatorLike extends BoundedTarget {\n page(): EvaluateTarget;\n}\n\n// SAFETY: the received value of an `expect(...)` call is genuinely unknown.\n// These two guards check for the shape each path needs and nothing more.\nfunction isLocatorLike(value: unknown): value is LocatorLike {\n if (typeof value !== 'object' || value === null) return false;\n const v = value as Record<string, unknown>;\n return typeof v.boundingBox === 'function' && typeof v.page === 'function';\n}\n\nfunction isPageLike(value: unknown): value is EvaluateTarget {\n if (typeof value !== 'object' || value === null) return false;\n const v = value as Record<string, unknown>;\n return typeof v.evaluate === 'function' && typeof v.screenshot === 'function';\n}\n\n/** The snapshot name, whichever of the two call shapes was used. */\nfunction snapshotNameOf(args: readonly unknown[]): string | null {\n const first = args[0];\n if (typeof first === 'string') return first;\n if (Array.isArray(first)) return first.join('/');\n if (typeof first === 'object' && first !== null) {\n const name = (first as Record<string, unknown>).name;\n if (typeof name === 'string') return name;\n }\n return null;\n}\n\n/** The options object, which may be the first or the second argument. */\nfunction optionsOf(args: readonly unknown[]): Record<string, unknown> {\n for (const arg of args) {\n if (typeof arg === 'object' && arg !== null && !Array.isArray(arg)) {\n return arg as Record<string, unknown>;\n }\n }\n return {};\n}\n\nfunction maskLocatorsOf(options: Record<string, unknown>): BoundedTarget[] {\n const mask = options.mask;\n if (!Array.isArray(mask)) return [];\n return mask.filter((entry): entry is BoundedTarget => {\n return (\n typeof entry === 'object' &&\n entry !== null &&\n typeof (entry as Record<string, unknown>).boundingBox === 'function'\n );\n });\n}\n\n/** `toHaveScreenshot` rejects a bare name; the resolved path needs the same form. */\nfunction withPngExtension(name: string): string {\n return /\\.(png|jpg|jpeg)$/i.test(name) ? name : `${name}.png`;\n}\n\ninterface DomComparisonInput {\n readonly received: unknown;\n readonly args: readonly unknown[];\n readonly testInfo: MinimalTestInfo;\n /** Attachment count before the assertion ran, to spot the ones it added. */\n readonly attachedBefore: number;\n}\n\nconst ROLE_SUFFIX_RE = /^(.+)-(expected|actual|diff|previous)\\.[A-Za-z0-9]+$/;\n\n/**\n * The stem the reporter will group this comparison under.\n *\n * It must be read back from the attachments the assertion just produced, not\n * derived from the snapshot path: Playwright names attachments after the\n * resolved OUTPUT path, and the two are genuinely different — a snapshot at\n * `drift-target-win32.png` attaches as `drift-target-expected.png`. Deriving it\n * the obvious way silently produced a stem that matched nothing, so every\n * natively-asserted comparison lost its element list.\n *\n * Falls back to the snapshot basename for a passing assertion, which attaches\n * no images — that is the path `toMatchVisualBaseline` takes, and it names its\n * own attachment with exactly that stem.\n */\nfunction resolveStem(testInfo: MinimalTestInfo, attachedBefore: number, fallback: string): string {\n const attachments = testInfo.attachments;\n for (let i = attachedBefore; i < attachments.length; i++) {\n const match = ROLE_SUFFIX_RE.exec(attachments[i]?.name ?? '');\n if (match) return match[1] as string;\n }\n return fallback;\n}\n\n/** Capture the DOM of whatever is about to be screenshotted. */\nasync function capture(input: DomComparisonInput): Promise<DomSnapshot | null> {\n const options = optionsOf(input.args);\n const locator = isLocatorLike(input.received) ? input.received : null;\n const target = locator ? locator.page() : (input.received as EvaluateTarget);\n\n return captureDom({\n target,\n bounded: locator,\n maskLocators: maskLocatorsOf(options),\n fullPage: options.fullPage === true,\n });\n}\n\n/** True when the run was told to rewrite baselines rather than compare against them. */\nfunction isUpdatingSnapshots(testInfo: MinimalTestInfo): boolean {\n const mode = testInfo.config?.updateSnapshots;\n return mode === 'all' || mode === 'changed';\n}\n\n/**\n * Compare the captured DOM against its baseline and record the result.\n *\n * Writes the baseline when there is not one yet, or when the run was invoked\n * with `--update-snapshots` — mirroring exactly what Playwright does with the\n * image beside it, so the two never drift apart.\n */\nasync function recordDomComparison(input: DomComparisonInput, actual: DomSnapshot): Promise<void> {\n const name = snapshotNameOf(input.args);\n if (!name) return;\n\n // Resolving the baseline needs the options form of snapshotPath (>= 1.51).\n // Without it the path comes back wrong and this whole feature silently did\n // nothing; say so once rather than leave the user guessing.\n if (!supportsSnapshotPathOptions(input.testInfo)) {\n warnSnapshotPathUnsupported('DOM comparison');\n return;\n }\n\n const screenshotPath = input.testInfo.snapshotPath(withPngExtension(name), {\n kind: 'screenshot',\n });\n const baselinePath = domBaselinePath(screenshotPath);\n const stem = resolveStem(\n input.testInfo,\n input.attachedBefore,\n basename(screenshotPath, extname(screenshotPath)),\n );\n\n const serialized = serializeSnapshot(actual);\n const updating = isUpdatingSnapshots(input.testInfo);\n const baseline = updating ? null : readDomBaseline(baselinePath);\n\n if (!baseline) {\n // No reference to compare against — write one, the way Playwright writes\n // the image baseline in the same situation. Nothing is reported as changed,\n // because nothing was compared.\n writeDomBaseline(baselinePath, serialized.json);\n return;\n }\n\n const { changes, total, rootCauses } = diffDomSnapshots(baseline, actual);\n\n let actualDomPath: string | undefined;\n try {\n actualDomPath = input.testInfo.outputPath(`${stem}.actual.dom.json`);\n writeFileSync(actualDomPath, serialized.json, 'utf-8');\n } catch {\n actualDomPath = undefined;\n }\n\n const record: VisualDomRecord = {\n stem,\n changes,\n total,\n rootCauses,\n truncated: serialized.truncatedBytes || actual.truncatedNodes > 0,\n captureBox: actual.captureBox,\n ...(existsSync(baselinePath) ? { baselineDomPath: baselinePath } : {}),\n ...(actualDomPath ? { actualDomPath } : {}),\n };\n\n await input.testInfo.attach(VISUAL_DOM_ATTACHMENT, {\n body: Buffer.from(JSON.stringify([record])),\n contentType: 'application/json',\n });\n}\n\ntype MatcherFn = (...args: unknown[]) => unknown;\n\n/**\n * Wrap an `expect` so visual assertions also compare the DOM.\n *\n * Everything that is not a visual assertion against a Page or Locator passes\n * through untouched, including `.not`, `.soft` and every other matcher.\n */\nexport function withVisualCapture(\n baseExpect: (...args: unknown[]) => unknown,\n getTestInfo: () => MinimalTestInfo | null,\n): (...args: unknown[]) => unknown {\n const wrapped = function visualExpect(received: unknown, ...rest: unknown[]): unknown {\n const matchers = baseExpect(received, ...rest);\n\n const isTarget = isLocatorLike(received) || isPageLike(received);\n if (!isTarget || typeof matchers !== 'object' || matchers === null) return matchers;\n\n return new Proxy(matchers as object, {\n get(target, prop, receiver): unknown {\n const value = Reflect.get(target, prop, receiver);\n if (typeof prop !== 'string' || !VISUAL_MATCHERS.has(prop)) return value;\n if (typeof value !== 'function') return value;\n\n const original = value as MatcherFn;\n return async function domAwareMatcher(...args: unknown[]): Promise<unknown> {\n const testInfo = getTestInfo();\n\n // Capture BEFORE the assertion: a failing comparison may leave the\n // page mid-retry, and the DOM we want is the one that was rendered.\n let snapshot: DomSnapshot | null = null;\n const attachedBefore = testInfo?.attachments.length ?? 0;\n if (testInfo) {\n try {\n snapshot = await capture({ received, args, testInfo, attachedBefore });\n } catch {\n // DOM capture never changes whether the assertion runs.\n }\n }\n\n try {\n return await original.apply(target, args);\n } finally {\n if (testInfo && snapshot) {\n try {\n await recordDomComparison({ received, args, testInfo, attachedBefore }, snapshot);\n } catch {\n // A DOM comparison problem must never alter the test outcome.\n }\n }\n }\n };\n },\n });\n };\n\n // Carry `expect.soft`, `expect.configure`, `expect.extend` and the rest\n // forward; a wrapper that drops them silently breaks unrelated assertions.\n const statics = baseExpect as unknown as Record<string, unknown>;\n const target = wrapped as unknown as Record<string, unknown>;\n for (const key of Object.keys(statics)) {\n target[key] = statics[key];\n }\n\n return wrapped;\n}\n","/**\n * Recover visual baseline comparisons from a Playwright test result.\n *\n * Playwright's snapshot matchers do not report what they compared; they only\n * leave attachments behind. `toHaveScreenshot()` / `toMatchSnapshot()` attach\n * `<stem>-expected`, `<stem>-actual`, `<stem>-diff` and, on a retried failure,\n * `<stem>-previous`. Those four share a stem, and the stem is the snapshot's\n * identity within the test.\n *\n * This module is pure — it groups names and reads a message, and touches no\n * filesystem. Copying the images out is `artifact-manager.copyVisualArtifacts`.\n */\n\nimport type { VisualSource, VisualStatus } from '@testrelic/core';\nimport { parseDiffMessage, isSizeMismatchFailure } from './visual-metrics.js';\nimport { VISUAL_DOM_ATTACHMENT, type VisualDomRecord } from './visual-expect.js';\n\ninterface Attachment {\n name: string;\n contentType: string;\n path?: string;\n body?: Buffer;\n}\n\n/**\n * A comparison found in the attachments, still pointing at Playwright's temp\n * files. `artifact-manager` turns this into the report-facing\n * `VisualComparison` once the images have been copied somewhere durable.\n */\nexport interface VisualCandidate {\n readonly name: string;\n readonly status: VisualStatus;\n readonly source: VisualSource;\n readonly expectedPath?: string;\n readonly actualPath?: string;\n readonly diffPath?: string;\n readonly previousPath?: string;\n readonly diffPixels?: number;\n readonly actualWidth?: number;\n readonly actualHeight?: number;\n /** The DOM comparison for this snapshot, when one was made. */\n readonly dom?: VisualDomRecord;\n}\n\n/** Attachment name carrying the explicit matcher's JSON side-channel. */\nexport const VISUAL_META_ATTACHMENT = 'testrelic-visual';\n\n/** The four roles a snapshot image can play, as Playwright suffixes them. */\ntype VisualRole = 'expected' | 'actual' | 'diff' | 'previous';\n\nconst VISUAL_SUFFIX_RE = /^(.+)-(expected|actual|diff|previous)(\\.[A-Za-z0-9]+)$/;\n\n/** One stem's worth of images, before a status is decided. */\ninterface StemGroup {\n expected?: string;\n actual?: string;\n diff?: string;\n previous?: string;\n}\n\n/**\n * Metadata the `toMatchVisualBaseline` matcher attaches alongside its images.\n *\n * It exists because a passing comparison is otherwise invisible: Playwright\n * attaches nothing at all when the images match, so without this the report\n * could only ever show the failures.\n */\nexport interface VisualMetaRecord {\n readonly stem: string;\n readonly name: string;\n readonly status: VisualStatus;\n}\n\n/** Read and validate the explicit matcher's metadata attachment. */\nexport function parseVisualMeta(attachments: readonly Attachment[]): Map<string, VisualMetaRecord> {\n const byStem = new Map<string, VisualMetaRecord>();\n\n for (const attachment of attachments) {\n if (attachment.name !== VISUAL_META_ATTACHMENT || !attachment.body) continue;\n try {\n const parsed: unknown = JSON.parse(attachment.body.toString('utf-8'));\n if (!Array.isArray(parsed)) continue;\n for (const entry of parsed) {\n const record = toMetaRecord(entry);\n if (record) byStem.set(record.stem, record);\n }\n } catch {\n // A malformed side-channel costs us the pass-case detail, nothing more.\n }\n }\n\n return byStem;\n}\n\nconst VALID_STATUSES: readonly string[] = ['passed', 'failed', 'new', 'updated'];\n\n// SAFETY: parsed from an attachment body written by the worker process; every\n// field is checked before use.\nfunction toMetaRecord(entry: unknown): VisualMetaRecord | null {\n if (typeof entry !== 'object' || entry === null) return null;\n const e = entry as Record<string, unknown>;\n if (typeof e.stem !== 'string' || !e.stem) return null;\n if (typeof e.name !== 'string' || !e.name) return null;\n if (typeof e.status !== 'string' || !VALID_STATUSES.includes(e.status)) return null;\n return { stem: e.stem, name: e.name, status: e.status as VisualStatus };\n}\n\n/**\n * Read the DOM comparison side-channel, keyed by the same stem the images use.\n *\n * Written by `visual-expect`; absent whenever the assertion used Playwright's\n * own `expect`, or the page had no DOM baseline yet.\n */\nexport function parseVisualDom(\n attachments: readonly Attachment[],\n): Map<string, VisualDomRecord> {\n const byStem = new Map<string, VisualDomRecord>();\n\n for (const attachment of attachments) {\n if (attachment.name !== VISUAL_DOM_ATTACHMENT || !attachment.body) continue;\n try {\n const parsed: unknown = JSON.parse(attachment.body.toString('utf-8'));\n if (!Array.isArray(parsed)) continue;\n for (const entry of parsed) {\n // SAFETY: written by the worker process; the two fields read before\n // anything else uses it are checked here.\n if (typeof entry !== 'object' || entry === null) continue;\n const record = entry as Record<string, unknown>;\n if (typeof record.stem !== 'string' || !Array.isArray(record.changes)) continue;\n byStem.set(record.stem, entry as VisualDomRecord);\n }\n } catch {\n // A malformed side-channel costs the element list, nothing more.\n }\n }\n\n return byStem;\n}\n\n/** Group every `<stem>-<role>.<ext>` attachment that has a file behind it. */\nfunction groupBySnapshotStem(attachments: readonly Attachment[]): Map<string, StemGroup> {\n const groups = new Map<string, StemGroup>();\n\n for (const attachment of attachments) {\n if (!attachment.path) continue;\n const match = VISUAL_SUFFIX_RE.exec(attachment.name);\n if (!match) continue;\n\n const [, stem, role] = match;\n const group = groups.get(stem as string) ?? {};\n group[role as VisualRole] = attachment.path;\n groups.set(stem as string, group);\n }\n\n return groups;\n}\n\n/**\n * Decide whether a stem is really a snapshot comparison.\n *\n * An ordinary user attachment named `report-actual.json` would otherwise be\n * mistaken for one. A genuine comparison always produces an `actual` next to\n * either the baseline it was compared against or the diff it produced, so\n * requiring that pair costs nothing real and rejects the lookalikes. Stems the\n * explicit matcher has vouched for skip this check — it attaches a lone\n * baseline on a pass, which is the one legitimate single-image case.\n */\nfunction isCredibleComparison(group: StemGroup): boolean {\n return Boolean(group.actual && (group.expected || group.diff));\n}\n\n/** Derive a status from the images alone, for natively-produced comparisons. */\nfunction deriveStatus(\n group: StemGroup,\n updatingSnapshots: boolean,\n failureMessage: string | null | undefined,\n): VisualStatus {\n if (group.diff) return 'failed';\n // A size mismatch also produces no diff — the comparator cannot draw one\n // when the two images have different dimensions, so it attaches expected +\n // actual and throws. Absence of a diff therefore does NOT imply \"no baseline\n // existed\": read the message before concluding that. Without this a genuine\n // failure was reported as a freshly written baseline, it was missing from\n // visualFailures / visualFailedCount / visualDiffPath, and because\n // applyMetrics only targets failed stems the parsed dimensions were dropped\n // too (making SIZE_MISMATCH_RE dead in practice).\n if (isSizeMismatchFailure(failureMessage)) return 'failed';\n // Playwright attached a baseline and an actual but no diff: it had no\n // baseline and wrote one. That is not a pass — nothing was compared.\n return updatingSnapshots ? 'updated' : 'new';\n}\n\n/**\n * Attach the pixel count to the comparison it actually describes.\n *\n * The failure message belongs to whichever assertion threw. When it echoes a\n * snapshot name, that name is matched against the stems; otherwise the numbers\n * are only trustworthy if exactly one comparison failed, and are dropped when\n * several did rather than being pinned on an arbitrary one.\n */\nfunction selectMetricsTarget(\n failed: readonly string[],\n snapshotName: string | undefined,\n): string | null {\n if (failed.length === 0) return null;\n if (failed.length === 1) return failed[0] as string;\n if (!snapshotName) return null;\n\n const base = snapshotName.replace(/\\.[A-Za-z0-9]+$/, '');\n const matches = failed.filter((stem) => stem === base || stem.endsWith(`/${base}`));\n return matches.length === 1 ? (matches[0] as string) : null;\n}\n\nexport interface ParseVisualOptions {\n /** True when the run was invoked with `--update-snapshots`. */\n readonly updatingSnapshots?: boolean;\n /** Concatenated failure messages for the test attempt. */\n readonly failureMessage?: string | null;\n}\n\n/**\n * Extract every visual comparison recorded against one test attempt.\n *\n * Returns an empty array — never throws — for a test that made no visual\n * assertions, which is the overwhelming majority of them.\n */\nexport function parseVisualAttachments(\n attachments: readonly Attachment[],\n options: ParseVisualOptions = {},\n): VisualCandidate[] {\n if (attachments.length === 0) return [];\n\n const meta = parseVisualMeta(attachments);\n const dom = parseVisualDom(attachments);\n const groups = groupBySnapshotStem(attachments);\n if (groups.size === 0) return [];\n\n const updating = options.updatingSnapshots === true;\n const candidates: VisualCandidate[] = [];\n\n for (const [stem, group] of groups) {\n const vouched = meta.get(stem);\n if (!vouched && !isCredibleComparison(group)) continue;\n\n candidates.push({\n name: vouched?.name ?? stem,\n status: vouched?.status ?? deriveStatus(group, updating, options.failureMessage),\n source: vouched ? 'toMatchVisualBaseline' : 'toHaveScreenshot',\n ...(group.expected ? { expectedPath: group.expected } : {}),\n ...(group.actual ? { actualPath: group.actual } : {}),\n ...(group.diff ? { diffPath: group.diff } : {}),\n ...(group.previous ? { previousPath: group.previous } : {}),\n ...(dom.has(stem) ? { dom: dom.get(stem) as VisualDomRecord } : {}),\n });\n }\n\n return applyMetrics(candidates, groups, options.failureMessage);\n}\n\n/** Fold the parsed failure numbers into the one comparison they belong to. */\nfunction applyMetrics(\n candidates: readonly VisualCandidate[],\n groups: ReadonlyMap<string, StemGroup>,\n failureMessage: string | null | undefined,\n): VisualCandidate[] {\n const parsed = parseDiffMessage(failureMessage);\n if (parsed.diffPixels === undefined && parsed.actualWidth === undefined) {\n return [...candidates];\n }\n\n const failedStems = candidates.filter((c) => c.status === 'failed').map((c) => stemOf(c, groups));\n const target = selectMetricsTarget(failedStems, parsed.snapshotName);\n if (target === null) return [...candidates];\n\n return candidates.map((candidate) =>\n stemOf(candidate, groups) === target\n ? {\n ...candidate,\n ...(parsed.diffPixels !== undefined ? { diffPixels: parsed.diffPixels } : {}),\n ...(parsed.actualWidth !== undefined ? { actualWidth: parsed.actualWidth } : {}),\n ...(parsed.actualHeight !== undefined ? { actualHeight: parsed.actualHeight } : {}),\n }\n : candidate,\n );\n}\n\n/**\n * Recover the stem a candidate came from.\n *\n * The explicit matcher renames a candidate to the author's chosen name, so the\n * name cannot be used to look it back up among the groups.\n */\nfunction stemOf(candidate: VisualCandidate, groups: ReadonlyMap<string, StemGroup>): string {\n if (groups.has(candidate.name)) return candidate.name;\n for (const [stem, group] of groups) {\n if (\n group.actual === candidate.actualPath &&\n group.expected === candidate.expectedPath &&\n group.diff === candidate.diffPath\n ) {\n return stem;\n }\n }\n return candidate.name;\n}\n","/**\n * Strip terminal colour codes from text bound for the HTML report.\n *\n * Playwright formats assertion failures for a terminal, so the message a\n * reporter receives carries SGR escape sequences. The report rendered them\n * verbatim, which is why a failure panel read:\n *\n * Error: ESC[2mexpect(ESC[22mESC[31mpageESC[39m...\n *\n * and the Playwright call log came out sprayed with `[2m` / `[22m`. The escape\n * character survives JSON and `textContent` untouched — only a terminal ever\n * consumed it.\n */\n\n/** ESC (0x1B) and the single-byte CSI introducer (0x9B). */\nconst ESC = '\\u001B';\nconst CSI = '\\u009B';\n\n/**\n * ANSI escape sequences, both the modern CSI form and the older two-byte one.\n *\n * Deliberately broader than colour codes alone: cursor moves and erase\n * sequences turn up in wrapped tool output too, and none of them mean anything\n * in HTML.\n */\nconst ANSI_PATTERN = new RegExp(\n `[${ESC}${CSI}][[\\\\]()#;?]*(?:\\\\d{1,4}(?:;\\\\d{0,4})*)?[0-9A-PR-TZcf-nqry=><]`,\n 'g',\n);\n\n/**\n * Remove ANSI escape sequences from a string.\n *\n * Returns the input unchanged when there is nothing to strip. That fast path\n * matters: this runs on every captured step title in a large suite, and the\n * overwhelming majority contain no escapes at all.\n */\nexport function stripAnsi(text: string): string {\n if (!text) return text;\n if (text.indexOf(ESC) === -1 && text.indexOf(CSI) === -1) return text;\n return text.replace(ANSI_PATTERN, '');\n}\n\n/** Strip, tolerating null and undefined so call sites stay uncluttered. */\nexport function stripAnsiSafe(text: string | null | undefined): string | null {\n if (text === null || text === undefined) return null;\n return stripAnsi(text);\n}\n","/**\n * ArtifactIndex — builds the artifact manifest by scanning run folders.\n *\n * Scans the artifacts directory for timestamped run folders,\n * computes file sizes, and produces an ArtifactRunManifest.\n */\n\nimport { readdirSync, statSync } from 'node:fs';\nimport { join, extname, basename } from 'node:path';\nimport type {\n ArtifactRunManifest,\n ArtifactRunEntry,\n ArtifactTestEntry,\n ArtifactFile,\n} from '@testrelic/core';\n\nconst TIMESTAMP_PATTERN = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}-\\d{2}-\\d{2}(-\\d+)?$/;\nconst MANIFEST_SCHEMA_VERSION = '1.0';\n\n/** Subdirectory holding visual baseline comparisons within a test folder. */\nconst VISUAL_DIR = 'visual';\n\nfunction classifyFileType(name: string): ArtifactFile['type'] {\n const ext = extname(name).toLowerCase();\n if (['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp'].includes(ext)) return 'screenshot';\n if (['.webm', '.mp4', '.avi', '.mov'].includes(ext)) return 'video';\n // Match on the NAME, not just the extension: `.zip` alone says nothing about\n // what is inside, and the collector writes this file as `trace.zip`.\n if (ext === '.zip' && basename(name, ext) === 'trace') return 'trace';\n return 'other';\n}\n\n/**\n * Classify a file inside a `visual/<snapshot>/` folder.\n *\n * These are all images, so extension alone would call every one of them a\n * screenshot and the manifest could not tell a baseline from the render that\n * disagreed with it. The writer controls these names, so the stem is reliable.\n */\nfunction classifyVisualFileType(name: string): ArtifactFile['type'] {\n if (name.endsWith('.dom.json')) return 'dom';\n const stem = basename(name, extname(name));\n if (stem === 'baseline') return 'baseline';\n if (stem === 'diff') return 'diff';\n return classifyFileType(name);\n}\n\n/** Collect one snapshot folder's images, e.g. `visual/home--a1b2c3/`. */\nfunction scanVisualFolder(\n snapshotDir: string,\n relativeBase: string,\n snapshotName: string,\n): ArtifactFile[] {\n const files: ArtifactFile[] = [];\n try {\n for (const entry of readdirSync(snapshotDir, { withFileTypes: true })) {\n if (!entry.isFile()) continue;\n const stat = statSync(join(snapshotDir, entry.name));\n files.push({\n name: `${VISUAL_DIR}/${snapshotName}/${entry.name}`,\n type: classifyVisualFileType(entry.name),\n relativePath: `${relativeBase}/${VISUAL_DIR}/${snapshotName}/${entry.name}`,\n sizeBytes: stat.size,\n });\n }\n } catch {\n // Skip unreadable snapshot folders\n }\n return files;\n}\n\nfunction scanTestFolder(testDir: string, runFolder: string, testName: string): ArtifactTestEntry {\n const files: ArtifactFile[] = [];\n const relativeBase = `artifacts/${runFolder}/${testName}`;\n try {\n const entries = readdirSync(testDir, { withFileTypes: true });\n for (const entry of entries) {\n // Visual comparisons are the one nested case: a test can hold many named\n // snapshots, so they live a level down rather than flat beside the video.\n if (entry.isDirectory() && entry.name === VISUAL_DIR) {\n const visualDir = join(testDir, entry.name);\n for (const snapshot of readdirSync(visualDir, { withFileTypes: true })) {\n if (!snapshot.isDirectory()) continue;\n files.push(\n ...scanVisualFolder(join(visualDir, snapshot.name), relativeBase, snapshot.name),\n );\n }\n continue;\n }\n if (!entry.isFile()) continue;\n const filePath = join(testDir, entry.name);\n const stat = statSync(filePath);\n files.push({\n name: entry.name,\n type: classifyFileType(entry.name),\n relativePath: `${relativeBase}/${entry.name}`,\n sizeBytes: stat.size,\n });\n }\n } catch {\n // Skip unreadable directories\n }\n return { testName, files };\n}\n\nfunction scanRunFolder(artifactsDir: string, folderName: string): ArtifactRunEntry {\n const runDir = join(artifactsDir, folderName);\n const tests: ArtifactTestEntry[] = [];\n let totalSizeBytes = 0;\n\n try {\n const entries = readdirSync(runDir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const testEntry = scanTestFolder(join(runDir, entry.name), folderName, entry.name);\n tests.push(testEntry);\n for (const file of testEntry.files) {\n totalSizeBytes += file.sizeBytes;\n }\n }\n } catch {\n // Skip unreadable directories\n }\n\n // Parse timestamp from folder name: 2026-03-02T14-30-00 → 2026-03-02T14:30:00\n const timestamp = folderName\n .replace(/^(\\d{4}-\\d{2}-\\d{2})T(\\d{2})-(\\d{2})-(\\d{2})/, '$1T$2:$3:$4')\n .replace(/-\\d+$/, ''); // Remove collision suffix for ISO parsing\n\n tests.sort((a, b) => a.testName.localeCompare(b.testName));\n\n return {\n folderName,\n timestamp,\n totalSizeBytes,\n testCount: tests.length,\n tests,\n isCurrentRun: false,\n };\n}\n\n/**\n * Build an artifact manifest by scanning the artifacts directory.\n *\n * Discovers timestamped run folders, scans their contents,\n * and returns a sorted manifest (newest first).\n * Legacy flat folders (non-timestamped) are grouped under a \"Legacy\" entry.\n */\nexport function buildArtifactManifest(\n outputDir: string,\n currentRunTimestamp: string,\n): ArtifactRunManifest {\n const artifactsDir = join(outputDir, 'artifacts');\n const runs: ArtifactRunEntry[] = [];\n const legacyTests: ArtifactTestEntry[] = [];\n let legacySize = 0;\n\n try {\n const entries = readdirSync(artifactsDir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n\n if (TIMESTAMP_PATTERN.test(entry.name)) {\n const run = scanRunFolder(artifactsDir, entry.name);\n runs.push({\n ...run,\n isCurrentRun: entry.name === currentRunTimestamp,\n });\n } else {\n // Legacy flat folder — treat as a test entry\n const testEntry = scanTestFolder(join(artifactsDir, entry.name), entry.name, entry.name);\n legacyTests.push(testEntry);\n for (const file of testEntry.files) {\n legacySize += file.sizeBytes;\n }\n }\n }\n } catch {\n // Artifacts directory doesn't exist or is unreadable — return empty manifest\n }\n\n // Sort runs by timestamp descending (newest first)\n runs.sort((a, b) => b.timestamp.localeCompare(a.timestamp));\n\n // Add legacy group if any non-timestamped folders exist\n if (legacyTests.length > 0) {\n legacyTests.sort((a, b) => a.testName.localeCompare(b.testName));\n runs.push({\n folderName: '__legacy__',\n timestamp: '1970-01-01T00:00:00',\n totalSizeBytes: legacySize,\n testCount: legacyTests.length,\n tests: legacyTests,\n isCurrentRun: false,\n });\n }\n\n const totalSizeBytes = runs.reduce((sum, r) => sum + r.totalSizeBytes, 0);\n\n return {\n schemaVersion: MANIFEST_SCHEMA_VERSION,\n generatedAt: new Date().toISOString(),\n artifactBaseDir: 'artifacts',\n totalSizeBytes,\n runs,\n serverPort: null,\n };\n}\n","/**\n * GitignoreManager — auto-add artifact directory to .gitignore.\n *\n * Ensures the configured artifact output directory is listed\n * in the project's .gitignore. Additive-only (never removes entries).\n */\n\nimport { existsSync, readFileSync, appendFileSync } from 'node:fs';\nimport { dirname, join, relative, resolve } from 'node:path';\n\n/** Hard ceiling on git-root walk depth — see `findGitRoot`. */\nconst MAX_WALK_DEPTH = 256;\n\n/**\n * Find the repository root by walking up from startDir looking for `.git`.\n * Returns null if no git repository is found.\n *\n * `startDir` is resolved to an absolute path first: a relative artifacts dir\n * (the default is `./test-results/artifacts`) is anchored to `process.cwd()`\n * so the walk climbs the project's real ancestors. Termination uses\n * `dirname(dir) === dir`, which is idempotent at the filesystem root on every\n * platform — unlike comparing against `parse(dir).root`, which is the empty\n * string for a relative path and is never reached, spinning forever.\n */\nfunction findGitRoot(startDir: string): string | null {\n let dir = resolve(startDir);\n // Belt-and-braces: `dirname(dir) === dir` already terminates at the filesystem\n // root, but this reporter runs inside the customer's `playwright test` process,\n // where an unbounded walk is fatal rather than cosmetic. A depth cap makes it\n // structurally impossible for this loop to spin the event loop again, whatever\n // a future path edge case (UNC share, exotic mount) does to the termination\n // condition. Real trees are nowhere near this deep.\n for (let depth = 0; depth < MAX_WALK_DEPTH; depth++) {\n if (existsSync(join(dir, '.git'))) {\n return dir;\n }\n const parent = dirname(dir);\n if (parent === dir) {\n return null; // reached the filesystem root without finding `.git`\n }\n dir = parent;\n }\n return null;\n}\n\n/**\n * Ensure the artifact output directory is listed in .gitignore.\n *\n * - If not in a git repo, silently returns (no error).\n * - If the pattern is already present, does nothing.\n * - If missing, appends the pattern with a descriptive comment.\n *\n * @param artifactsDir - Absolute path to the artifacts directory\n */\nexport function ensureGitignore(artifactsDir: string): void {\n try {\n const gitRoot = findGitRoot(artifactsDir);\n if (!gitRoot) return;\n\n const gitignorePath = join(gitRoot, '.gitignore');\n const relativePath = relative(gitRoot, artifactsDir).replace(/\\\\/g, '/');\n const pattern = relativePath.endsWith('/') ? relativePath : `${relativePath}/`;\n\n if (existsSync(gitignorePath)) {\n const content = readFileSync(gitignorePath, 'utf-8');\n // Check if the pattern (or a parent pattern) already covers the directory\n const lines = content.split('\\n').map((l) => l.trim());\n if (lines.some((line) => line === pattern || line === relativePath)) {\n return; // Already covered\n }\n }\n\n // Append the pattern\n const entry = `\\n# TestRelic test artifacts\\n${pattern}\\n`;\n appendFileSync(gitignorePath, entry, 'utf-8');\n } catch {\n // Never crash — gitignore management is best-effort\n }\n}\n","/**\n * Watchdog — fail-open timeout guards for reporter phases.\n *\n * A Playwright reporter runs *inside* the customer's `playwright test` process.\n * Playwright awaits `onBegin`/`onEnd`, so anything the reporter awaits that never\n * settles wedges the entire test run — before a single test executes, in the\n * `onBegin` case. Reporting is telemetry: it must never be load-bearing for the\n * customer's tests.\n *\n * Every await in the reporter that touches the network, auth, the filesystem, or\n * a child process goes through `withTimeout`, which guarantees three things:\n *\n * 1. **It always settles.** A pending operation is abandoned after `timeoutMs`.\n * 2. **It never rejects.** A rejection becomes `undefined` — callers cannot be\n * made to crash the run by a telemetry failure.\n * 3. **It never holds the process open.** The timer is `unref()`d, so a pending\n * watchdog cannot by itself keep Node's event loop alive at exit.\n *\n * On expiry a single line goes to stderr, so a stalled run is diagnosable\n * instead of silent — the failure mode that made the 2.11.1 hang so expensive.\n */\n\n/** Sentinel distinguishing \"timer won the race\" from a legitimate `undefined`. */\nconst TIMED_OUT: unique symbol = Symbol('testrelic.timeout');\n\n/** Cloud auth + run-init during `onBegin`. Blocks the first test, so keep it tight. */\nexport const CLOUD_INIT_TIMEOUT_MS = 30_000;\n\n/** Cloud finalize + result upload during `onEnd`. Larger payloads, more headroom. */\nexport const CLOUD_FINALIZE_TIMEOUT_MS = 120_000;\n\n/** Report finalization / artifact flush — local disk I/O, should be fast. */\nexport const REPORT_FINALIZE_TIMEOUT_MS = 60_000;\n\n/** Cloud client teardown (timer cleanup + queue flush). */\nexport const CLOUD_DISPOSE_TIMEOUT_MS = 15_000;\n\n/**\n * Run `operation` with a hard upper bound, failing open on timeout or rejection.\n *\n * Resolves with the operation's value, or `undefined` if it timed out or threw.\n * Never rejects. The abandoned promise keeps running (we cannot cancel an\n * arbitrary promise) but no longer blocks the caller — the point is that the\n * customer's test run proceeds regardless.\n *\n * @param operation - Factory invoked immediately; a throwing factory is caught too.\n * @param timeoutMs - Upper bound in milliseconds.\n * @param label - Human-readable phase name used in the stderr warning.\n */\nexport async function withTimeout<T>(\n operation: () => Promise<T>,\n timeoutMs: number,\n label: string,\n): Promise<T | undefined> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const timeout = new Promise<typeof TIMED_OUT>((resolve) => {\n timer = setTimeout(() => resolve(TIMED_OUT), timeoutMs);\n // Never let a pending watchdog be the reason the process cannot exit.\n if (typeof timer?.unref === 'function') timer.unref();\n });\n\n try {\n // Invoking inside the try means a factory that throws synchronously is\n // handled identically to one that returns a rejected promise.\n const result = await Promise.race([operation(), timeout]);\n\n if (result === TIMED_OUT) {\n process.stderr.write(\n `[testrelic] ${label} timed out after ${Math.round(timeoutMs / 1000)}s — ` +\n 'continuing without it. Your tests are unaffected.\\n',\n );\n return undefined;\n }\n return result as T;\n } catch (err) {\n process.stderr.write(\n `[testrelic] ${label} failed — continuing without it. Your tests are unaffected. ` +\n `(${err instanceof Error ? err.message : String(err)})\\n`,\n );\n return undefined;\n } finally {\n // Clear eagerly so a completed phase leaves no timer behind at all.\n if (timer) clearTimeout(timer);\n }\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 ActionStep,\n ApiCallRecord,\n ApiAssertion,\n NavigationAnnotation,\n NavigationType,\n FailureDiagnostic,\n NetworkStats,\n TestStatus,\n TestType,\n TestResult as TRTestResult,\n TestArtifacts,\n ConsoleLogEntry,\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 readonly actions: ActionStep[] | null;\n readonly consoleLogs: ConsoleLogEntry[] | 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 actions: test.actions,\n consoleLogs: test.consoleLogs,\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 ActionStep,\n VisualComparison,\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 readonly actions: readonly ActionStep[] | null;\n readonly visual?: readonly VisualComparison[] | 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 */\nexport function 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. */\nexport function 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 // The published shape is readonly; the accumulator must not be. Tallying\n // into a `Readonly` alias was five assignment errors, one per key.\n const statusRanges: Record<keyof ApiCallsByStatusRange, number> =\n { '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 // Action step statistics\n let totalActionSteps = 0;\n const actionStepsByCategory: Record<string, number> = {};\n function countActions(steps: readonly ActionStep[]): void {\n for (const step of steps) {\n totalActionSteps++;\n actionStepsByCategory[step.category] = (actionStepsByCategory[step.category] ?? 0) + 1;\n if (step.children.length > 0) {\n countActions(step.children);\n }\n }\n }\n for (const test of tests) {\n if (test.actions) {\n countActions(test.actions);\n }\n }\n\n // Visual comparison counts. A `new` or `updated` snapshot is neither a\n // pass nor a failure — nothing was compared — so it is counted apart\n // rather than folded into either bucket.\n let visualChecks = 0;\n let visualFailures = 0;\n let visualNew = 0;\n let visualDomChanges = 0;\n for (const test of tests) {\n if (!test.visual) continue;\n for (const comparison of test.visual) {\n visualChecks++;\n if (comparison.status === 'failed') visualFailures++;\n else if (comparison.status === 'new' || comparison.status === 'updated') visualNew++;\n visualDomChanges += comparison.domChangeCount ?? 0;\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 totalActionSteps,\n actionStepsByCategory,\n visualChecks,\n visualFailures,\n visualNew,\n visualDomChanges,\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 htmlReportPath - Path to the HTML report file\n * @param quiet - When true, suppresses all output\n */\nexport function printConsoleSummary(\n summary: Summary,\n outputPath: string,\n htmlReportPath: 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 AI - Playwright Test Report');\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 // Action steps section (only if action steps present)\n if (summary.totalActionSteps > 0) {\n const catParts: string[] = [];\n const catMap = summary.actionStepsByCategory;\n if (catMap['ui_action']) catParts.push(`${catMap['ui_action']} UI`);\n if (catMap['assertion']) catParts.push(`${catMap['assertion']} assertions`);\n if (catMap['custom_step']) catParts.push(`${catMap['custom_step']} custom`);\n const catStr = catParts.length > 0 ? ` (${catParts.join(' ')})` : '';\n output += line(`Actions: ${summary.totalActionSteps} steps${catStr}`);\n }\n\n // Output paths\n output += line(`Report: ${htmlReportPath}`);\n output += line(`Data: ${outputPath}`);\n\n output += bottom;\n output += 'For more information visit us at https://docs.testrelic.ai\\n';\n\n process.stderr.write(output);\n}\n","/**\n * Action Step Collector\n *\n * Converts Playwright TestStep trees into ActionStep arrays.\n * Filters to user-meaningful categories, calculates video offsets,\n * and preserves parent-child hierarchy.\n */\n\nimport type { ActionStep, ActionCategory } from '@testrelic/core';\nimport { stripAnsiSafe } from './ansi.js';\n\n/** Playwright step categories we include in the report. */\nconst INCLUDED_CATEGORIES = new Set(['pw:api', 'expect', 'test.step']);\n\n/** Maps Playwright step category to our ActionCategory. */\nfunction mapCategory(pwCategory: string): ActionCategory {\n if (pwCategory === 'expect') return 'assertion';\n if (pwCategory === 'test.step') return 'custom_step';\n return 'ui_action';\n}\n\n/**\n * Minimal shape of Playwright's TestStep that we consume.\n * Avoids importing Playwright types directly in this module.\n */\nexport interface PwStepLike {\n readonly title: string;\n readonly category: string;\n readonly startTime: Date;\n readonly duration: number;\n readonly error?: { message?: string } | undefined;\n readonly steps: readonly PwStepLike[];\n}\n\n/**\n * Recursively converts a Playwright step tree into ActionStep objects.\n *\n * When a step's category is included (pw:api, expect, test.step), it is\n * converted and its children are nested underneath it.\n *\n * When a step's category is excluded (hook, fixture, attach, etc.), the\n * step itself is skipped but its children are still traversed — any\n * included descendants are \"promoted\" into the output list. This ensures\n * actions inside beforeEach/afterEach hooks are captured.\n *\n * @param step - A single Playwright TestStep\n * @param testStartTime - Test result start time for video offset calculation\n * @param output - Array to push results into (for promoted children)\n */\nfunction convertStep(\n step: PwStepLike,\n testStartTime: Date,\n output: ActionStep[],\n): void {\n if (INCLUDED_CATEGORIES.has(step.category)) {\n const children: ActionStep[] = [];\n for (const child of step.steps) {\n convertStep(child, testStartTime, children);\n }\n\n const videoOffsetMs = step.startTime.getTime() - testStartTime.getTime();\n\n output.push({\n title: step.title,\n category: mapCategory(step.category),\n timestamp: step.startTime.toISOString(),\n duration: step.duration,\n videoOffset: videoOffsetMs >= 0 ? videoOffsetMs / 1000 : null,\n status: step.error ? 'failed' : 'passed',\n // Assertion step errors carry the same terminal colour codes.\n error: stripAnsiSafe(step.error?.message),\n children,\n });\n } else {\n // Skip this step but look through to its children —\n // promotes pw:api/expect/test.step steps inside hooks/fixtures.\n for (const child of step.steps) {\n convertStep(child, testStartTime, output);\n }\n }\n}\n\n/**\n * Collects action steps from a Playwright test result's step tree.\n *\n * Filters to only user-meaningful categories (pw:api, expect, test.step),\n * looks through excluded parents (hook, fixture, attach) to capture their\n * included children, calculates video offsets, and preserves nesting hierarchy.\n *\n * @param steps - Top-level Playwright steps from result.steps\n * @param testStartTime - Test result start time (for video offset)\n * @returns Chronologically ordered array of ActionStep objects\n */\nexport function collectActionSteps(\n steps: readonly PwStepLike[],\n testStartTime: Date,\n): ActionStep[] {\n const result: ActionStep[] = [];\n for (const step of steps) {\n convertStep(step, testStartTime, result);\n }\n return result;\n}\n","/**\n * Cloud authentication — token exchange and refresh.\n *\n * Handles the JWT exchange model: API key → short-lived access token.\n * All HTTP via built-in `fetch` (Node.js 18+). Timeouts via AbortController.\n *\n * SECURITY (Constitution Principle VIII):\n * - API key is never logged or serialized beyond the single POST body.\n * - HTTPS enforced for all non-localhost communication.\n * - Error messages never expose credentials or internal paths.\n */\n\nimport { ErrorCode, createError } from '@testrelic/core';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface TokenExchangeResult {\n readonly accessToken: string;\n readonly refreshToken: string;\n readonly expiresIn: number;\n readonly orgId: string;\n readonly orgName: string;\n readonly userId: string;\n readonly userName: string;\n}\n\nexport interface TokenRefreshResult {\n readonly accessToken: string;\n readonly refreshToken: string;\n readonly expiresIn: number;\n}\n\nexport interface AuthError {\n readonly code: 'invalid_key' | 'expired_key' | 'validation_error' | 'rate_limited' | 'server_error' | 'network_error' | 'timeout';\n readonly message: string;\n readonly statusCode: number | null;\n}\n\n// ---------------------------------------------------------------------------\n// HTTPS Enforcement\n// ---------------------------------------------------------------------------\n\nconst LOCALHOST_HOSTS = new Set(['localhost', '127.0.0.1', '0.0.0.0']);\n\n/**\n * Validate that the endpoint URL uses HTTPS.\n * HTTP is allowed only for localhost addresses (dev environments).\n * Throws on violation (Constitution Principle VIII — transport security).\n */\nexport function enforceHttps(endpoint: string): void {\n const url = new URL(endpoint);\n if (url.protocol === 'https:') return;\n if (url.protocol === 'http:' && LOCALHOST_HOSTS.has(url.hostname)) return;\n throw createError(\n ErrorCode.CLOUD_CONFIG_INVALID,\n `HTTPS is required for cloud communication. Got: ${url.protocol}//${url.hostname}`,\n );\n}\n\n// ---------------------------------------------------------------------------\n// Health Check\n// ---------------------------------------------------------------------------\n\nconst HEALTH_CHECK_TIMEOUT_MS = 3000;\n\n/**\n * Lightweight connectivity probe — GET request to `{endpoint}/health`.\n * Returns true if the cloud is reachable and healthy, false otherwise.\n */\nexport async function healthCheck(endpoint: string): Promise<boolean> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), HEALTH_CHECK_TIMEOUT_MS);\n try {\n const response = await fetch(`${endpoint}/health`, {\n method: 'GET',\n signal: controller.signal,\n });\n return response.ok;\n } catch {\n return false;\n } finally {\n clearTimeout(timer);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Structured Error Parsing\n// ---------------------------------------------------------------------------\n\nasync function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Parse a structured cloud error response: `{ error: { code, message, details? } }`.\n * Returns the human-readable message, or a fallback string if parsing fails.\n */\nasync function parseCloudError(response: Response): Promise<string> {\n try {\n const body = await response.json() as Record<string, unknown>;\n const error = body.error as Record<string, unknown> | undefined;\n if (error && typeof error.message === 'string') {\n const details = error.details as Array<{ field: string; message: string }> | undefined;\n if (Array.isArray(details) && details.length > 0) {\n const detailMessages = details.map((d) => `${d.field}: ${d.message}`).join(', ');\n return `${error.message} (${detailMessages})`;\n }\n return error.message;\n }\n } catch {\n // Response body not JSON — use fallback\n }\n return `HTTP ${response.status}`;\n}\n\n// ---------------------------------------------------------------------------\n// Token Exchange\n// ---------------------------------------------------------------------------\n\n/**\n * Exchange an API key for a short-lived JWT access token.\n *\n * POST /sdk/auth/token with { apiKey }\n *\n * Returns a TokenExchangeResult on success.\n * Returns an AuthError on failure (never throws).\n */\nexport async function exchangeToken(\n endpoint: string,\n apiKey: string,\n timeout: number,\n): Promise<TokenExchangeResult | AuthError> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeout);\n try {\n const response = await fetch(`${endpoint}/sdk/auth/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ apiKey }),\n signal: controller.signal,\n });\n\n if (response.ok) {\n const body = await response.json() as Record<string, unknown>;\n return {\n accessToken: body.accessToken as string,\n refreshToken: body.refreshToken as string,\n expiresIn: body.expiresIn as number,\n orgId: body.orgId as string,\n orgName: body.orgName as string,\n userId: body.userId as string,\n userName: body.userName as string,\n };\n }\n\n // 429 — rate limited, respect Retry-After and retry once\n if (response.status === 429) {\n const retryAfter = response.headers.get('Retry-After');\n const waitMs = retryAfter ? parseInt(retryAfter, 10) * 1000 : 5000;\n if (!isNaN(waitMs) && waitMs > 0) {\n await sleep(waitMs);\n // Retry once after waiting\n const retryResponse = await fetch(`${endpoint}/sdk/auth/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ apiKey }),\n signal: controller.signal,\n });\n if (retryResponse.ok) {\n const body = await retryResponse.json() as Record<string, unknown>;\n return {\n accessToken: body.accessToken as string,\n refreshToken: body.refreshToken as string,\n expiresIn: body.expiresIn as number,\n orgId: body.orgId as string,\n orgName: body.orgName as string,\n userId: body.userId as string,\n userName: body.userName as string,\n };\n }\n }\n return { code: 'rate_limited', message: 'Rate limited during token exchange.', statusCode: 429 };\n }\n\n // 400 — validation error (e.g., malformed API key format)\n if (response.status === 400) {\n const errorMessage = await parseCloudError(response);\n return { code: 'validation_error', message: errorMessage, statusCode: 400 };\n }\n\n if (response.status === 401) {\n return { code: 'invalid_key', message: 'API key is invalid or has been revoked.', statusCode: 401 };\n }\n if (response.status === 403) {\n return { code: 'expired_key', message: 'API key has expired.', statusCode: 403 };\n }\n\n const errorMessage = await parseCloudError(response);\n return {\n code: 'server_error',\n message: errorMessage,\n statusCode: response.status,\n };\n } catch (err) {\n if (err instanceof DOMException && err.name === 'AbortError') {\n return { code: 'timeout', message: 'Token exchange timed out.', statusCode: null };\n }\n return {\n code: 'network_error',\n message: 'Failed to reach cloud for token exchange.',\n statusCode: null,\n };\n } finally {\n clearTimeout(timer);\n }\n}\n\n/** Type guard: check if result is an AuthError. */\nexport function isAuthError(result: TokenExchangeResult | AuthError): result is AuthError {\n return 'code' in result && typeof (result as AuthError).code === 'string'\n && ['invalid_key', 'expired_key', 'validation_error', 'rate_limited', 'server_error', 'network_error', 'timeout'].includes((result as AuthError).code);\n}\n\n// ---------------------------------------------------------------------------\n// Token Refresh\n// ---------------------------------------------------------------------------\n\n/**\n * Refresh an expiring access token.\n *\n * POST /sdk/auth/refresh with { refreshToken }\n *\n * Returns a TokenRefreshResult on success, or null on failure.\n */\nexport async function refreshAccessToken(\n endpoint: string,\n currentRefreshToken: string,\n timeout: number,\n): Promise<TokenRefreshResult | null> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeout);\n try {\n let response = await fetch(`${endpoint}/sdk/auth/refresh`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ refreshToken: currentRefreshToken }),\n signal: controller.signal,\n });\n\n // 429 — rate limited, respect Retry-After and retry once\n if (response.status === 429) {\n const retryAfter = response.headers.get('Retry-After');\n const waitMs = retryAfter ? parseInt(retryAfter, 10) * 1000 : 5000;\n if (!isNaN(waitMs) && waitMs > 0) {\n await sleep(waitMs);\n response = await fetch(`${endpoint}/sdk/auth/refresh`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ refreshToken: currentRefreshToken }),\n signal: controller.signal,\n });\n }\n }\n\n if (!response.ok) return null;\n\n const body = await response.json() as Record<string, unknown>;\n return {\n accessToken: body.accessToken as string,\n refreshToken: body.refreshToken as string,\n expiresIn: body.expiresIn as number,\n };\n } catch {\n return null;\n } finally {\n clearTimeout(timer);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Repo Resolution\n// ---------------------------------------------------------------------------\n\nexport interface RepoResolveResult {\n readonly repoId: string;\n readonly displayName: string;\n}\n\n/**\n * Resolve or auto-create a repo by git identity.\n *\n * POST /repos/resolve with { gitId, displayName, branch }\n *\n * Returns the resolved repo, or null on failure.\n */\nexport async function resolveRepo(\n endpoint: string,\n accessToken: string,\n gitId: string,\n displayName: string,\n timeout: number,\n branch?: string | null,\n): Promise<RepoResolveResult | null> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeout);\n try {\n const requestBody: Record<string, string> = { gitId, displayName };\n if (branch) requestBody.branch = branch;\n const response = await fetch(`${endpoint}/repos/resolve`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${accessToken}`,\n },\n body: JSON.stringify(requestBody),\n signal: controller.signal,\n });\n\n if (!response.ok) return null;\n\n const responseBody = await response.json() as Record<string, unknown>;\n return {\n repoId: responseBody.repoId as string,\n displayName: responseBody.displayName as string,\n };\n } catch {\n return null;\n } finally {\n clearTimeout(timer);\n }\n}\n","/**\n * Git metadata collection and project ID normalization.\n *\n * Collects branch, commit, message, and remote URL using git CLI.\n * Each field fails independently — a missing remote doesn't prevent\n * branch detection.\n *\n * Uses `execSync` for simplicity (< 100ms total, runs at startup only).\n */\n\nimport { execSync } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\nimport { basename, join } from 'node:path';\nimport type { GitMetadata } from '@testrelic/core';\n\n// ---------------------------------------------------------------------------\n// Git Command Execution\n// ---------------------------------------------------------------------------\n\nconst GIT_TIMEOUT_MS = 5000;\n\n/** Run a git command and return stdout, or null on any failure. */\nfunction execGit(command: string, cwd?: string): string | null {\n try {\n const result = execSync(command, {\n cwd,\n timeout: GIT_TIMEOUT_MS,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n return result.trim() || null;\n } catch {\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Git Metadata Collection\n// ---------------------------------------------------------------------------\n\n/**\n * Collect git metadata from the current working directory.\n * Each field is collected independently — failures don't cascade.\n * Non-git projects return all nulls without error.\n */\nexport function collectGitMetadata(cwd?: string): GitMetadata {\n const branch = execGit('git rev-parse --abbrev-ref HEAD', cwd);\n const commitSha = execGit('git rev-parse --short HEAD', cwd);\n const commitMessage = execGit('git log -1 --pretty=%s', cwd);\n const commitAuthor =\n execGit('git log -1 --format=%an', cwd) ??\n execGit('git config user.name', cwd);\n const rawRemoteUrl = getRemoteUrl(cwd);\n const remoteUrl = rawRemoteUrl ? normalizeGitRemoteUrl(rawRemoteUrl) : null;\n\n return { branch, commitSha, commitMessage, commitAuthor, remoteUrl };\n}\n\n/**\n * Get the remote URL, preferring `origin` but falling back to the first\n * available remote. Handles monorepos where the remote may not be named `origin`.\n */\nfunction getRemoteUrl(cwd?: string): string | null {\n // Try origin first (most common)\n const originUrl = execGit('git remote get-url origin', cwd);\n if (originUrl) return originUrl;\n\n // List all remotes and use the first one\n const remotes = execGit('git remote', cwd);\n if (!remotes) return null;\n\n const firstRemote = remotes.split('\\n')[0]?.trim();\n if (!firstRemote) return null;\n\n return execGit(`git remote get-url ${firstRemote}`, cwd);\n}\n\n// ---------------------------------------------------------------------------\n// Remote URL Normalization\n// ---------------------------------------------------------------------------\n\n/**\n * Normalize a git remote URL into a stable project identifier.\n *\n * Strips protocol, replaces `:` with `/`, strips `.git` suffix, lowercases.\n *\n * Examples:\n * https://github.com/acme/checkout-service.git → github.com/acme/checkout-service\n * git@github.com:acme/checkout-service.git → github.com/acme/checkout-service\n */\nexport function normalizeGitRemoteUrl(url: string): string {\n let normalized = url.trim();\n\n // Strip protocol (https://, http://, git://, ssh://)\n normalized = normalized.replace(/^[a-z+]+:\\/\\//, '');\n\n // Handle SSH format: git@host:path → host/path\n normalized = normalized.replace(/^[^@]+@/, '');\n normalized = normalized.replace(/:(?!\\d)/, '/');\n\n // Strip .git suffix\n normalized = normalized.replace(/\\.git$/, '');\n\n // Strip trailing slashes\n normalized = normalized.replace(/\\/+$/, '');\n\n // Strip authentication (user:pass@)\n normalized = normalized.replace(/^[^@/]+@/, '');\n\n return normalized.toLowerCase();\n}\n\n// ---------------------------------------------------------------------------\n// Repo Display Name\n// ---------------------------------------------------------------------------\n\n/**\n * Derive a human-readable repo display name from a normalized git remote URL\n * or from a directory path.\n *\n * Returns the last path segment (e.g., \"checkout-service\" from \"github.com/acme/checkout-service\").\n */\nexport function deriveRepoDisplayName(normalizedUrl: string): string {\n const segments = normalizedUrl.split('/').filter(Boolean);\n return segments[segments.length - 1] ?? normalizedUrl;\n}\n\n/**\n * Read the `name` field from a package.json in the given directory.\n * Returns null if the file doesn't exist or can't be parsed.\n */\nexport function readPackageJsonName(dirPath: string): string | null {\n try {\n const raw = readFileSync(join(dirPath, 'package.json'), 'utf-8');\n const pkg = JSON.parse(raw) as Record<string, unknown>;\n return typeof pkg.name === 'string' && pkg.name.length > 0 ? pkg.name : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Derive a project git ID for non-git projects.\n * Tries package.json `name` first, falls back to `local/{dirname}`.\n */\nexport function deriveNonGitProjectId(dirPath: string): string {\n const pkgName = readPackageJsonName(dirPath);\n if (pkgName) return pkgName;\n return `local/${basename(dirPath)}`;\n}\n","/**\n * Maps TESTRELIC_RUN_TYPE to the cloud ingest enum so monitoring dashboards\n * (which filter by run_type) receive runs in the correct bucket.\n */\nconst ALLOWED = new Set(['smoke', 'regression', 'nightly', 'ci']);\n\nexport type SdkRunType = 'smoke' | 'regression' | 'nightly' | 'ci';\n\nexport function resolveSdkRunTypeFromEnv(): SdkRunType | undefined {\n const raw = process.env.TESTRELIC_RUN_TYPE?.trim().toLowerCase();\n if (!raw) return undefined;\n if (!ALLOWED.has(raw)) return undefined;\n return raw as SdkRunType;\n}\n","/**\n * Deadlines for the upload path — the thing that was missing.\n *\n * ⛔ THE BUG THIS EXISTS TO KILL. Every request in `cloud-upload.ts` and\n * `cloud-artifact-upload.ts` called bare `fetch(url, init)` with no `signal`,\n * so each one inherited undici's defaults — 300s to receive headers, 300s\n * between body chunks — and each sat inside a 3-attempt retry loop. One stalled\n * connection therefore held the process for roughly 3 × 5 minutes plus backoff.\n * The upload runs inside Playwright's `onEnd`, which the runner must await\n * before it can exit, so the visible symptom was \"the tests passed and then\n * Studio hung\", with Ctrl+C doing nothing: an in-flight request with no abort\n * signal is not cancelled by a signal handler. Observed at 11m49s on a real run.\n *\n * ⛔ AND THE CONFIGURED TIMEOUT NEVER REACHED IT. `timeout` (default 30s) was\n * threaded into `cloud-auth.ts` — the token exchange and the health check — and\n * nowhere else. A tester who set it believed they had bounded uploading; they\n * had bounded the handshake.\n *\n * Three tools, deliberately separate:\n *\n * · `withTimeout` — one request, one AbortController, always cleared.\n * · `putDeadlineMs` — a transfer's budget scales with its size, because a\n * 200 MB trace legitimately takes longer than a 2 KB JSON POST and a flat\n * cap would abandon healthy uploads on slow links.\n * · `Budget` — the phase-level circuit breaker: once the whole upload has\n * spent its allowance, later requests fail immediately rather than each\n * paying its own timeout. Ten artifacts × one stall each is the difference\n * between one late run and an unusable app.\n */\n\n/** What the upload phase gets in total, when nothing else is configured. */\nexport const DEFAULT_UPLOAD_BUDGET_MS = 120_000;\n\n/** Floor and ceiling for a single presigned PUT, whatever the file size says. */\nexport const PUT_MIN_MS = 30_000;\nexport const PUT_MAX_MS = 600_000;\n\n/** Assumed throughput for sizing a transfer's deadline: 1 MB/s is slow enough\n * to cover a hotel wifi and fast enough that a wedged socket still gives up. */\nconst ASSUMED_BYTES_PER_MS = 1_000;\n\n/**\n * Run one fetch under a deadline.\n *\n * The timer is cleared in `finally` — a pending `setTimeout` keeps Node's event\n * loop alive, which in a reporter that has just finished its work is its own\n * flavour of \"the process won't exit\".\n */\nexport async function withTimeout(\n url: string,\n init: RequestInit,\n timeoutMs: number,\n): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), Math.max(1, timeoutMs));\n try {\n return await fetch(url, { ...init, signal: controller.signal });\n } finally {\n clearTimeout(timer);\n }\n}\n\n/** How long one presigned PUT may take, from the size it declares. */\nexport function putDeadlineMs(sizeBytes: number, base: number): number {\n const scaled = base + Math.ceil(Math.max(0, sizeBytes) / ASSUMED_BYTES_PER_MS);\n return Math.min(PUT_MAX_MS, Math.max(PUT_MIN_MS, scaled));\n}\n\n/**\n * The upload phase's total allowance.\n *\n * ⛔ `remaining()` returning 0 is a REFUSAL, not a hint: callers must stop\n * rather than start a request with no time left, because a request started at\n * the boundary still pays its own full timeout — which is exactly how \"one\n * stalled artifact\" became \"ten stalled artifacts\".\n */\nexport class Budget {\n private readonly deadline: number;\n\n constructor(totalMs: number = DEFAULT_UPLOAD_BUDGET_MS, now: number = Date.now()) {\n this.deadline = now + Math.max(0, totalMs);\n }\n\n remaining(now: number = Date.now()): number {\n return Math.max(0, this.deadline - now);\n }\n\n expired(now: number = Date.now()): boolean {\n return this.remaining(now) === 0;\n }\n\n /** The budget for one request: never more than what the phase has left. */\n slice(requestMs: number, now: number = Date.now()): number {\n return Math.min(requestMs, this.remaining(now));\n }\n}\n","/**\n * Cloud upload — batch and realtime upload functions.\n *\n * Handles payload construction, gzip compression for large payloads,\n * retry with exponential backoff, rate limit handling, and structured\n * failure reporting.\n *\n * Constitution Principle VIII: HTTPS enforced via cloud-auth.ts.\n * Never throws to caller — returns structured results.\n */\n\nimport { gzip } from 'node:zlib';\nimport { promisify } from 'node:util';\nimport type {\n TestRunReport,\n GitMetadata,\n CIMetadata,\n Summary,\n TimelineEntry,\n TimelineStep,\n} from '@testrelic/core';\nimport type { SdkRunType } from './run-type-env.js';\nimport { resolveSdkRunTypeFromEnv } from './run-type-env.js';\nimport { Budget, withTimeout } from './cloud-deadline.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface TestResultForUpload {\n readonly testId: string;\n readonly title: string;\n readonly status: string;\n readonly duration: number;\n readonly suiteName: string;\n readonly filePath: string;\n readonly testType: string;\n readonly isFlaky: boolean;\n readonly startedAt: string;\n readonly completedAt: string;\n readonly tags: readonly string[];\n readonly failure: unknown | null;\n /** Current attempt index (0 = first run, 1 = first retry, etc.) */\n readonly retry: number;\n /** Total number of retries that occurred (test.results.length - 1) */\n readonly retryCount: number;\n /** Human-readable retry outcome, e.g. \"passed on retry 1\", or null if no retries */\n readonly retryStatus: string | null;\n /** Testing platform/origin, e.g. \"Local\", \"GitHub Actions\", \"BrowserStack\" */\n readonly source?: string;\n /** UI tests — browser name, e.g. \"Chrome\", \"Firefox\", \"WebKit\" */\n readonly browser?: string;\n /** UI tests — operating system, e.g. \"Windows\", \"macOS\", \"Linux\" */\n readonly os?: string;\n /** API tests — test runner, e.g. \"Newman Runner\", \"Playwright API Client\" */\n readonly runner?: string;\n /** API tests — protocol, e.g. \"rest\", \"graphql\", \"grpc\". Drives API-workspace routing. */\n readonly apiProtocol?: string;\n /** UI tests — viewport resolution, e.g. \"1920x1080\" */\n readonly resolution?: string;\n /**\n * Playwright action steps (click, fill, expect, etc.) included in the batch\n * payload as a reliable fallback for the fire-and-forget per-test realtime\n * upload. If the realtime call succeeds first, the server deduplicates by\n * skipping reingest. If it fails silently, the batch guarantees delivery.\n */\n readonly actions?: readonly Record<string, unknown>[];\n /** Console log entries captured during this test — batch fallback. */\n readonly consoleLogs?: readonly Record<string, unknown>[];\n /** Network/HAR request entries captured during this test — batch fallback. */\n readonly networkRequests?: readonly Record<string, unknown>[];\n /**\n * Navigation entries captured during this test — batch fallback. This is the\n * fix for Test Navigation never reaching the server under the default 'batch'\n * upload strategy: the realtime per-test path (which carries navigations) never\n * runs, and the streaming report's timeline is empty, so the batch per-test\n * payload is the only delivery route. Small (a few URL entries), so the payload\n * size guard above never strips it.\n */\n readonly navigations?: readonly Record<string, unknown>[];\n /**\n * Visual regression comparisons for this test — baseline/actual/diff paths,\n * the pixel counts, and the DOM elements that differed.\n *\n * The IMAGES have always been uploaded as artifacts; this is the structured\n * result that says what they mean. Without it the platform receives three\n * PNGs with no record of which snapshot they belong to, whether the\n * comparison passed, how much changed, or which element caused it — so a\n * reviewer has the pictures and nothing to decide with.\n *\n * Bounded, not small: the capped element list can reach tens of KB per\n * comparison, which is why `visual` is one of the HEAVY_TEST_KEYS the\n * payload-size tiers shed.\n */\n readonly visual?: readonly Record<string, unknown>[];\n /**\n * API request/response calls in the canonical `apiReport.apiCalls` shape —\n * batch fallback so the API-workspace inspector renders req/res even when the\n * fire-and-forget realtime per-test upload is lost. Without this the batch\n * re-ingest would overwrite the realtime apiReport with one that has no calls.\n */\n readonly apiCalls?: readonly Record<string, unknown>[];\n /** Captured API assertions in the canonical `protocolAssertions` shape — batch fallback. */\n readonly protocolAssertions?: readonly Record<string, unknown>[];\n}\n\nexport interface RunUploadPayload {\n readonly runId: string;\n readonly repoGitId: string;\n readonly startedAt: string;\n readonly summary: Summary;\n readonly timeline: readonly (TimelineEntry | TimelineStep)[];\n readonly tests?: readonly TestResultForUpload[];\n readonly branch?: string;\n readonly commit?: string;\n readonly commitMessage?: string;\n readonly commitAuthor?: string;\n readonly finishedAt?: string;\n readonly duration?: number;\n readonly ciProvider?: string;\n readonly ciRunUrl?: string;\n /** Where tests ran: \"local\", \"github-actions\", \"gitlab-ci\", etc. */\n readonly environment?: string;\n /** Cloud-farm platform this run executed on (BrowserStack / LambdaTest). */\n readonly cloudPlatform?: 'browserstack' | 'lambdatest';\n readonly cloudBuildId?: string;\n readonly cloudSessionId?: string;\n /** When set, monitoring dashboards (/monitoring/*) include this run for that bucket. */\n readonly runType?: SdkRunType;\n /** Run-level API signal — \"api\" when every test in the run is an API test. Lets the cloud open the API workspace without a ?testType=api URL param. */\n readonly testType?: string;\n /** Dominant API protocol for the run (e.g. \"rest\"). Pairs with testType==='api'. */\n readonly apiProtocol?: string;\n /** Free-form run metadata; mirrors the cloud contract (`testType`/`primaryProtocol`) so already-deployed servers classify the run too. */\n readonly metadata?: Record<string, unknown>;\n}\n\nexport interface UploadResult {\n readonly success: boolean;\n readonly statusCode: number | null;\n readonly error: string | null;\n}\n\nexport interface UploadFailure {\n readonly success: false;\n readonly reason: string;\n readonly statusCode: number | null;\n readonly payload: Record<string, unknown>;\n readonly targetEndpoint: string;\n readonly method: string;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst GZIP_THRESHOLD_BYTES = 1_048_576; // 1MB\nconst RETRY_DELAYS_MS = [1000, 3000, 9000];\nconst MAX_RETRIES = 3;\n/** Per-request ceiling for the JSON calls (run upload, init, finalize). The\n * config's own `timeout` overrides it — see cloud-deadline for why bare\n * `fetch` could not be left to undici's 5-minute defaults. */\nconst DEFAULT_REQUEST_TIMEOUT_MS = 30_000;\n/** Strip per-test consoleLogs/networkRequests from the batch if the total\n * serialized payload exceeds this ceiling. The batch request is gzipped before\n * upload (~10x smaller) and the server accepts 100MB *decompressed*, so this\n * sits just under that limit — detail is kept for all realistic runs and the\n * strip is a last resort (with a warning), never a silent drop. The default\n * 'batch' strategy never runs the realtime per-test path, so the batch arrays\n * are the ONLY copies the cloud receives. */\nconst MAX_BATCH_PAYLOAD_BYTES = 90 * 1_048_576; // 90MB raw (server inflates to <=100MB)\n\n// ---------------------------------------------------------------------------\n// Payload Construction\n// ---------------------------------------------------------------------------\n\nexport interface RunApiSignal {\n readonly testType: 'api';\n readonly apiProtocol: string;\n readonly metadata: { readonly testType: 'api'; readonly primaryProtocol: string };\n}\n\nexport interface RunTestTypeSignal {\n /** Run-level test type the cloud labels: Web / API / Mobile / Unit. */\n readonly testType: 'e2e' | 'api' | 'mobile' | 'unit';\n /** Dominant API protocol — present only when testType === 'api'. */\n readonly apiProtocol?: string;\n /** Mirrors the cloud contract — present only when testType === 'api'. */\n readonly metadata?: { readonly testType: 'api'; readonly primaryProtocol: string };\n}\n\n/** Non-api per-test slugs eligible for the run-level most-common fallback. */\nconst RUN_NONAPI_SLUGS = new Set(['e2e', 'mobile', 'unit']);\n\n/**\n * Run-level test-type classification for a Playwright run.\n *\n * 1. EVERY test is an API test ⇒ 'api' (+ dominant protocol). Requiring *all*\n * tests avoids misrouting a mixed UI+API run to the API report, and keeps\n * the API-workspace routing (which keys on testType === 'api') intact.\n * 2. else the most-common recognized non-api slug ({e2e, mobile, unit}).\n * 3. else 'e2e' — Playwright is browser-driven, so Web is the capture-truth\n * default for an otherwise-unclassified run.\n */\nexport function deriveRunTestType(\n tests: readonly TestResultForUpload[] | undefined,\n): RunTestTypeSignal {\n if (tests && tests.length > 0 && tests.every((t) => t.testType === 'api')) {\n const counts = new Map<string, number>();\n for (const t of tests) {\n const proto = t.apiProtocol ?? 'rest';\n counts.set(proto, (counts.get(proto) ?? 0) + 1);\n }\n let primaryProtocol = 'rest';\n let max = 0;\n for (const [proto, n] of counts) {\n if (n > max) { max = n; primaryProtocol = proto; }\n }\n return { testType: 'api', apiProtocol: primaryProtocol, metadata: { testType: 'api', primaryProtocol } };\n }\n\n const counts = new Map<string, number>();\n for (const t of tests ?? []) {\n if (RUN_NONAPI_SLUGS.has(t.testType)) counts.set(t.testType, (counts.get(t.testType) ?? 0) + 1);\n }\n let mostCommon: 'e2e' | 'mobile' | 'unit' | null = null;\n let max = 0;\n for (const [slug, n] of counts) {\n if (n > max) { max = n; mostCommon = slug as 'e2e' | 'mobile' | 'unit'; }\n }\n return { testType: mostCommon ?? 'e2e' };\n}\n\n/**\n * Back-compat wrapper preserving the original API-only signal shape (some\n * callers/tests only care about the all-API case). Returns null for non-API runs.\n */\nexport function deriveRunApiSignal(\n tests: readonly TestResultForUpload[] | undefined,\n): RunApiSignal | null {\n const sig = deriveRunTestType(tests);\n if (sig.testType !== 'api') return null;\n return { testType: 'api', apiProtocol: sig.apiProtocol ?? 'rest', metadata: sig.metadata ?? { testType: 'api', primaryProtocol: sig.apiProtocol ?? 'rest' } };\n}\n\nexport function buildUploadPayload(\n report: TestRunReport,\n repoGitId: string,\n git: GitMetadata | null,\n ci: CIMetadata | null,\n tests?: readonly TestResultForUpload[],\n cloudFarm?: {\n cloudPlatform?: 'browserstack' | 'lambdatest';\n cloudBuildId?: string;\n cloudSessionId?: string;\n },\n): RunUploadPayload {\n const environment = cloudFarm?.cloudPlatform\n ? cloudFarm.cloudPlatform\n : ci?.provider && ci.provider !== 'unknown' ? ci.provider : 'local';\n const runType = resolveSdkRunTypeFromEnv();\n // Derive the run-level test type (Web / API / Mobile / Unit). When the run is\n // all-API, also stamp each test's apiProtocol so the API report can show\n // per-test protocol badges; for non-API runs nothing is mutated on the tests.\n // NOTE: a MIXED run deliberately does NOT stamp apiProtocol on its api tests —\n // a leaked per-test protocol would trip the server's \"any protocol => api\"\n // run rule and misroute the whole run. The cloud tree instead defaults the\n // badge from the per-test testType. (See the api-classification regression test.)\n const typeSignal = deriveRunTestType(tests);\n const apiSignal = typeSignal.testType === 'api' ? typeSignal : null;\n const effectiveTests = apiSignal && tests\n ? tests.map((t) => (t.apiProtocol ? t : { ...t, apiProtocol: apiSignal.apiProtocol! }))\n : tests;\n const payload: RunUploadPayload = {\n runId: report.testRunId,\n repoGitId,\n startedAt: report.startedAt,\n summary: report.summary,\n timeline: report.timeline,\n environment,\n ...(runType ? { runType } : {}),\n ...(apiSignal\n ? { testType: apiSignal.testType, apiProtocol: apiSignal.apiProtocol, metadata: apiSignal.metadata }\n : { testType: typeSignal.testType }),\n ...(effectiveTests && effectiveTests.length > 0 ? { tests: effectiveTests } : {}),\n ...(git?.branch ? { branch: git.branch } : {}),\n ...(git?.commitSha ? { commit: git.commitSha } : {}),\n ...(git?.commitMessage ? { commitMessage: git.commitMessage } : {}),\n ...(git?.commitAuthor ? { commitAuthor: git.commitAuthor } : {}),\n ...(report.completedAt ? { finishedAt: report.completedAt } : {}),\n ...(report.totalDuration ? { duration: report.totalDuration } : {}),\n ...(ci?.provider ? { ciProvider: ci.provider } : {}),\n ...(ci?.runUrl ? { ciRunUrl: ci.runUrl } : {}),\n ...(cloudFarm?.cloudPlatform ? { cloudPlatform: cloudFarm.cloudPlatform } : {}),\n ...(cloudFarm?.cloudBuildId ? { cloudBuildId: cloudFarm.cloudBuildId } : {}),\n ...(cloudFarm?.cloudSessionId ? { cloudSessionId: cloudFarm.cloudSessionId } : {}),\n };\n\n // The 413 size-guard / strip lives in prepareBody (the single serialization\n // point), so the payload is measured once and stripped only if it actually\n // exceeds the limit AFTER serialization — and the offline-queue replay path\n // gets the identical treatment for free.\n return payload;\n}\n\n// ---------------------------------------------------------------------------\n// Canonical `apiReport` shape mappers\n//\n// The cloud API-workspace inspector reads `apiReport.apiCalls` (ApiCall shape:\n// statusCode/statusText/ok/durationMs/bytesIn/bytesOut) and\n// `apiReport.protocolAssertions` (kind/description/status/detail). The capture\n// layer records ApiCallRecord (responseStatusCode/responseTimeMs) and\n// ApiAssertion (type/expected/actual/expression), so every cloud payload must\n// map to the canonical shape — otherwise the panel renders empty cells (or, for\n// assertions, nothing at all). Coalescing makes them idempotent if a row is\n// already canonical.\n// ---------------------------------------------------------------------------\n\n/** UTF-8 byte length of a string body (0 for null/non-string). */\nfunction bodyBytes(v: unknown): number {\n return typeof v === 'string' ? Buffer.byteLength(v, 'utf-8') : 0;\n}\n\n/** Map captured API calls to the cloud `apiReport.apiCalls` (ApiCall) shape. */\nexport function toApiReportCalls(\n records: readonly Record<string, unknown>[] | null | undefined,\n): Record<string, unknown>[] {\n if (!Array.isArray(records)) return [];\n return records.map((c) => {\n const statusCode = (c.statusCode ?? c.responseStatusCode ?? null) as number | null;\n return {\n // Stable per-call id so protocolAssertions can be attributed to their call\n // (the assertion carries the matching callId). Preserved end-to-end even\n // though today's single-call inspector doesn't yet key on it.\n ...(c.id != null ? { id: c.id } : {}),\n ...(c.protocol != null ? { protocol: c.protocol } : {}),\n method: (c.method ?? null) as string | null,\n url: (c.url ?? null) as string | null,\n statusCode,\n statusText: (c.statusText ?? c.responseStatusText ?? null) as string | null,\n ok: typeof statusCode === 'number' ? statusCode < 400 : ((c.ok ?? null) as boolean | null),\n durationMs: (c.durationMs ?? c.responseTimeMs ?? null) as number | null,\n bytesIn: typeof c.bytesIn === 'number' ? c.bytesIn : bodyBytes(c.responseBody),\n bytesOut: typeof c.bytesOut === 'number' ? c.bytesOut : bodyBytes(c.requestBody),\n requestHeaders: (c.requestHeaders ?? null) as Record<string, string> | null,\n responseHeaders: (c.responseHeaders ?? null) as Record<string, string> | null,\n requestBody: (c.requestBody ?? null) as string | null,\n responseBody: (c.responseBody ?? null) as string | null,\n requestCookies: (c.requestCookies ?? null) as Record<string, unknown>[] | null,\n responseCookies: (c.responseCookies ?? null) as Record<string, unknown>[] | null,\n };\n });\n}\n\n/** Map captured API calls to the generic Network-inspector (network.json) shape. */\nexport function toNetworkRequestsFromApiCalls(\n records: readonly Record<string, unknown>[] | null | undefined,\n): Record<string, unknown>[] {\n if (!Array.isArray(records)) return [];\n return records.map((c) => {\n const status = (c.responseStatusCode ?? c.statusCode ?? 0) as number;\n const ts = (c.timestamp ?? null) as string | null;\n return {\n method: c.method ?? 'GET',\n url: c.url ?? '',\n status,\n statusCode: status,\n type: 'xhr',\n size: bodyBytes(c.responseBody),\n duration: (c.responseTimeMs ?? c.durationMs ?? 0) as number,\n responseTimeMs: (c.responseTimeMs ?? c.durationMs ?? 0) as number,\n requestHeaders: c.requestHeaders ?? null,\n responseHeaders: c.responseHeaders ?? null,\n requestBody: c.requestBody ?? null,\n responseBody: c.responseBody ?? null,\n timestamp: ts,\n startedAt: ts,\n };\n });\n}\n\n/** Map captured assertions to the cloud `apiReport.protocolAssertions` shape. */\nexport function toProtocolAssertions(\n assertions: readonly Record<string, unknown>[] | null | undefined,\n): Record<string, unknown>[] {\n if (!Array.isArray(assertions)) return [];\n const fmt = (v: unknown): string => {\n if (v === null || v === undefined) return String(v);\n if (typeof v === 'string') return v;\n try { return JSON.stringify(v); } catch { return String(v); }\n };\n return assertions.map((a) => {\n const status = a.status === 'failed' ? 'failed' : 'passed';\n const expr = typeof a.expression === 'string' && a.expression.trim() ? a.expression.trim() : null;\n const kind = (a.kind ?? a.type ?? 'assertion') as string;\n const detail =\n status === 'failed'\n ? `expected ${fmt(a.expected)}, received ${fmt(a.actual)}`\n : a.expected !== undefined\n ? `expected ${fmt(a.expected)}`\n : null;\n return {\n // callId links the assertion to its ApiCallRecord (preserved so a future\n // multi-call inspector can attribute assertions per request; harmless for\n // the current single-call view, which shows all of a test's assertions).\n ...(a.callId != null ? { callId: a.callId } : {}),\n kind,\n description: expr ?? (a.description as string | undefined) ?? `${kind} assertion`,\n status,\n detail,\n };\n });\n}\n\n// ---------------------------------------------------------------------------\n// Retry Logic\n// ---------------------------------------------------------------------------\n\nasync function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/** Strip null/undefined values from an object before sending to the cloud API. */\nfunction stripNulls(obj: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(obj)) {\n if (val !== null && val !== undefined) result[key] = val;\n }\n return result;\n}\n\n/**\n * Retry a fetch request with exponential backoff.\n * Handles 429 rate limits (reads Retry-After header), 5xx errors, and network failures.\n * Returns the response on success, or null after all retries exhausted.\n */\nasync function retryWithBackoff(\n url: string,\n init: RequestInit,\n onTokenRefresh?: () => Promise<string | null>,\n timeoutMs: number = DEFAULT_REQUEST_TIMEOUT_MS,\n budget?: Budget,\n): Promise<Response | null> {\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n // ⛔ Refuse rather than start: a request begun with no budget left still\n // pays its own timeout, which is how one stalled upload became ten.\n if (budget?.expired()) return null;\n const slice = budget ? budget.slice(timeoutMs) : timeoutMs;\n if (slice <= 0) return null;\n try {\n const response = await withTimeout(url, init, slice);\n\n if (response.ok) return response;\n\n // 401 — attempt token refresh and retry once\n if (response.status === 401 && onTokenRefresh && attempt === 0) {\n const newToken = await onTokenRefresh();\n if (newToken) {\n const refreshedInit = {\n ...init,\n headers: { ...init.headers as Record<string, string>, Authorization: `Bearer ${newToken}` },\n };\n const retryResponse = await withTimeout(url, refreshedInit, slice);\n if (retryResponse.ok) return retryResponse;\n }\n return null; // Refresh failed or retry failed\n }\n\n // 429 — rate limited, respect Retry-After header\n if (response.status === 429 && attempt < MAX_RETRIES - 1) {\n const retryAfter = response.headers.get('Retry-After');\n const waitMs = retryAfter ? parseInt(retryAfter, 10) * 1000 : RETRY_DELAYS_MS[attempt];\n if (!isNaN(waitMs) && waitMs > 0) {\n await sleep(waitMs);\n } else {\n await sleep(RETRY_DELAYS_MS[attempt]);\n }\n continue;\n }\n\n // 5xx — retry with backoff\n if (response.status >= 500 && attempt < MAX_RETRIES - 1) {\n await sleep(RETRY_DELAYS_MS[attempt]);\n continue;\n }\n\n // Return the response for caller-level handling (e.g., 409)\n return response;\n } catch {\n // Network error — retry with backoff\n if (attempt < MAX_RETRIES - 1) {\n await sleep(RETRY_DELAYS_MS[attempt]);\n continue;\n }\n return null;\n }\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Compression\n// ---------------------------------------------------------------------------\n\nconst gzipAsync = promisify(gzip);\n\n/** A batch upload's per-test rows carry the heavy artifact arrays. */\ntype TestsCarrier = { tests?: Array<Record<string, unknown>> } & Record<string, unknown>;\n\n/**\n * Shed the heaviest per-test arrays so an oversized batch fits under the server\n * limit instead of being lost whole to a 413. `dropApiCalls=false` keeps apiCalls\n * (the API-workspace inspector needs them); `true` drops them too as a last resort.\n */\nfunction stripOversizeTests(payload: TestsCarrier, dropApiCalls: boolean): TestsCarrier {\n if (!Array.isArray(payload.tests)) return payload;\n const tests = payload.tests.map((t) => {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { consoleLogs: _c, networkRequests: _n, apiCalls, ...rest } = t;\n return dropApiCalls ? rest : { ...rest, ...(apiCalls !== undefined ? { apiCalls } : {}) };\n });\n return { ...payload, tests };\n}\n\n/** Every heavy per-test array carried in the batch payload (besides the failure blob). */\n// `visual` belongs here for the same reason as the rest: a comparison carries\n// up to DEFAULT_MAX_CHANGES element records, each with its style, attr and\n// text diffs, so a large visual suite is tens of KB per comparison — not the\n// \"few hundred bytes\" the field's own doc comment once claimed. Omitting it\n// broke the tier contract: tier 3 promises to shed EVERY heavy array, and an\n// oversize visual run instead fell through to tier 4, which drops every\n// per-test row to save the summary. Losing visual detail is the intended\n// sacrifice; losing every test result is not.\nconst HEAVY_TEST_KEYS = ['consoleLogs', 'networkRequests', 'apiCalls', 'actions', 'navigations', 'protocolAssertions', 'visual'];\n\n/**\n * Tier-3 strip: keep only lightweight per-test metadata — drop EVERY heavy array and\n * trim the failure to its message/line — so the per-test results (status, timing,\n * identity) still land even when console/network/apiCalls alone weren't enough.\n */\nfunction stripToMinimalTests(payload: TestsCarrier): TestsCarrier {\n if (!Array.isArray(payload.tests)) return payload;\n const heavy = new Set(HEAVY_TEST_KEYS);\n const tests = payload.tests.map((t) => {\n const out: Record<string, unknown> = {};\n for (const k in t) {\n if (heavy.has(k)) continue;\n if (k === 'failure' && t[k] && typeof t[k] === 'object') {\n const f = t[k] as Record<string, unknown>;\n out.failure = { message: f.message ?? null, line: f.line ?? null };\n } else {\n out[k] = t[k];\n }\n }\n return out;\n });\n return { ...payload, tests };\n}\n\n/**\n * Tier-4 strip (absolute last resort): drop the per-test rows and any run-level heavy\n * arrays entirely, so the run record + summary still land instead of the WHOLE upload\n * being lost to a 413. Bounds the body to the run summary + metadata.\n */\nfunction stripAllTests(payload: TestsCarrier): TestsCarrier {\n const heavyRunKeys = new Set(['tests', 'timeline', 'unifiedTimeline', 'navigations']);\n const out: Record<string, unknown> = {};\n for (const k in payload) if (!heavyRunKeys.has(k)) out[k] = (payload as Record<string, unknown>)[k];\n out.tests = [];\n return out as TestsCarrier;\n}\n\n/**\n * Serialize a batch payload ONCE, apply the 413 size-guard, and gzip large bodies\n * off the main thread. The server enforces its 100MB limit on the DECOMPRESSED body\n * and we gzip here, so the guard trips only for an extreme outlier; when it does it\n * sheds detail in tiers, RE-MEASURING after each, until the body fits — so a run is\n * NEVER lost whole to a 413 (the worst case still uploads the run summary). The tiers:\n * 1. drop consoleLogs/networkRequests (keep apiCalls for the inspector)\n * 2. also drop apiCalls\n * 3. shed all remaining per-test arrays (actions/navigations/assertions) + failure detail\n * 4. drop the per-test rows entirely — run summary only (absolute last resort)\n * Each tier warns; tier 4 is an error. Exported so the offline-queue replay path\n * compresses identically AND bounds the body the same way (otherwise a queued oversize\n * payload re-413s on every flush forever).\n */\nexport async function prepareBody(\n payload: unknown,\n maxBytes: number = MAX_BATCH_PAYLOAD_BYTES,\n): Promise<{ body: BodyInit; headers: Record<string, string> }> {\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n let json = JSON.stringify(payload);\n const carrier = payload as TestsCarrier;\n const testCount = Array.isArray(carrier?.tests) ? carrier.tests.length : 0;\n const over = (s: string): boolean => Buffer.byteLength(s, 'utf-8') > maxBytes;\n\n if (testCount > 0 && over(json)) {\n const limitMb = Math.max(1, Math.round(maxBytes / 1_048_576));\n\n // Tier 1 — drop consoleLogs/networkRequests, keep apiCalls.\n process.stderr.write(\n `[testrelic] WARNING: batch payload exceeds ${limitMb}MB — dropping consoleLogs/networkRequests ` +\n `from ${testCount} test(s) to avoid HTTP 413. Console/network detail will be missing in the cloud.\\n`,\n );\n json = JSON.stringify(stripOversizeTests(carrier, false));\n\n // Tier 2 — also drop apiCalls.\n if (over(json)) {\n process.stderr.write(\n `[testrelic] WARNING: batch still exceeds ${limitMb}MB after dropping logs — also dropping apiCalls ` +\n `from ${testCount} test(s). API request/response detail will be missing in the cloud.\\n`,\n );\n json = JSON.stringify(stripOversizeTests(carrier, true));\n }\n\n // Tier 3 — shed every remaining heavy per-test array + trim failure detail.\n if (over(json)) {\n process.stderr.write(\n `[testrelic] WARNING: batch still exceeds ${limitMb}MB — shedding remaining per-test detail ` +\n `(actions/navigations/assertions) from ${testCount} test(s). Only test results + summary will reach the cloud.\\n`,\n );\n json = JSON.stringify(stripToMinimalTests(carrier));\n }\n\n // Tier 4 — last resort: drop per-test rows so the run summary still lands.\n if (over(json)) {\n process.stderr.write(\n `[testrelic] ERROR: batch still exceeds ${limitMb}MB after shedding all per-test detail — uploading ` +\n `run summary only (${testCount} per-test result(s) omitted) to avoid losing the run to HTTP 413.\\n`,\n );\n json = JSON.stringify(stripAllTests(carrier));\n }\n }\n\n if (Buffer.byteLength(json, 'utf-8') > GZIP_THRESHOLD_BYTES) {\n headers['Content-Encoding'] = 'gzip';\n const gz = await gzipAsync(Buffer.from(json, 'utf-8'));\n // A zero-copy view over the same bytes. Buffer is a Uint8Array, but its\n // ArrayBufferLike generic admits SharedArrayBuffer and so keeps it out of\n // BodyInit.\n //\n // SAFETY: a zlib result is always backed by a plain ArrayBuffer, never a\n // shared one, so narrowing the buffer is sound and avoids copying an\n // already-large compressed payload.\n const backing = gz.buffer as ArrayBuffer;\n return { body: new Uint8Array(backing, gz.byteOffset, gz.byteLength), headers };\n }\n return { body: json, headers };\n}\n\n// ---------------------------------------------------------------------------\n// Batch Upload\n// ---------------------------------------------------------------------------\n\n/**\n * Upload a complete test run to the cloud.\n * POST /runs with the full payload.\n * Returns a structured result (never throws).\n */\nexport async function uploadBatchRun(\n endpoint: string,\n accessToken: string,\n payload: RunUploadPayload,\n onTokenRefresh?: () => Promise<string | null>,\n timeoutMs?: number,\n budget?: Budget,\n): Promise<UploadResult | UploadFailure> {\n const url = `${endpoint}/runs`;\n const { body, headers } = await prepareBody(payload);\n headers['Authorization'] = `Bearer ${accessToken}`;\n\n const response = await retryWithBackoff(\n url,\n { method: 'POST', headers, body },\n onTokenRefresh,\n timeoutMs,\n budget,\n );\n\n if (response?.ok) {\n return { success: true, statusCode: response.status, error: null };\n }\n\n // 409 — duplicate run, treat as success (data already ingested)\n if (response?.status === 409) {\n return { success: true, statusCode: 409, error: null };\n }\n\n return {\n success: false,\n reason: response ? `Upload failed with status ${response.status}` : 'Upload failed after retries',\n statusCode: response?.status ?? null,\n payload: payload as unknown as Record<string, unknown>,\n targetEndpoint: url,\n method: 'POST',\n };\n}\n\n// ---------------------------------------------------------------------------\n// Realtime Upload Functions\n// ---------------------------------------------------------------------------\n\nexport interface RealtimeInitResult {\n readonly runId: string;\n}\n\n/**\n * Initialize a realtime run on the cloud.\n * POST /runs/init\n */\nexport async function initRealtimeRun(\n endpoint: string,\n accessToken: string,\n payload: {\n runId: string;\n repoGitId: string;\n branch: string | null;\n commit: string | null;\n commitMessage?: string | null;\n commitAuthor?: string | null;\n startedAt: string;\n totalTests: number | null;\n ciProvider: string | null;\n ciRunUrl: string | null;\n environment?: string | null;\n cloudPlatform?: string | null;\n cloudBuildId?: string | null;\n cloudSessionId?: string | null;\n runType?: SdkRunType;\n /** Run-level test-type default (e.g. 'e2e'); finalize overwrites with the derived slug. */\n testType?: string;\n },\n timeoutMs?: number,\n): Promise<RealtimeInitResult | null> {\n const url = `${endpoint}/runs/init`;\n try {\n const response = await withTimeout(\n url,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${accessToken}`,\n },\n body: JSON.stringify(stripNulls(payload as unknown as Record<string, unknown>)),\n },\n timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,\n );\n if (!response.ok) return null;\n const responseBody = await response.json() as Record<string, unknown>;\n return { runId: responseBody.runId as string };\n } catch {\n return null;\n }\n}\n\n/**\n * Upload a single test result to the cloud (fire-and-forget).\n * POST /runs/{runId}/tests\n */\nexport function uploadTestResult(\n endpoint: string,\n accessToken: string,\n cloudRunId: string,\n testData: Record<string, unknown>,\n): void {\n const url = `${endpoint}/runs/${cloudRunId}/tests`;\n // Fire-and-forget — intentionally not awaited\n void fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${accessToken}`,\n },\n body: JSON.stringify(testData),\n }).catch(() => {\n // Silently ignore realtime upload errors\n });\n}\n\n/**\n * Finalize a realtime run on the cloud with retry and backoff.\n * POST /runs/{runId}/finalize\n */\nexport async function finalizeRun(\n endpoint: string,\n accessToken: string,\n cloudRunId: string,\n payload: {\n finishedAt: string;\n duration: number;\n summary: Summary;\n commitMessage?: string | null;\n /** Run-level API signal — realtime init ran before any test, so the server learns the run is API here. */\n testType?: string;\n apiProtocol?: string;\n metadata?: Record<string, unknown>;\n },\n): Promise<boolean> {\n const url = `${endpoint}/runs/${cloudRunId}/finalize`;\n const response = await retryWithBackoff(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${accessToken}`,\n },\n body: JSON.stringify(payload),\n });\n return response?.ok ?? false;\n}\n\n/**\n * Fire-and-forget finalize for use in signal handlers (SIGINT/SIGTERM).\n * Does not await the response — the process may exit before it completes,\n * but the HTTP request will still be dispatched to the OS network stack.\n */\nexport function finalizeRunFireAndForget(\n endpoint: string,\n accessToken: string,\n cloudRunId: string,\n startedAt: string,\n): void {\n const url = `${endpoint}/runs/${cloudRunId}/finalize`;\n const now = new Date();\n const payload = {\n finishedAt: now.toISOString(),\n duration: now.getTime() - new Date(startedAt).getTime(),\n summary: {},\n };\n void fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${accessToken}`,\n },\n body: JSON.stringify(payload),\n }).catch(() => {\n // Best-effort — silently ignore errors in signal handlers\n });\n}\n","/**\n * Cloud queue — offline storage and retry for failed uploads.\n *\n * Writes failed upload payloads to individual JSON files in .testrelic/queue/.\n * Flushes queued files on next successful auth, oldest first (FIFO).\n *\n * Constitution Principle VI: all I/O errors caught, never crash test run.\n * Constitution Principle VIII: atomic writes (tmp → rename).\n */\n\nimport {\n mkdirSync,\n writeFileSync,\n readFileSync,\n readdirSync,\n unlinkSync,\n renameSync,\n statSync,\n} from 'node:fs';\nimport { join } from 'node:path';\nimport type { QueueEntry } from '@testrelic/core';\nimport { isValidQueueEntry } from '@testrelic/core';\nimport { prepareBody } from './cloud-upload.js';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst QUEUE_ENTRY_VERSION = '1.0.0';\nconst FLUSH_BATCH_SIZE = 10;\n\n/** Best-effort message for a caught unknown, for queue-flush diagnostics. */\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/** A client error that will never succeed on replay, so the queued entry should be\n * retired rather than retried forever. 408 (timeout) and 429 (rate-limit) ARE\n * transient and are treated as retryable. */\nfunction isPermanentHttpFailure(status: number): boolean {\n return status >= 400 && status < 500 && status !== 408 && status !== 429;\n}\n\n// ---------------------------------------------------------------------------\n// Write to Queue\n// ---------------------------------------------------------------------------\n\n/**\n * Write a failed upload payload to the offline queue.\n * Creates the queue directory if needed. Uses atomic write.\n * Silently fails on disk errors (ENOSPC, permissions).\n */\nexport function writeToQueue(\n queueDirectory: string,\n runId: string,\n type: string,\n reason: string,\n targetEndpoint: string,\n method: string,\n payload: Record<string, unknown>,\n headers: Record<string, string>,\n): void {\n try {\n mkdirSync(queueDirectory, { recursive: true });\n\n const timestamp = Date.now();\n const filename = `${timestamp}-${runId}-${type}.json`;\n const filePath = join(queueDirectory, filename);\n\n const entry: QueueEntry = {\n version: QUEUE_ENTRY_VERSION,\n queuedAt: new Date(timestamp).toISOString(),\n reason,\n retryCount: 0,\n targetEndpoint,\n method,\n payload,\n headers,\n };\n\n const tmpPath = filePath + '.tmp';\n writeFileSync(tmpPath, JSON.stringify(entry, null, 2), 'utf-8');\n renameSync(tmpPath, filePath);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOSPC') {\n process.stderr.write('⚠ TestRelic: Disk full — unable to queue upload data.\\n');\n } else {\n process.stderr.write('⚠ TestRelic: Unable to queue upload data.\\n');\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Queue Flush\n// ---------------------------------------------------------------------------\n\n/**\n * Flush queued uploads to the cloud. Oldest first (FIFO).\n * Stops on first failure (circuit-breaker).\n * Processes in batches of 10 for large queues.\n */\nexport async function flushQueue(\n queueDirectory: string,\n endpoint: string,\n accessToken: string,\n): Promise<void> {\n let files: string[];\n try {\n files = readdirSync(queueDirectory)\n .filter((f) => f.endsWith('.json') && !f.endsWith('.tmp'))\n .sort(); // Lexicographic sort = FIFO by timestamp prefix\n } catch {\n return; // Queue directory doesn't exist or unreadable\n }\n\n if (files.length === 0) return;\n\n // Process in batches\n const batches: string[][] = [];\n for (let i = 0; i < files.length; i += FLUSH_BATCH_SIZE) {\n batches.push(files.slice(i, i + FLUSH_BATCH_SIZE));\n }\n\n for (const batch of batches) {\n for (const file of batch) {\n const filePath = join(queueDirectory, file);\n\n // Stage 1 — load + validate the entry. A bad/unreadable file is a PER-FILE\n // problem: delete it (with a warning) and move on, so one poison entry at the\n // FIFO head can't wedge every later entry behind it (cleanupExpiredQueue would\n // otherwise only clear it by age).\n let queueEntry: QueueEntry;\n try {\n const stat = statSync(filePath);\n if (!stat.isFile()) continue;\n const entry = JSON.parse(readFileSync(filePath, 'utf-8')) as unknown;\n if (!isValidQueueEntry(entry)) {\n process.stderr.write(`⚠ TestRelic: Corrupted queue file deleted: ${file}\\n`);\n unlinkSync(filePath);\n continue;\n }\n queueEntry = entry as QueueEntry;\n } catch (err) {\n process.stderr.write(`⚠ TestRelic: Skipping unreadable queue file ${file}: ${errorMessage(err)}\\n`);\n try { unlinkSync(filePath); } catch { /* ignore */ }\n continue;\n }\n\n // Stage 2 — replay the upload. A failure here is a SERVER/NETWORK problem, not a\n // per-file one, so it's handled distinctly: a permanent rejection retires the\n // entry; a transient one pauses the flush (with a log, so a stuck queue is\n // diagnosable instead of silently stalling).\n try {\n // Replay through the SAME prepareBody used by the direct upload path, so the\n // gzip/Content-Encoding (and the tiered 413 size-guard) stay in lockstep — a\n // payload gzipped or shed on first send must replay identically.\n const { body: flushBody, headers: prepared } = await prepareBody(queueEntry.payload);\n const flushHeaders: Record<string, string> = {\n ...queueEntry.headers,\n ...prepared,\n 'Authorization': `Bearer ${accessToken}`,\n };\n const response = await fetch(queueEntry.targetEndpoint, {\n method: queueEntry.method,\n headers: flushHeaders,\n body: flushBody,\n });\n\n if (response.ok) {\n unlinkSync(filePath);\n } else if (isPermanentHttpFailure(response.status)) {\n // A permanent client error (400/401/403/413/…) will never succeed on replay —\n // move the entry aside so it can't wedge the queue forever.\n process.stderr.write(\n `⚠ TestRelic: Queue entry ${file} rejected (HTTP ${response.status}) — moving aside; it will not be retried.\\n`,\n );\n try { renameSync(filePath, `${filePath}.failed`); }\n catch { try { unlinkSync(filePath); } catch { /* ignore */ } }\n } else {\n // Transient server error (5xx / 408 / 429) — stop and retry the whole queue\n // on the next flush; don't hammer a down server.\n process.stderr.write(\n `⚠ TestRelic: Queue flush paused — server returned HTTP ${response.status} for ${file}; will retry later.\\n`,\n );\n return;\n }\n } catch (err) {\n // Network failure / fetch rejection — server unreachable. Stop and retry the\n // whole queue next flush; log so a persistently stuck queue is visible.\n process.stderr.write(`⚠ TestRelic: Queue flush paused — ${errorMessage(err)}; will retry later.\\n`);\n return;\n }\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Retention Cleanup\n// ---------------------------------------------------------------------------\n\n/**\n * Delete queue files older than maxAge (in milliseconds).\n * Runs before flush on startup. Also ages out `.failed` entries (permanently-rejected\n * uploads moved aside by flushQueue) so they don't accumulate on disk forever — they\n * carry the same `queuedAt`, so the same retention applies.\n */\nexport function cleanupExpiredQueue(\n queueDirectory: string,\n maxAge: number,\n): void {\n try {\n const files = readdirSync(queueDirectory)\n .filter((f) => (f.endsWith('.json') || f.endsWith('.failed')) && !f.endsWith('.tmp'));\n\n const now = Date.now();\n\n for (const file of files) {\n const filePath = join(queueDirectory, file);\n try {\n const raw = readFileSync(filePath, 'utf-8');\n const entry = JSON.parse(raw) as Record<string, unknown>;\n const queuedAt = entry.queuedAt;\n\n if (typeof queuedAt === 'string') {\n const queuedTime = new Date(queuedAt).getTime();\n if (now - queuedTime > maxAge) {\n unlinkSync(filePath);\n }\n }\n } catch {\n // Corrupted file — delete\n try { unlinkSync(filePath); } catch { /* ignore */ }\n }\n }\n } catch {\n // Queue directory doesn't exist — nothing to clean\n }\n}\n","/**\n * CloudClient — manages cloud connectivity lifecycle.\n *\n * Handles auth, mode transitions, repo resolution, token refresh,\n * and queue flush orchestration. Never throws — all errors result\n * in graceful local-mode fallback.\n *\n * Constitution Principle VI: all timers/intervals tracked and cleaned up in dispose().\n */\n\nimport { readFileSync, writeFileSync, mkdirSync, renameSync, existsSync } from 'node:fs';\nimport { join, dirname, basename } from 'node:path';\nimport { createHash } from 'node:crypto';\nimport type { CloudConfig, AuthState, AuthMode, GitMetadata } from '@testrelic/core';\nimport {\n enforceHttps,\n healthCheck,\n exchangeToken,\n refreshAccessToken,\n isAuthError,\n resolveRepo,\n} from './cloud-auth.js';\nimport type { TokenExchangeResult } from './cloud-auth.js';\nimport { collectGitMetadata, normalizeGitRemoteUrl, deriveRepoDisplayName, deriveNonGitProjectId, readPackageJsonName } from './git-metadata.js';\nimport { cleanupExpiredQueue, flushQueue } from './cloud-queue.js';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst TOKEN_REFRESH_BUFFER_MS = 300_000; // 5 minutes\nconst PROJECT_CACHE_TTL_MS = 86_400_000; // 24 hours\nconst HEALTH_CHECK_INTERVAL_MS = 60_000; // 60 seconds\nconst FLUSH_TIMEOUT_MS = 30_000; // 30 seconds\nconst DASHBOARD_SETTINGS_URL = 'https://platform.testrelic.ai/settings/api-keys';\n\n/**\n * A timeout promise whose timer is `unref()`d.\n *\n * These promises are used as the losing side of `Promise.race`, so when the real\n * work wins the timer is still armed. A ref'd timer would keep Node's event loop\n * alive — up to `FLUSH_TIMEOUT_MS` of dead air after every test run finished.\n * Telemetry must never be the reason `playwright test` doesn't exit.\n */\nfunction unrefTimeout(ms: number): Promise<void> {\n return new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, ms);\n if (typeof timer?.unref === 'function') timer.unref();\n });\n}\n\n// ---------------------------------------------------------------------------\n// Repo Cache\n// ---------------------------------------------------------------------------\n\ninterface RepoCache {\n readonly repoId: string;\n readonly gitId: string;\n readonly displayName: string;\n readonly resolvedAt: number;\n readonly apiKeyHash: string;\n}\n\n// ---------------------------------------------------------------------------\n// CloudClient\n// ---------------------------------------------------------------------------\n\nexport class CloudClient {\n private config: CloudConfig | null;\n private authState: AuthState;\n private gitMetadata: GitMetadata | null = null;\n private repoId: string | null = null;\n private failureReason: string | null = null;\n private flushPromise: Promise<void> | null = null;\n private healthCheckTimer: ReturnType<typeof setInterval> | null = null;\n\n constructor(cloudConfig: CloudConfig | null) {\n this.config = cloudConfig;\n this.authState = {\n mode: 'pending',\n accessToken: null,\n refreshToken: null,\n expiresAt: null,\n orgId: null,\n orgName: null,\n userId: null,\n userName: null,\n };\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n\n async initialize(): Promise<void> {\n // No config or no API key → local mode immediately\n if (!this.config || !this.config.apiKey) {\n this.setLocalMode('no_api_key');\n process.stderr.write('ℹ TestRelic: No API key configured. Running in local mode.\\n');\n return;\n }\n\n try {\n // HTTPS enforcement\n enforceHttps(this.config.endpoint);\n\n // Collect git metadata\n this.gitMetadata = collectGitMetadata();\n\n // Health check\n const healthy = await healthCheck(this.config.endpoint);\n if (!healthy) {\n this.setLocalMode('cloud_unreachable');\n process.stderr.write('⚠ TestRelic: Cloud unreachable. Switching to local mode. Data will be queued for later upload.\\n');\n return;\n }\n\n // Token exchange\n const result = await exchangeToken(this.config.endpoint, this.config.apiKey, this.config.timeout);\n if (isAuthError(result)) {\n this.handleAuthError(result.code, result.statusCode);\n return;\n }\n\n // Success — cloud mode\n const tokenResult = result as TokenExchangeResult;\n this.authState = {\n mode: 'cloud',\n accessToken: tokenResult.accessToken,\n refreshToken: tokenResult.refreshToken,\n expiresAt: Date.now() + tokenResult.expiresIn * 1000,\n orgId: tokenResult.orgId,\n orgName: tokenResult.orgName,\n userId: tokenResult.userId,\n userName: tokenResult.userName,\n };\n\n process.stderr.write(`✓ TestRelic: Connected to cloud (${tokenResult.orgName} / ${tokenResult.userName})\\n`);\n\n // Repo resolution\n await this.resolveRepoId();\n\n // Queue cleanup and background flush\n if (this.config.queueDirectory) {\n cleanupExpiredQueue(this.config.queueDirectory, this.config.queueMaxAge);\n this.startBackgroundFlush();\n }\n\n // Start periodic health check for network recovery\n this.startHealthCheck();\n } catch (err) {\n this.setLocalMode('unexpected_error');\n process.stderr.write(`⚠ TestRelic: Unexpected error during cloud initialization. Running in local mode. ${err instanceof Error ? err.message : String(err)}\\n`);\n }\n }\n\n getMode(): AuthMode {\n return this.authState.mode;\n }\n\n isCloudMode(): boolean {\n return this.authState.mode === 'cloud';\n }\n\n isLocalMode(): boolean {\n return this.authState.mode === 'local';\n }\n\n getAccessToken(): string | null {\n return this.authState.accessToken;\n }\n\n getRepoId(): string | null {\n return this.repoId;\n }\n\n getGitMetadata(): GitMetadata | null {\n return this.gitMetadata;\n }\n\n getConfig(): CloudConfig | null {\n return this.config;\n }\n\n getFailureReason(): string | null {\n return this.failureReason;\n }\n\n getEndpoint(): string {\n return this.config?.endpoint ?? 'https://platform.testrelic.ai/api/v1';\n }\n\n /**\n * Ensure the access token is valid. Refreshes if expiring within 5 minutes.\n * On refresh failure, switches to local mode.\n */\n async ensureValidToken(): Promise<boolean> {\n if (!this.isCloudMode() || !this.authState.accessToken) return false;\n\n const expiresAt = this.authState.expiresAt ?? 0;\n if (Date.now() + TOKEN_REFRESH_BUFFER_MS < expiresAt) {\n return true; // Token still valid\n }\n\n // Attempt refresh\n if (!this.config || !this.authState.refreshToken) {\n this.switchToLocalMode('token_expired_no_refresh');\n return false;\n }\n\n try {\n const refreshResult = await refreshAccessToken(\n this.config.endpoint,\n this.authState.refreshToken,\n this.config.timeout,\n );\n\n if (!refreshResult) {\n this.switchToLocalMode('token_refresh_failed');\n return false;\n }\n\n this.authState.accessToken = refreshResult.accessToken;\n this.authState.refreshToken = refreshResult.refreshToken;\n this.authState.expiresAt = Date.now() + refreshResult.expiresIn * 1000;\n return true;\n } catch {\n this.switchToLocalMode('token_refresh_error');\n return false;\n }\n }\n\n /**\n * Switch from cloud to local mode. One-way transition during a run.\n */\n switchToLocalMode(reason: string): void {\n if (this.authState.mode === 'local') return;\n this.authState.mode = 'local';\n this.failureReason = reason;\n process.stderr.write(`⚠ TestRelic: Switched to local mode (${reason}).\\n`);\n }\n\n /**\n * Clean up all resources: timers, pending requests, state.\n */\n async dispose(): Promise<void> {\n // Stop health check\n if (this.healthCheckTimer) {\n clearInterval(this.healthCheckTimer);\n this.healthCheckTimer = null;\n }\n\n // Wait for background flush (with timeout)\n if (this.flushPromise) {\n try {\n await Promise.race([\n this.flushPromise,\n unrefTimeout(FLUSH_TIMEOUT_MS),\n ]);\n } catch {\n // Ignore flush errors during dispose\n }\n this.flushPromise = null;\n }\n\n // Clear auth state\n this.authState = {\n mode: 'local',\n accessToken: null,\n refreshToken: null,\n expiresAt: null,\n orgId: null,\n orgName: null,\n userId: null,\n userName: null,\n };\n this.repoId = null;\n this.gitMetadata = null;\n }\n\n // -------------------------------------------------------------------------\n // Private Methods\n // -------------------------------------------------------------------------\n\n private setLocalMode(reason: string): void {\n this.authState.mode = 'local';\n this.failureReason = reason;\n }\n\n private handleAuthError(code: string, statusCode: number | null): void {\n switch (code) {\n case 'invalid_key':\n this.setLocalMode('invalid_api_key');\n process.stderr.write(\n `⚠ TestRelic: API key is invalid or revoked. Running in local mode.\\n` +\n ` → Manage keys: ${DASHBOARD_SETTINGS_URL}\\n`,\n );\n break;\n case 'expired_key':\n this.setLocalMode('expired_api_key');\n process.stderr.write(\n `⚠ TestRelic: API key has expired. Running in local mode.\\n` +\n ` → Generate a new key: ${DASHBOARD_SETTINGS_URL}\\n`,\n );\n break;\n case 'validation_error':\n this.setLocalMode('validation_error');\n process.stderr.write(\n `⚠ TestRelic: API key format invalid. Running in local mode.\\n` +\n ` → Manage keys: ${DASHBOARD_SETTINGS_URL}\\n`,\n );\n break;\n case 'rate_limited':\n this.setLocalMode('rate_limited');\n process.stderr.write('⚠ TestRelic: Rate limited during authentication. Running in local mode.\\n');\n break;\n case 'timeout':\n this.setLocalMode('auth_timeout');\n process.stderr.write('⚠ TestRelic: Authentication timed out. Running in local mode.\\n');\n break;\n default:\n this.setLocalMode(`auth_error_${statusCode ?? 'unknown'}`);\n process.stderr.write('⚠ TestRelic: Cloud authentication failed. Running in local mode.\\n');\n break;\n }\n }\n\n private async resolveRepoId(): Promise<void> {\n if (!this.config || !this.authState.accessToken) return;\n\n const gitId = this.gitMetadata?.remoteUrl\n ? normalizeGitRemoteUrl(this.gitMetadata.remoteUrl)\n : null;\n\n const displayName = gitId\n ? deriveRepoDisplayName(gitId)\n : this.config.projectName ?? deriveNonGitProjectId(process.cwd());\n\n const effectiveGitId = gitId ?? (this.config.projectName ?? deriveNonGitProjectId(process.cwd()));\n\n // Check cache first\n const cached = this.readRepoCache(effectiveGitId);\n if (cached) {\n this.repoId = cached.repoId;\n return;\n }\n\n // Resolve from cloud\n try {\n const resolved = await resolveRepo(\n this.config.endpoint,\n this.authState.accessToken,\n effectiveGitId,\n displayName,\n this.config.timeout,\n this.gitMetadata?.branch,\n );\n\n if (resolved) {\n this.repoId = resolved.repoId;\n this.writeRepoCache(effectiveGitId, resolved.repoId, resolved.displayName);\n }\n } catch {\n // Non-fatal — repo resolution failure doesn't block uploads\n }\n\n if (!gitId && !this.config.projectName && !readPackageJsonName(process.cwd())) {\n process.stderr.write(\n 'ℹ TestRelic: No git remote or package.json detected. Set project.name in .testrelic for stable project identity.\\n',\n );\n }\n }\n\n private readRepoCache(gitId: string): RepoCache | null {\n if (!this.config) return null;\n const cachePath = join(this.config.queueDirectory, '..', 'cache', 'repo.json');\n try {\n if (!existsSync(cachePath)) return null;\n const raw = readFileSync(cachePath, 'utf-8');\n const cache = JSON.parse(raw) as RepoCache;\n if (cache.gitId !== gitId) return null;\n if (Date.now() - cache.resolvedAt > PROJECT_CACHE_TTL_MS) return null;\n // Check API key hash matches\n const currentKeyHash = this.hashApiKey();\n if (currentKeyHash && cache.apiKeyHash !== currentKeyHash) return null;\n return cache;\n } catch {\n return null;\n }\n }\n\n private writeRepoCache(gitId: string, repoId: string, displayName: string): void {\n if (!this.config) return;\n const cacheDir = join(this.config.queueDirectory, '..', 'cache');\n const cachePath = join(cacheDir, 'repo.json');\n try {\n mkdirSync(cacheDir, { recursive: true });\n const cache: RepoCache = {\n repoId,\n gitId,\n displayName,\n resolvedAt: Date.now(),\n apiKeyHash: this.hashApiKey() ?? '',\n };\n const tmpPath = cachePath + '.tmp';\n writeFileSync(tmpPath, JSON.stringify(cache, null, 2), 'utf-8');\n renameSync(tmpPath, cachePath);\n } catch {\n // Non-fatal\n }\n }\n\n private hashApiKey(): string | null {\n if (!this.config?.apiKey) return null;\n return createHash('sha256').update(this.config.apiKey).digest('hex').substring(0, 16);\n }\n\n private startBackgroundFlush(): void {\n if (!this.config || !this.authState.accessToken) return;\n const accessToken = this.authState.accessToken;\n const queueDir = this.config.queueDirectory;\n const endpoint = this.config.endpoint;\n\n this.flushPromise = Promise.race([\n flushQueue(queueDir, endpoint, accessToken),\n unrefTimeout(FLUSH_TIMEOUT_MS),\n ]).catch(() => {\n // Background flush errors are non-fatal\n });\n }\n\n private startHealthCheck(): void {\n if (!this.config) return;\n const endpoint = this.config.endpoint;\n\n // `unref()`d: this recovery poller is pure telemetry upkeep and must never\n // be the handle that stops the customer's test process from exiting.\n this.healthCheckTimer = setInterval(async () => {\n // Only useful if we've switched to local mode due to connectivity\n if (this.isCloudMode()) return;\n if (!this.failureReason || !['cloud_unreachable', 'auth_timeout', 'network_error'].includes(this.failureReason)) return;\n\n try {\n const healthy = await healthCheck(endpoint);\n if (!healthy) return;\n\n // Attempt re-auth\n if (this.config?.apiKey) {\n const result = await exchangeToken(this.config.endpoint, this.config.apiKey, this.config.timeout);\n if (!isAuthError(result)) {\n const tokenResult = result as TokenExchangeResult;\n this.authState = {\n mode: 'cloud',\n accessToken: tokenResult.accessToken,\n refreshToken: tokenResult.refreshToken,\n expiresAt: Date.now() + tokenResult.expiresIn * 1000,\n orgId: tokenResult.orgId,\n orgName: tokenResult.orgName,\n userId: tokenResult.userId,\n userName: tokenResult.userName,\n };\n this.failureReason = null;\n process.stderr.write('✓ TestRelic: Cloud connectivity restored.\\n');\n this.startBackgroundFlush();\n }\n }\n } catch {\n // Health check errors are silently ignored\n }\n }, HEALTH_CHECK_INTERVAL_MS);\n\n if (typeof this.healthCheckTimer?.unref === 'function') this.healthCheckTimer.unref();\n }\n}\n","/**\n * Test data extraction — parses file-based payloads (v2) and legacy inline payloads (v1).\n *\n * In v2, heavy data (network requests, console logs, API calls) is referenced\n * by file path rather than embedded inline. The reporter moves these files to\n * the report directory without loading them into memory.\n */\n\nimport type {\n NavigationAnnotation,\n ApiAssertion,\n ApiCallsByStatusRange,\n} from '@testrelic/core';\nimport { isTestRelicFilePayload, isTestRelicDataPayload, ATTACHMENT_NAME } from '@testrelic/core';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface AttachmentLike {\n name: string;\n contentType: string;\n body?: Buffer;\n}\n\ninterface AnnotationLike {\n type: string;\n description?: string;\n}\n\n/** Extracted data with file references for heavy data. */\nexport interface ExtractedTestData {\n navigations: NavigationAnnotation[];\n apiAssertions: ApiAssertion[];\n /** Path to JSONL file with network requests, or null */\n networkRequestsFile: string | null;\n networkRequestsCount: number;\n /** Path to JSONL file with console logs, or null */\n consoleLogsFile: string | null;\n consoleLogsCount: number;\n /** Path to JSONL file with API calls, or null */\n apiCallsFile: string | null;\n apiCallsCount: number;\n /** Response-status mix of those calls, tallied at capture time. */\n apiStatusMix: ApiCallsByStatusRange | null;\n}\n\n/** Sum two status mixes. A test can attach more than one payload. */\nfunction addStatusMix(\n a: ApiCallsByStatusRange | null,\n b: ApiCallsByStatusRange | null | undefined,\n): ApiCallsByStatusRange | null {\n if (!b) return a;\n if (!a) return { ...b };\n return {\n '2xx': a['2xx'] + b['2xx'],\n '3xx': a['3xx'] + b['3xx'],\n '4xx': a['4xx'] + b['4xx'],\n '5xx': a['5xx'] + b['5xx'],\n error: a.error + b.error,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Extraction\n// ---------------------------------------------------------------------------\n\n/**\n * Extract test data from Playwright attachments.\n *\n * Supports both v2 file-based payloads (preferred) and v1 inline payloads (legacy fallback).\n * For v1 payloads, data is still inline but wrapped in the same ExtractedTestData shape\n * with file paths set to null.\n */\nexport function extractTestData(\n attachments: readonly AttachmentLike[],\n annotations: readonly AnnotationLike[],\n testTitle: string,\n isSkipped: boolean,\n): ExtractedTestData {\n let navigations: NavigationAnnotation[] = [];\n let apiAssertions: ApiAssertion[] = [];\n let networkRequestsFile: string | null = null;\n let networkRequestsCount = 0;\n let consoleLogsFile: string | null = null;\n let consoleLogsCount = 0;\n let apiCallsFile: string | null = null;\n let apiCallsCount = 0;\n let apiStatusMix: ApiCallsByStatusRange | null = null;\n\n const payloadAttachments = attachments.filter(\n (a) => a.name === ATTACHMENT_NAME && a.body,\n );\n\n let attachmentParsed = false;\n if (payloadAttachments.length > 0) {\n for (const att of payloadAttachments) {\n try {\n const parsed = JSON.parse(att.body!.toString());\n\n // v2 file-based payload (preferred)\n if (isTestRelicFilePayload(parsed)) {\n navigations = navigations.concat(parsed.navigations);\n apiAssertions = apiAssertions.concat(parsed.apiAssertions);\n if (parsed.networkRequestsFile) {\n networkRequestsFile = parsed.networkRequestsFile;\n networkRequestsCount += parsed.networkRequestsCount;\n }\n if (parsed.consoleLogsFile) {\n consoleLogsFile = parsed.consoleLogsFile;\n consoleLogsCount += parsed.consoleLogsCount;\n }\n if (parsed.apiCallsFile) {\n apiCallsFile = parsed.apiCallsFile;\n apiCallsCount += parsed.apiCallsCount;\n }\n apiStatusMix = addStatusMix(apiStatusMix, parsed.apiStatusMix);\n attachmentParsed = true;\n continue;\n }\n\n // v1 inline payload (legacy fallback)\n if (isTestRelicDataPayload(parsed)) {\n navigations = navigations.concat(parsed.navigations);\n if (Array.isArray(parsed.apiAssertions)) {\n apiAssertions = apiAssertions.concat(parsed.apiAssertions as ApiAssertion[]);\n }\n // v1 data is inline — counts tracked but no files\n networkRequestsCount += parsed.networkRequests?.length ?? 0;\n apiCallsCount += parsed.apiCalls?.length ?? 0;\n // v1 payloads carried consoleLogs inline; the interface never\n // declared it, so this is a probe of a legacy shape.\n const payloadLogs = (parsed as unknown as Record<string, unknown>).consoleLogs;\n if (Array.isArray(payloadLogs)) {\n consoleLogsCount += payloadLogs.length;\n }\n attachmentParsed = true;\n }\n } catch {\n process.stderr.write(`[testrelic] Warning: Corrupt attachment for test \"${testTitle}\"\\n`);\n }\n }\n }\n\n if (!attachmentParsed) {\n // Fallback: read navigations from legacy annotations\n navigations = 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\n const hasNoData = navigations.length === 0 && networkRequestsCount === 0 && apiCallsCount === 0;\n if (hasNoData && !isSkipped && !attachmentParsed) {\n process.stderr.write(\n `[testrelic] Warning: No data found for test \"${testTitle}\". ` +\n `Make sure your tests import { test, expect } from '@testrelic/playwright-analytics/fixture' ` +\n `instead of '@playwright/test'. Without the TestRelic fixture, network logs, video sync, ` +\n `and Test Navigation data cannot be captured.\\n`,\n );\n }\n\n return {\n navigations,\n apiAssertions,\n networkRequestsFile,\n networkRequestsCount,\n consoleLogsFile,\n consoleLogsCount,\n apiCallsFile,\n apiCallsCount,\n apiStatusMix,\n };\n}\n","/**\n * Cloud Farm detection for the TestRelic Playwright reporter.\n *\n * When Playwright tests are executed on a remote browser farm (BrowserStack\n * or LambdaTest), the reporter needs to forward `cloudPlatform`,\n * `cloudBuildId`, and `cloudSessionId` to the TestRelic server so the run\n * row can be linked to the vendor session (and, downstream, to the\n * ingested artifacts and Ask-AI tooling).\n *\n * Detection is best-effort and purely metadata-based:\n * 1. Prefer env vars the user sets in their Playwright config\n * (TESTRELIC_CLOUD_PLATFORM, TESTRELIC_CLOUD_BUILD_ID, TESTRELIC_CLOUD_SESSION_ID).\n * 2. Fall back to vendor env vars commonly present in CI\n * (BROWSERSTACK_USERNAME / LT_USERNAME / LAMBDATEST_USERNAME).\n * 3. As a last resort, parse the `PLAYWRIGHT_WS_ENDPOINT` env var if the\n * project pipes its LT/BS wsEndpoint through that convention.\n *\n * The returned values are optional — the SDK simply omits them from\n * `/runs/init` when nothing is detected.\n */\nexport interface CloudFarmMetadata {\n readonly cloudPlatform?: 'browserstack' | 'lambdatest';\n readonly cloudBuildId?: string;\n readonly cloudSessionId?: string;\n}\n\nexport function detectCloudFarm(env: NodeJS.ProcessEnv = process.env): CloudFarmMetadata {\n // 1. Explicit TestRelic env overrides — highest priority.\n const explicitPlatform = normalizePlatform(env.TESTRELIC_CLOUD_PLATFORM);\n if (explicitPlatform) {\n return {\n cloudPlatform: explicitPlatform,\n cloudBuildId: env.TESTRELIC_CLOUD_BUILD_ID || undefined,\n cloudSessionId: env.TESTRELIC_CLOUD_SESSION_ID || undefined,\n };\n }\n\n // 2. Vendor-native env vars.\n if (env.BROWSERSTACK_USERNAME || env.BROWSERSTACK_ACCESS_KEY) {\n return {\n cloudPlatform: 'browserstack',\n cloudBuildId: env.BROWSERSTACK_BUILD_NAME || env.BROWSERSTACK_BUILD_ID || undefined,\n cloudSessionId: env.BROWSERSTACK_SESSION_ID || undefined,\n };\n }\n if (\n env.LT_USERNAME || env.LAMBDATEST_USERNAME ||\n env.LT_ACCESS_KEY || env.LAMBDATEST_ACCESS_KEY\n ) {\n return {\n cloudPlatform: 'lambdatest',\n cloudBuildId: env.LT_BUILD || env.LAMBDATEST_BUILD || undefined,\n cloudSessionId: env.LT_SESSION_ID || env.LAMBDATEST_SESSION_ID || undefined,\n };\n }\n\n // 3. Parse wsEndpoint hostname (opt-in convention).\n const ws = env.PLAYWRIGHT_WS_ENDPOINT;\n if (ws) {\n if (/cdp\\.lambdatest\\.com/i.test(ws)) return { cloudPlatform: 'lambdatest' };\n if (/cdp\\.browserstack\\.com/i.test(ws)) return { cloudPlatform: 'browserstack' };\n }\n\n return {};\n}\n\nfunction normalizePlatform(raw: string | undefined): 'browserstack' | 'lambdatest' | undefined {\n if (!raw) return undefined;\n const v = raw.toLowerCase().trim();\n if (v === 'browserstack' || v === 'bs') return 'browserstack';\n if (v === 'lambdatest' || v === 'lt') return 'lambdatest';\n return undefined;\n}\n","/**\n * Cloud artifact upload — binary file upload via presigned S3 URLs.\n *\n * Flow per artifact:\n * 1. POST /artifacts/upload-url → receive presigned PUT URL + artifactId\n * 2. PUT binary file to the presigned URL\n * 3. POST /artifacts/confirm → mark upload complete\n *\n * Never throws to caller — returns structured results.\n */\n\nimport { createReadStream, statSync } from 'node:fs';\nimport { basename } from 'node:path';\nimport { Readable } from 'node:stream';\nimport type { ReadableStream as WebReadableStream } from 'node:stream/web';\nimport { Budget, putDeadlineMs, withTimeout } from './cloud-deadline.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface ArtifactUploadRequest {\n readonly filePath: string;\n readonly runId: string;\n readonly testId: string;\n readonly type: 'screenshot' | 'video' | 'trace' | 'baseline' | 'diff' | 'dom' | 'other';\n}\n\nexport interface ArtifactUploadResult {\n readonly success: boolean;\n readonly storageKey: string | null;\n readonly artifactId: string | null;\n readonly error: string | null;\n}\n\ninterface UploadUrlResponse {\n readonly artifactId: string;\n readonly uploadUrl: string;\n readonly storageKey: string;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst UPLOAD_CONCURRENCY = 5;\nconst RETRY_DELAYS_MS = [1000, 3000, 9000];\nconst MAX_RETRIES = 3;\n/** The small JSON calls around a transfer (presign, confirm). The transfer\n * itself gets a size-scaled deadline — see cloud-deadline. */\nconst JSON_REQUEST_TIMEOUT_MS = 30_000;\n\nconst CONTENT_TYPE_MAP: Record<string, string> = {\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.webp': 'image/webp',\n '.webm': 'video/webm',\n '.mp4': 'video/mp4',\n '.zip': 'application/zip',\n '.json': 'application/json',\n};\n\n// ---------------------------------------------------------------------------\n// Concurrency Limiter\n// ---------------------------------------------------------------------------\n\nlet activeUploads = 0;\nconst pendingUploads: Array<() => void> = [];\n\nasync function acquireSlot(): Promise<void> {\n if (activeUploads >= UPLOAD_CONCURRENCY) {\n await new Promise<void>((resolve) => pendingUploads.push(resolve));\n }\n activeUploads++;\n}\n\nfunction releaseSlot(): void {\n activeUploads--;\n if (pendingUploads.length > 0) pendingUploads.shift()!();\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nasync function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction getContentType(filePath: string): string {\n const ext = filePath.substring(filePath.lastIndexOf('.')).toLowerCase();\n return CONTENT_TYPE_MAP[ext] ?? 'application/octet-stream';\n}\n\nfunction getFileSize(filePath: string): number {\n try {\n return statSync(filePath).size;\n } catch {\n return 0;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Core Upload\n// ---------------------------------------------------------------------------\n\nasync function requestUploadUrl(\n endpoint: string,\n accessToken: string,\n request: ArtifactUploadRequest,\n sizeBytes: number,\n contentType: string,\n): Promise<UploadUrlResponse | null> {\n const url = `${endpoint}/artifacts/upload-url`;\n const body = JSON.stringify({\n runId: request.runId,\n testId: request.testId,\n fileName: basename(request.filePath),\n contentType,\n type: request.type,\n sizeBytes,\n });\n\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n try {\n const response = await withTimeout(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${accessToken}`,\n },\n body,\n }, JSON_REQUEST_TIMEOUT_MS);\n\n if (response.ok) {\n return (await response.json()) as UploadUrlResponse;\n }\n\n if (response.status >= 500 && attempt < MAX_RETRIES - 1) {\n await sleep(RETRY_DELAYS_MS[attempt]);\n continue;\n }\n\n return null;\n } catch {\n if (attempt < MAX_RETRIES - 1) {\n await sleep(RETRY_DELAYS_MS[attempt]);\n continue;\n }\n return null;\n }\n }\n return null;\n}\n\nasync function putFileToPresignedUrl(\n presignedUrl: string,\n filePath: string,\n contentType: string,\n sizeBytes: number,\n): Promise<boolean> {\n const deadlineMs = putDeadlineMs(sizeBytes, JSON_REQUEST_TIMEOUT_MS);\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n try {\n const nodeStream = createReadStream(filePath);\n const webStream = Readable.toWeb(nodeStream) as WebReadableStream<Uint8Array>;\n\n const response = await withTimeout(\n presignedUrl,\n {\n method: 'PUT',\n headers: {\n 'Content-Type': contentType,\n 'Content-Length': String(sizeBytes),\n },\n body: webStream,\n // Node's fetch requires `duplex` for a stream body; it is not in the\n // DOM RequestInit type, hence the cast rather than a suppression.\n ...({ duplex: 'half' } as Record<string, unknown>),\n } as RequestInit,\n // A transfer's budget follows its size — a 200 MB trace legitimately\n // outlasts a 2 KB POST, and a flat cap would abandon healthy uploads\n // on a slow link while still leaving a wedged socket to undici.\n deadlineMs,\n );\n\n if (response.ok) return true;\n\n if (response.status >= 500 && attempt < MAX_RETRIES - 1) {\n await sleep(RETRY_DELAYS_MS[attempt]);\n continue;\n }\n\n return false;\n } catch {\n if (attempt < MAX_RETRIES - 1) {\n await sleep(RETRY_DELAYS_MS[attempt]);\n continue;\n }\n return false;\n }\n }\n return false;\n}\n\nasync function confirmUpload(\n endpoint: string,\n accessToken: string,\n artifactId: string,\n): Promise<boolean> {\n const url = `${endpoint}/artifacts/confirm`;\n try {\n const response = await withTimeout(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${accessToken}`,\n },\n body: JSON.stringify({ artifactId }),\n }, JSON_REQUEST_TIMEOUT_MS);\n return response.ok;\n } catch {\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Upload a single artifact file to cloud storage via presigned URL.\n * Concurrency-limited and retried. Never throws.\n */\nexport async function uploadArtifact(\n endpoint: string,\n accessToken: string,\n request: ArtifactUploadRequest,\n maxSizeMb: number,\n): Promise<ArtifactUploadResult> {\n const sizeBytes = getFileSize(request.filePath);\n\n if (sizeBytes === 0) {\n return { success: false, storageKey: null, artifactId: null, error: 'file_not_found_or_empty' };\n }\n\n if (sizeBytes > maxSizeMb * 1024 * 1024) {\n return { success: false, storageKey: null, artifactId: null, error: 'file_too_large' };\n }\n\n const contentType = getContentType(request.filePath);\n\n await acquireSlot();\n try {\n const urlResult = await requestUploadUrl(\n endpoint,\n accessToken,\n request,\n sizeBytes,\n contentType,\n );\n\n if (!urlResult) {\n return { success: false, storageKey: null, artifactId: null, error: 'upload_url_request_failed' };\n }\n\n const uploaded = await putFileToPresignedUrl(\n urlResult.uploadUrl,\n request.filePath,\n contentType,\n sizeBytes,\n );\n\n if (!uploaded) {\n return {\n success: false,\n storageKey: urlResult.storageKey,\n artifactId: urlResult.artifactId,\n error: 'presigned_put_failed',\n };\n }\n\n const confirmed = await confirmUpload(endpoint, accessToken, urlResult.artifactId);\n\n return {\n success: confirmed,\n storageKey: urlResult.storageKey,\n artifactId: urlResult.artifactId,\n error: confirmed ? null : 'confirm_failed',\n };\n } finally {\n releaseSlot();\n }\n}\n\n/**\n * Upload multiple artifacts in parallel (bounded by UPLOAD_CONCURRENCY).\n * Returns a map from filePath to the upload result.\n */\nexport async function uploadArtifacts(\n endpoint: string,\n accessToken: string,\n requests: readonly ArtifactUploadRequest[],\n maxSizeMb: number,\n budget?: Budget,\n): Promise<Map<string, ArtifactUploadResult>> {\n const results = new Map<string, ArtifactUploadResult>();\n\n const promises = requests.map(async (req) => {\n // ⛔ The circuit breaker. Without it, a run whose storage endpoint is\n // wedged pays one full deadline PER ARTIFACT — ten screenshots turned a\n // slow upload into an app that would not exit. Once the phase's budget is\n // gone, the rest are recorded as skipped and the process gets to leave;\n // the local report still holds everything, and the next run re-uploads.\n if (budget?.expired()) {\n results.set(req.filePath, {\n success: false,\n storageKey: null,\n artifactId: null,\n error: 'upload_budget_exhausted',\n });\n return;\n }\n const result = await uploadArtifact(endpoint, accessToken, req, maxSizeMb);\n results.set(req.filePath, result);\n });\n\n await Promise.allSettled(promises);\n return results;\n}\n","/**\n * Cloud reporter orchestration — init, upload, and finalize cloud runs.\n *\n * Extracted from reporter.ts to comply with Constitution Principle II (500-line limit).\n * Never throws — all errors result in graceful degradation.\n */\n\nimport { join } from 'node:path';\nimport type {\n CloudConfig,\n TestRunReport,\n Summary,\n TestArtifacts,\n VisualComparison,\n} from '@testrelic/core';\nimport { CloudClient } from './cloud-client.js';\nimport { buildUploadPayload, uploadBatchRun, initRealtimeRun, finalizeRun, deriveRunTestType } from './cloud-upload.js';\nimport { detectCloudFarm } from './cloud-farm-detect.js';\nimport { resolveSdkRunTypeFromEnv } from './run-type-env.js';\nimport type { UploadFailure, TestResultForUpload } from './cloud-upload.js';\nimport { uploadArtifacts } from './cloud-artifact-upload.js';\nimport type { ArtifactUploadRequest } from './cloud-artifact-upload.js';\nimport { writeToQueue } from './cloud-queue.js';\nimport { detectCI } from './ci-detector.js';\nimport { normalizeGitRemoteUrl, deriveNonGitProjectId } from './git-metadata.js';\nimport type { CloudFinalizeOutcome } from './cloud-receipt.js';\nimport { Budget, DEFAULT_UPLOAD_BUDGET_MS } from './cloud-deadline.js';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction getEffectiveGitId(cloudClient: CloudClient): string {\n const gitMeta = cloudClient.getGitMetadata();\n const remoteUrl = gitMeta?.remoteUrl;\n if (remoteUrl) return normalizeGitRemoteUrl(remoteUrl);\n const projectName = cloudClient.getConfig()?.projectName;\n return projectName ?? deriveNonGitProjectId(process.cwd());\n}\n\n// ---------------------------------------------------------------------------\n// Realtime Init\n// ---------------------------------------------------------------------------\n\n/**\n * Initialize a realtime run on the cloud during onBegin.\n * Returns the cloud run ID, or null if init fails or is not applicable.\n */\nexport async function initCloudRun(\n cloudClient: CloudClient,\n uploadStrategy: string | undefined,\n testRunId: string,\n startedAt: string,\n environment?: string,\n): Promise<string | null> {\n if (!cloudClient.isCloudMode()) return null;\n if (uploadStrategy !== 'realtime' && uploadStrategy !== 'both') return null;\n\n const tokenValid = await cloudClient.ensureValidToken();\n if (!tokenValid) return null;\n\n const gitMeta = cloudClient.getGitMetadata();\n const ciMeta = detectCI();\n const cloudFarm = detectCloudFarm();\n const runType = resolveSdkRunTypeFromEnv();\n\n const initResult = await initRealtimeRun(\n cloudClient.getEndpoint(),\n cloudClient.getAccessToken()!,\n {\n runId: testRunId,\n repoGitId: getEffectiveGitId(cloudClient),\n branch: gitMeta?.branch ?? null,\n commit: gitMeta?.commitSha ?? null,\n commitMessage: gitMeta?.commitMessage ?? null,\n commitAuthor: gitMeta?.commitAuthor ?? null,\n startedAt,\n totalTests: null,\n ciProvider: ciMeta?.provider ?? null,\n ciRunUrl: ciMeta?.runUrl ?? null,\n environment: environment ?? (cloudFarm.cloudPlatform ?? null),\n cloudPlatform: cloudFarm.cloudPlatform ?? null,\n cloudBuildId: cloudFarm.cloudBuildId ?? null,\n cloudSessionId: cloudFarm.cloudSessionId ?? null,\n ...(runType ? { runType } : {}),\n // Playwright runs are browser-driven by default; finalize overwrites this\n // with the derived slug once the per-test types are known. Ensures a run\n // that crashes before finalize still carries a non-empty test type.\n testType: 'e2e',\n },\n );\n\n return initResult?.runId ?? null;\n}\n\n// ---------------------------------------------------------------------------\n// Artifact Collection\n// ---------------------------------------------------------------------------\n\nexport interface ArtifactEntry {\n readonly testId: string;\n readonly artifacts: TestArtifacts;\n /** Visual comparisons recorded for this test, if it made any. */\n readonly visual?: readonly VisualComparison[] | null;\n readonly outputDir: string;\n}\n\n/** Which image of a comparison maps to which upload type and storage-key field. */\nconst VISUAL_UPLOAD_ROLES = [\n { field: 'baseline', keyField: 'baselineKey', type: 'baseline' },\n { field: 'actual', keyField: 'actualKey', type: 'screenshot' },\n { field: 'diff', keyField: 'diffKey', type: 'diff' },\n { field: 'baselineDom', keyField: 'baselineDomKey', type: 'dom' },\n { field: 'actualDom', keyField: 'actualDomKey', type: 'dom' },\n] as const;\n\ntype VisualKeyField = (typeof VISUAL_UPLOAD_ROLES)[number]['keyField'];\n\nconst DEFAULT_UPLOAD_ARTIFACTS = true;\nconst DEFAULT_ARTIFACT_MAX_SIZE_MB = 50;\n\n/** Artifact key map plus the counts that go into the run's upload receipt. */\ninterface ArtifactUploadOutcome {\n readonly keyMap: Map<string, TestArtifacts>;\n /** Comparisons with their storage keys filled in, per testId. */\n readonly visualKeyMap: Map<string, VisualComparison[]>;\n /** Artifact files offered for upload. */\n readonly total: number;\n /** Artifact files the platform confirmed it stored. */\n readonly uploaded: number;\n}\n\nconst NO_ARTIFACTS: ArtifactUploadOutcome = {\n keyMap: new Map<string, TestArtifacts>(),\n visualKeyMap: new Map<string, VisualComparison[]>(),\n total: 0,\n uploaded: 0,\n};\n\n/**\n * Upload all binary artifacts and return a map from testId to updated TestArtifacts\n * with cloud storage keys filled in, alongside the offered/confirmed counts.\n *\n * `cloudConfig` may be null when the user authenticated via `testrelic login` (session-based\n * auth) without an explicit apiKey in the reporter config. In that case we fall back to\n * safe defaults so artifacts are still uploaded.\n */\nasync function uploadRunArtifacts(\n cloudClient: CloudClient,\n cloudConfig: CloudConfig | null | undefined,\n runId: string,\n artifactEntries: readonly ArtifactEntry[],\n budget: Budget,\n): Promise<ArtifactUploadOutcome> {\n const result = new Map<string, TestArtifacts>();\n const visualResult = new Map<string, VisualComparison[]>();\n\n const shouldUpload = cloudConfig?.uploadArtifacts ?? DEFAULT_UPLOAD_ARTIFACTS;\n if (!shouldUpload) return NO_ARTIFACTS;\n\n const tokenValid = await cloudClient.ensureValidToken();\n if (!tokenValid) return NO_ARTIFACTS;\n\n const requests: ArtifactUploadRequest[] = [];\n const requestToEntry = new Map<\n string,\n {\n testId: string;\n field: 'screenshot' | 'video' | 'trace' | 'visual';\n visualIndex?: number;\n visualKey?: VisualKeyField;\n }\n >();\n /** testId → comparison index → { baselineKey?, actualKey?, diffKey? } */\n const visualKeys = new Map<string, Map<number, Record<string, string>>>();\n\n for (const entry of artifactEntries) {\n if (entry.artifacts.screenshot) {\n const absPath = join(entry.outputDir, entry.artifacts.screenshot);\n requests.push({ filePath: absPath, runId, testId: entry.testId, type: 'screenshot' });\n requestToEntry.set(absPath, { testId: entry.testId, field: 'screenshot' });\n }\n if (entry.artifacts.video) {\n const absPath = join(entry.outputDir, entry.artifacts.video);\n requests.push({ filePath: absPath, runId, testId: entry.testId, type: 'video' });\n requestToEntry.set(absPath, { testId: entry.testId, field: 'video' });\n }\n if (entry.artifacts.trace) {\n const absPath = join(entry.outputDir, entry.artifacts.trace);\n requests.push({ filePath: absPath, runId, testId: entry.testId, type: 'trace' });\n requestToEntry.set(absPath, { testId: entry.testId, field: 'trace' });\n }\n const comparisons = entry.visual ?? [];\n for (let vi = 0; vi < comparisons.length; vi++) {\n const comparison = comparisons[vi];\n if (!comparison) continue;\n for (const role of VISUAL_UPLOAD_ROLES) {\n const relativePath = comparison[role.field];\n if (!relativePath) continue;\n const absPath = join(entry.outputDir, relativePath);\n requests.push({ filePath: absPath, runId, testId: entry.testId, type: role.type });\n requestToEntry.set(absPath, {\n testId: entry.testId,\n field: 'visual',\n visualIndex: vi,\n visualKey: role.keyField,\n });\n }\n }\n }\n\n if (requests.length === 0) return NO_ARTIFACTS;\n\n const maxSizeMb = cloudConfig?.artifactMaxSizeMb ?? DEFAULT_ARTIFACT_MAX_SIZE_MB;\n const uploadResults = await uploadArtifacts(\n cloudClient.getEndpoint(),\n cloudClient.getAccessToken()!,\n requests,\n maxSizeMb,\n budget,\n );\n\n // Build updated TestArtifacts per testId\n const keysByTest = new Map<\n string,\n { screenshotKey?: string; videoKey?: string; traceKey?: string }\n >();\n for (const [filePath, uploadResult] of uploadResults) {\n if (!uploadResult.success || !uploadResult.storageKey) continue;\n const meta = requestToEntry.get(filePath);\n if (!meta) continue;\n const existing = keysByTest.get(meta.testId) ?? {};\n if (meta.field === 'screenshot') existing.screenshotKey = uploadResult.storageKey;\n if (meta.field === 'video') existing.videoKey = uploadResult.storageKey;\n if (meta.field === 'trace') existing.traceKey = uploadResult.storageKey;\n if (meta.field === 'visual' && meta.visualIndex !== undefined && meta.visualKey) {\n const perTest = visualKeys.get(meta.testId) ?? new Map<number, Record<string, string>>();\n const perComparison = perTest.get(meta.visualIndex) ?? {};\n perComparison[meta.visualKey] = uploadResult.storageKey;\n perTest.set(meta.visualIndex, perComparison);\n visualKeys.set(meta.testId, perTest);\n }\n keysByTest.set(meta.testId, existing);\n }\n\n // Merge with original artifacts\n for (const entry of artifactEntries) {\n if (entry.visual && entry.visual.length > 0) {\n const perTest = visualKeys.get(entry.testId);\n visualResult.set(\n entry.testId,\n entry.visual.map((comparison, index) => ({\n ...comparison,\n ...(perTest?.get(index) ?? {}),\n })),\n );\n }\n const keys = keysByTest.get(entry.testId);\n result.set(entry.testId, {\n ...entry.artifacts,\n ...(keys?.screenshotKey ? { screenshotKey: keys.screenshotKey } : {}),\n ...(keys?.videoKey ? { videoKey: keys.videoKey } : {}),\n ...(keys?.traceKey ? { traceKey: keys.traceKey } : {}),\n });\n }\n\n const uploaded = Array.from(uploadResults.values()).filter((r) => r.success).length;\n if (uploaded > 0) {\n process.stderr.write(`✓ TestRelic: Uploaded ${uploaded} artifact(s) to cloud storage.\\n`);\n }\n\n return { keyMap: result, visualKeyMap: visualResult, total: requests.length, uploaded };\n}\n\n// ---------------------------------------------------------------------------\n// Finalize & Upload\n// ---------------------------------------------------------------------------\n\n/**\n * Finalize realtime run, perform batch upload, and dispose the cloud client.\n * Queues failed uploads for later retry. Never throws.\n *\n * Returns what the platform actually did, so the reporter can write an honest\n * `cloudUpload` receipt into `summary.json` instead of leaving consumers to infer\n * \"uploaded\" from the locally generated `testRunId`.\n *\n * @param artifactEntries - collected test artifacts to upload before the batch payload.\n * When provided, binary files are uploaded and storage keys injected into the report.\n * @param outputDir - root output directory for resolving relative artifact paths.\n */\nexport async function finalizeAndUpload(\n cloudClient: CloudClient,\n cloudConfig: CloudConfig | undefined | null,\n uploadStrategy: string | undefined,\n testRunId: string,\n cloudRunId: string | null,\n report: TestRunReport,\n completedAt: string,\n totalDuration: number,\n summary: Summary,\n artifactEntries?: readonly ArtifactEntry[],\n tests?: readonly TestResultForUpload[],\n): Promise<CloudFinalizeOutcome> {\n const outcome = {\n realtimeFinalizeAttempted: false,\n realtimeFinalized: false,\n batchAttempted: false,\n batchAcknowledged: false,\n artifactsTotal: 0,\n artifactsUploaded: 0,\n failureReason: null as string | null,\n };\n\n // ⛔ ONE budget for the whole phase, started here.\n //\n // This function runs inside Playwright's `onEnd`, which the runner awaits\n // before the process may exit — so everything below is, in the tester's\n // experience, \"the run hasn't finished\". Before this, nothing bounded it:\n // each request used bare `fetch` (undici's 5-minute defaults) inside a\n // 3-attempt retry, so a wedged endpoint held the process for a quarter of an\n // hour per request family, and Ctrl+C could not cancel a request that had no\n // abort signal. Measured on a real run: 11m49s of \"Reporter is still stuck\".\n //\n // Expiry is not data loss: the local report is already written, the queue\n // holds what did not land, and the next run retries.\n const budget = new Budget(cloudConfig?.uploadBudgetMs ?? DEFAULT_UPLOAD_BUDGET_MS);\n /** Keep the FIRST failure — it is the one that explains the rest. */\n const noteFailure = (reason: string): void => {\n if (!outcome.failureReason) outcome.failureReason = reason;\n };\n\n try {\n // Finalize realtime run (with retry; queue on failure for later retry)\n if (cloudClient.isCloudMode() && cloudRunId && (uploadStrategy === 'realtime' || uploadStrategy === 'both')) {\n outcome.realtimeFinalizeAttempted = true;\n const tokenValid = await cloudClient.ensureValidToken();\n const gitMetaForFinalize = cloudClient.getGitMetadata();\n // Realtime /runs/init ran before any test, so the run-level test type is\n // only fully known now. Send the derived slug (Web / API / Mobile / Unit);\n // the api protocol + metadata ride along only when the run is all-API.\n const finalizeTypeSignal = deriveRunTestType(tests);\n const finalizePayload = {\n finishedAt: completedAt,\n duration: totalDuration,\n summary,\n ...(gitMetaForFinalize?.commitMessage ? { commitMessage: gitMetaForFinalize.commitMessage } : {}),\n testType: finalizeTypeSignal.testType,\n ...(finalizeTypeSignal.testType === 'api'\n ? { apiProtocol: finalizeTypeSignal.apiProtocol, metadata: finalizeTypeSignal.metadata }\n : {}),\n };\n if (tokenValid) {\n const finalized = await finalizeRun(\n cloudClient.getEndpoint(),\n cloudClient.getAccessToken()!,\n cloudRunId,\n finalizePayload,\n );\n outcome.realtimeFinalized = finalized;\n if (!finalized) {\n noteFailure('finalize_failed_after_retries');\n const queueDir = cloudConfig?.queueDirectory ?? '.testrelic/queue';\n writeToQueue(\n queueDir,\n testRunId,\n 'finalize',\n 'finalize_failed_after_retries',\n `${cloudClient.getEndpoint()}/runs/${cloudRunId}/finalize`,\n 'POST',\n finalizePayload as unknown as Record<string, unknown>,\n { 'Content-Type': 'application/json' },\n );\n }\n } else {\n noteFailure(cloudClient.getFailureReason() ?? 'token_invalid');\n const queueDir = cloudConfig?.queueDirectory ?? '.testrelic/queue';\n writeToQueue(\n queueDir,\n testRunId,\n 'finalize',\n cloudClient.getFailureReason() ?? 'token_invalid',\n `${cloudClient.getEndpoint()}/runs/${cloudRunId}/finalize`,\n 'POST',\n finalizePayload as unknown as Record<string, unknown>,\n { 'Content-Type': 'application/json' },\n );\n }\n }\n\n // Upload binary artifacts before building the batch payload.\n // cloudConfig may be null when the user authenticated via `testrelic login` — fall back\n // to defaults inside uploadRunArtifacts so artifacts still reach cloud storage.\n let artifactKeyMap = new Map<string, TestArtifacts>();\n let visualKeyMap = new Map<string, VisualComparison[]>();\n if (cloudClient.isCloudMode() && artifactEntries && artifactEntries.length > 0) {\n const artifactOutcome = await uploadRunArtifacts(cloudClient, cloudConfig, testRunId, artifactEntries, budget);\n artifactKeyMap = artifactOutcome.keyMap;\n visualKeyMap = artifactOutcome.visualKeyMap;\n outcome.artifactsTotal = artifactOutcome.total;\n outcome.artifactsUploaded = artifactOutcome.uploaded;\n }\n\n // Batch upload (default, or 'both' mode)\n if (cloudClient.isCloudMode() && (!uploadStrategy || uploadStrategy === 'batch' || uploadStrategy === 'both')) {\n outcome.batchAttempted = true;\n const tokenValid = await cloudClient.ensureValidToken();\n if (tokenValid) {\n const batchGit = cloudClient.getGitMetadata();\n const batchCi = detectCI();\n const batchGitId = getEffectiveGitId(cloudClient);\n\n // Inject artifact storage keys into the report timeline before uploading\n const enrichedReport = injectVisualKeys(\n injectArtifactKeys(report, artifactKeyMap),\n visualKeyMap,\n );\n const payload = buildUploadPayload(\n enrichedReport,\n batchGitId,\n batchGit,\n batchCi,\n testsWithVisualKeys(tests, visualKeyMap),\n detectCloudFarm(),\n );\n\n const result = await uploadBatchRun(\n cloudClient.getEndpoint(),\n cloudClient.getAccessToken()!,\n payload,\n async () => {\n const refreshed = await cloudClient.ensureValidToken();\n return refreshed ? cloudClient.getAccessToken() : null;\n },\n cloudConfig?.timeout,\n budget,\n );\n\n outcome.batchAcknowledged = result.success;\n if (!result.success) {\n const failure = result as UploadFailure;\n noteFailure(failure.reason);\n const queueDir = cloudConfig?.queueDirectory ?? '.testrelic/queue';\n writeToQueue(queueDir, testRunId, 'batch', failure.reason, failure.targetEndpoint, failure.method, failure.payload, { 'Content-Type': 'application/json' });\n }\n } else {\n // Token invalid — queue the payload\n noteFailure(cloudClient.getFailureReason() ?? 'token_invalid');\n const queueDir = cloudConfig?.queueDirectory ?? '.testrelic/queue';\n const queueGit = cloudClient.getGitMetadata();\n const queueCi = detectCI();\n const queueGitId = getEffectiveGitId(cloudClient);\n const queuePayload = buildUploadPayload(report, queueGitId, queueGit, queueCi, undefined, detectCloudFarm());\n writeToQueue(\n queueDir,\n testRunId,\n 'batch',\n cloudClient.getFailureReason() ?? 'token_invalid',\n `${cloudClient.getEndpoint()}/runs`,\n 'POST',\n queuePayload as unknown as Record<string, unknown>,\n { 'Content-Type': 'application/json' },\n );\n }\n }\n\n // Dispose cloud client (clean up timers, flush)\n await cloudClient.dispose();\n } catch (err) {\n // Cloud errors never prevent local report writing — but they must still be\n // reported, or the receipt would claim a clean upload after an exception.\n noteFailure(err instanceof Error ? err.message : 'unexpected_upload_error');\n try { await cloudClient.dispose(); } catch { /* ignore */ }\n }\n return outcome;\n}\n\n/**\n * Inject cloud storage keys into timeline steps that have matching test artifacts.\n * Returns a shallow copy of the report with enriched timeline entries.\n */\n/**\n * Put the cloud storage keys onto the per-test batch array as well.\n *\n * `injectVisualKeys` enriches `report.timeline[].tests[].visual`, but the batch\n * payload's `tests` array is a SEPARATE snapshot taken at `onTestEnd` — long\n * before `uploadRunArtifacts` computes the keys — and it is handed to\n * `buildUploadPayload` verbatim. So the copy the server re-ingests per test had\n * local report-relative paths and no `baselineKey`/`actualKey`/`diffKey`.\n *\n * That is not cosmetic. In STREAMING mode `buildStreamingReport` returns\n * `timeline: []`, so the enriched copy does not exist at all and this array is\n * the only visual record the cloud ever sees — metadata with no way to reach\n * the images, the exact inverse of the bug the `visual` field was added to fix.\n * In batch mode it is worse than useless: two divergent copies of the same\n * comparisons, and the per-test one is documented elsewhere in this pipeline as\n * the authoritative re-ingest that overwrites the other.\n */\nexport function testsWithVisualKeys(\n tests: readonly TestResultForUpload[] | undefined,\n visualKeyMap: Map<string, VisualComparison[]>,\n): readonly TestResultForUpload[] | undefined {\n if (!tests || visualKeyMap.size === 0) return tests;\n return tests.map((test) => {\n const withKeys = visualKeyMap.get(test.testId);\n if (!withKeys) return test;\n return { ...test, visual: withKeys as unknown as Record<string, unknown>[] };\n });\n}\n\n/**\n * Write cloud storage keys back onto each test's visual comparisons.\n *\n * Unlike screenshots and videos, comparisons live on the tests inside a\n * timeline entry rather than on the entry itself, so the rewrite descends\n * into `entry.tests` and leaves everything it does not recognise alone.\n */\nfunction injectVisualKeys(\n report: TestRunReport,\n visualKeyMap: Map<string, VisualComparison[]>,\n): TestRunReport {\n if (visualKeyMap.size === 0) return report;\n\n const enrichedTimeline = report.timeline.map((entry) => {\n // SAFETY: the timeline is a union of shapes; only entries that actually\n // carry a `tests` array are rewritten.\n const tests = (entry as { tests?: unknown }).tests;\n if (!Array.isArray(tests)) return entry;\n\n let changed = false;\n const rewritten = tests.map((test) => {\n const testId = (test as { testId?: string }).testId;\n if (!testId) return test;\n const withKeys = visualKeyMap.get(testId);\n if (!withKeys) return test;\n changed = true;\n return { ...(test as object), visual: withKeys };\n });\n\n return changed ? { ...entry, tests: rewritten } : entry;\n });\n\n return { ...report, timeline: enrichedTimeline as TestRunReport['timeline'] };\n}\n\nfunction injectArtifactKeys(\n report: TestRunReport,\n keyMap: Map<string, TestArtifacts>,\n): TestRunReport {\n if (keyMap.size === 0) return report;\n\n const enrichedTimeline = report.timeline.map((entry) => {\n // `testId` is present on the step arm of the timeline union and absent on\n // the entry arm, so the read is probing rather than asserting.\n const testId = (entry as unknown as Record<string, unknown>).testId as string | undefined;\n if (!testId) return entry;\n\n const keys = keyMap.get(testId);\n if (!keys) return entry;\n\n return {\n ...entry,\n ...(keys.screenshotKey ? { screenshotKey: keys.screenshotKey } : {}),\n ...(keys.videoKey ? { videoKey: keys.videoKey } : {}),\n };\n });\n\n return { ...report, timeline: enrichedTimeline };\n}\n","/**\n * Cloud upload receipt — the honest record of whether a run reached the platform.\n *\n * `summary.json` has always carried `testRunId`, and consumers (TestRelic Studio\n * among them) read its presence as proof the run was uploaded. It is not proof:\n * `testRunId` is `config.testRunId ?? randomUUID()`, generated locally in `onBegin`\n * and written on every run — including a fully offline one, which then renders as\n * \"Uploaded\" behind a dashboard link that 404s.\n *\n * The platform does not mint run ids either — `POST /runs/init` and `POST /runs`\n * both file the run under the id the SDK sent — so there is no server-side id to\n * record instead. What the SDK *does* know, and never wrote down, is whether the\n * platform acknowledged the run. That is what `cloudUpload` carries.\n *\n * The block is written on EVERY run, so a consumer can distinguish three states:\n * - block absent → an SDK too old to report a verdict; treat as unknown\n * - `acknowledged:false` → the run did not reach the platform, and `detail` says why\n * - `acknowledged:true` → the platform accepted the run under `runId`\n *\n * It is written twice per run, both times atomically (tmp + rename): once with the\n * pre-upload state, and again once the upload settles. A process killed between the\n * two leaves the earlier, more conservative verdict on disk — this block under-claims\n * when it is wrong, never over-claims.\n */\n\nimport type { CloudClient } from './cloud-client.js';\n\n/** Upload strategies the reporter actually acts on. Anything else uploads nothing. */\nconst KNOWN_STRATEGIES: ReadonlySet<string> = new Set(['batch', 'realtime', 'both']);\n\n/**\n * Whether the platform acknowledged this run, and what it did with it.\n * Written to `summary.json` as `cloudUpload` on every run.\n */\nexport interface CloudUploadReceipt {\n /** Cloud mode was on and a strategy the reporter acts on was selected. */\n readonly attempted: boolean;\n /** The platform accepted the run — realtime `/runs/init`, or the batch `POST /runs`. */\n readonly acknowledged: boolean;\n /** The id the run was filed under on the platform, or null when never acknowledged. */\n readonly runId: string | null;\n /** The ingest endpoint the run was sent to, or null when nothing was sent. */\n readonly endpoint: string | null;\n /** The run was closed out — realtime finalize returned ok, or the batch upload landed. */\n readonly finalized: boolean;\n /** Artifacts the platform confirmed it stored. */\n readonly artifactsUploaded: number;\n /** Artifacts offered for upload. */\n readonly artifactsTotal: number;\n /** Short plain reason when the run was not acknowledged. Null once it was. */\n readonly detail: string | null;\n}\n\n/** A streaming summary carrying the upload receipt. */\nexport type WithCloudUpload<T> = T & { readonly cloudUpload: CloudUploadReceipt };\n\n/** What `finalizeAndUpload` observed, so the reporter can compose the final receipt. */\nexport interface CloudFinalizeOutcome {\n /** A realtime finalize POST was issued (strategy realtime/both, run was initialised). */\n readonly realtimeFinalizeAttempted: boolean;\n /** That finalize POST returned ok. */\n readonly realtimeFinalized: boolean;\n /** A batch `POST /runs` was issued (strategy batch/both). */\n readonly batchAttempted: boolean;\n /** That batch POST landed (2xx, or 409 duplicate — the data is already ingested). */\n readonly batchAcknowledged: boolean;\n /** Artifacts offered for upload. */\n readonly artifactsTotal: number;\n /** Artifacts the platform confirmed it stored. */\n readonly artifactsUploaded: number;\n /** Short machine reason for the first thing that failed, if anything did. */\n readonly failureReason: string | null;\n}\n\n/** The receipt for a run where nothing was ever wired up to the cloud. */\nexport const UNATTEMPTED_RECEIPT: CloudUploadReceipt = Object.freeze({\n attempted: false,\n acknowledged: false,\n runId: null,\n endpoint: null,\n finalized: false,\n artifactsUploaded: 0,\n artifactsTotal: 0,\n detail: 'cloud reporting was never initialised',\n});\n\n/** Translate a CloudClient failure reason into one short plain sentence. */\nfunction describeLocalMode(reason: string | null): string {\n switch (reason) {\n case 'no_api_key':\n return 'cloud mode off — no API key configured';\n case 'cloud_unreachable':\n case 'network_error':\n return 'cloud endpoint unreachable — the run stayed local';\n case 'invalid_api_key':\n return 'API key rejected as invalid or revoked — the run stayed local';\n case 'expired_api_key':\n return 'API key has expired — the run stayed local';\n case 'validation_error':\n return 'API key format rejected — the run stayed local';\n case 'rate_limited':\n return 'rate limited during authentication — the run stayed local';\n case 'auth_timeout':\n return 'authentication timed out — the run stayed local';\n case 'token_expired_no_refresh':\n case 'token_refresh_failed':\n case 'token_refresh_error':\n return 'access token could not be refreshed — the run stayed local';\n case 'init_error':\n return 'cloud initialisation timed out — the run stayed local';\n case 'unexpected_error':\n return 'cloud initialisation failed — the run stayed local';\n default:\n return reason\n ? `cloud mode off (${reason}) — the run stayed local`\n : 'cloud mode off — the run stayed local';\n }\n}\n\n/**\n * Build the receipt as it stands right after `onBegin` — before any finalize or\n * batch upload has run. This is the verdict a crashed or interrupted run carries.\n */\nexport function buildInitialReceipt(\n cloudClient: CloudClient | null,\n uploadStrategy: string | undefined,\n cloudRunId: string | null,\n): CloudUploadReceipt {\n if (!cloudClient) return UNATTEMPTED_RECEIPT;\n\n if (!cloudClient.isCloudMode()) {\n return {\n ...UNATTEMPTED_RECEIPT,\n detail: describeLocalMode(cloudClient.getFailureReason()),\n };\n }\n\n // `isValidCloudConfig` rejects an unknown strategy upstream (the whole cloud block\n // falls back to defaults), so this should be unreachable through config. It is kept\n // because `finalizeAndUpload` takes `uploadStrategy: string | undefined` from any\n // caller, and an unrecognised value matches neither of its branches — the run then\n // uploads nothing at all, silently. Name that rather than imply it shipped.\n const strategy = uploadStrategy ?? 'batch';\n if (!KNOWN_STRATEGIES.has(strategy)) {\n return {\n ...UNATTEMPTED_RECEIPT,\n detail: `upload strategy '${strategy}' is not recognised — nothing was sent`,\n };\n }\n\n const endpoint = cloudClient.getEndpoint();\n const isRealtime = strategy === 'realtime' || strategy === 'both';\n\n if (isRealtime && cloudRunId) {\n return {\n attempted: true,\n acknowledged: true,\n runId: cloudRunId,\n endpoint,\n finalized: false,\n artifactsUploaded: 0,\n artifactsTotal: 0,\n detail: null,\n };\n }\n\n return {\n attempted: true,\n acknowledged: false,\n runId: null,\n endpoint,\n finalized: false,\n artifactsUploaded: 0,\n artifactsTotal: 0,\n detail: isRealtime\n ? 'run init failed — the platform did not acknowledge this run'\n : `upload runs at the end of the run under the '${strategy}' strategy — not sent yet`,\n };\n}\n\n/**\n * Fold what the upload phase observed into the receipt.\n *\n * `testRunId` is the id the SDK filed the run under; it becomes `runId` only once the\n * platform has actually answered, so an unacknowledged run still reports `runId: null`.\n */\nexport function applyFinalizeOutcome(\n receipt: CloudUploadReceipt,\n outcome: CloudFinalizeOutcome | undefined,\n testRunId: string,\n): CloudUploadReceipt {\n if (!receipt.attempted) return receipt;\n\n if (!outcome) {\n return {\n ...receipt,\n detail: receipt.acknowledged\n ? 'upload timed out before it completed — the run may be incomplete on the platform'\n : 'upload timed out before the platform acknowledged this run',\n acknowledged: receipt.acknowledged,\n };\n }\n\n const acknowledged = receipt.acknowledged || outcome.batchAcknowledged;\n const runId = receipt.runId ?? (outcome.batchAcknowledged ? testRunId : null);\n\n // \"Closed out\" means different things per strategy: realtime has an explicit finalize\n // POST, while a batch upload IS the whole run — landing it closes the run by itself.\n const finalized = outcome.realtimeFinalizeAttempted\n ? outcome.realtimeFinalized\n : outcome.batchAcknowledged;\n\n let detail: string | null = null;\n if (!acknowledged) {\n detail = outcome.failureReason\n ? `upload failed (${outcome.failureReason}) — queued locally for retry`\n : 'upload failed — the platform did not acknowledge this run';\n } else if (!finalized) {\n detail = 'run acknowledged but not closed out — finalize failed and was queued for retry';\n }\n\n return {\n attempted: true,\n acknowledged,\n runId,\n endpoint: receipt.endpoint,\n finalized,\n artifactsUploaded: outcome.artifactsUploaded,\n artifactsTotal: outcome.artifactsTotal,\n detail,\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/reporter.ts","../src/streaming-writer.ts","../src/cloud-config.ts","../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-js-actions.ts","../src/html-js-console.ts","../src/html-js-artifacts.ts","../src/html-js-visual.ts","../src/html-js-dom.ts","../src/html-js-layout.ts","../src/html-js-api.ts","../src/html-css-visual.ts","../src/html-css-layout.ts","../src/html-css-api.ts","../src/html-template.ts","../src/browser-open.ts","../src/jsonl-stream.ts","../src/report-server-routes.ts","../src/report-server.ts","../src/artifact-server.ts","../src/html-report.ts","../src/visual-metrics.ts","../src/artifact-manager.ts","../src/visual-expect.ts","../src/visual-capture.ts","../src/ansi.ts","../src/artifact-index.ts","../src/gitignore-manager.ts","../src/watchdog.ts","../src/timeline-builder.ts","../src/summary-builder.ts","../src/console-summary.ts","../src/action-step-collector.ts","../src/cloud-auth.ts","../src/git-metadata.ts","../src/run-type-env.ts","../src/cloud-deadline.ts","../src/cloud-upload.ts","../src/cloud-queue.ts","../src/cloud-client.ts","../src/test-data-extractor.ts","../src/cloud-farm-detect.ts","../src/cloud-artifact-upload.ts","../src/cloud-reporter.ts","../src/cloud-receipt.ts"],"names":["REPORT_DIR_NAME","generateStreamingTestId","filePath","titlePath","project","retryIndex","input","createHash","generateRetryGroupKey","StreamingWriter","outputDir","join","mkdirSync","testId","data","files","testDir","metaPath","tmpPath","json","writeFileSync","renameSync","err","errorMsg","src","dest","copyFileSync","unlinkSync","entries","compact","_tp","_p","rest","summary","manifest","CONFIG_DIR","CONFIG_FILENAME","LEGACY_CONFIG_FILENAME","MAX_WALK_UP_LEVELS","DANGEROUS_KEYS","DEFAULT_CLOUD_CONFIG","DURATION_UNITS","discoverConfigFile","startDir","currentDir","resolve","i","candidate","existsSync","statSync","legacyCandidate","parentDir","dirname","parseConfigFile","content","readFileSync","parsed","isSafeObject","obj","key","val","resolveEnvVar","value","bracketMatch","dollarMatch","resolveEnvVars","result","parseDuration","match","amount","unit","multiplier","mergeCloudConfig","fileConfig","reporterOptions","target","cloud","repoSection","queue","ms","resolved","envApiKey","envEndpoint","isValidEndpointUrl","envUpload","envTimeout","config","isValidCloudConfig","DEFAULT_REDACTION_PATTERNS","DEFAULT_REDACT_HEADERS","DEFAULT_REDACT_BODY_FIELDS","resolveApiConfig","options","resolveConfig","isValidConfig","createError","ErrorCode","outputPath","fileConfigPath","resolvedFileConfig","cloudConfig","SCHEMA_VERSION","extractCodeSnippet","line","contextLines","lines","startLine","endLine","snippetLines","marker","lineNum","createRedactor","patterns","text","pattern","escaped","flags","cloned","detectGitHubActions","env","detectGitLabCI","detectJenkins","branch","detectCircleCI","detectBitbucketPipelines","runUrl","detectors","detectCI","envVars","detect","CSS","LOGO_SVG_RAW","LOGO_SVG","JS_RENDER","JS_NETWORK","JS_FILTERS","JS_INTERACTIONS","JS_ACTIONS","JS_CONSOLE","JS_ARTIFACTS","JS_VISUAL","JS_DOM","JS_LAYOUT","JS_ROUTER","JS_API","CSS_VISUAL","CSS_DOM","CSS_LAYOUT","CSS_DETAIL_PAGE","CSS_API","JS","renderHtmlDocument","reportJson","serverPort","manifestJson","safeJson","safeManifestJson","renderStreamingHtmlDocument","summaryJson","indexJson","safeSummary","safeIndex","safeManifest","summaryObj","streamingData","JS_STREAMING","openInBrowser","platform","file","args","execFile","tmpdir","readJsonlPage","page","pageSize","knownTotal","skip","items","lineCount","rl","createInterface","createReadStream","total","totalPages","SAFE_ID_PATTERN","TIMESTAMP_PATTERN","MAX_PAGE_SIZE","sendJson","res","status","body","setCorsHeaders","readJsonFile","getDirSize","dirPath","size","readdirSync","entry","fullPath","handleHealth","_req","reportDir","startTime","index","handleSummary","handleTests","req","params","statusFilter","fileFilter","searchQuery","tagFilter","sortField","sortOrder","filtered","t","tag","a","b","cmp","totalItems","startIdx","tests","handleTestDetail","legacyPath","handleTestDataFile","dataType","jsonlPath","meta","handleFiles","fileMap","stats","s","handleArtifactsList","artifactsDir","runs","totalSizeBytes","runDir","testDirs","e","handleArtifactsDeleteAll","deletedCount","freedBytes","rmSync","handleArtifactDelete","folderName","handleShutdown","server","serveStaticFile","ext","extname","contentType","MIME_TYPES","range","parseByteRange","pipeOrFail","stream","header","rawStart","rawEnd","start","end","suffix","DEFAULT_PORT","MAX_PORT_ATTEMPTS","INACTIVITY_TIMEOUT_MS","startReportServer","reject","startPort","inactivityTimer","currentAttempt","resetTimer","htmlReportPath","htmlFile","f","createServer","pathname","testDataMatch","testMatch","deleteMatch","relPath","tryListen","port","errorHandler","addr","startArtifactServer","handle","generateHtmlReport","report","writeHtmlReport","serverHandle","isStreamingMode","html","htmlDir","compactPath","htmlPath","shutdownExistingServers","spawnDetachedServer","keepAlive","cleanup","updatedHtml","tmpRewrite","absolutePath","detectExistingServer","controller","timer","resp","r","cliPath","p","resolvePort","child","spawn","stderr","timeout","chunk","PNG_SIGNATURE","IHDR_WIDTH_OFFSET","IHDR_HEIGHT_OFFSET","PNG_HEADER_BYTES","readPngDimensions","width","height","PIXELS_DIFFERENT_RE","SIZE_MISMATCH_RE","isSizeMismatchFailure","message","SNAPSHOT_NAME_RE","parseDiffMessage","pixels","count","sizes","ew","eh","aw","ah","v","name","computeDiffRatio","diffPixels","dimensions","COPY_CONCURRENCY","activeCopies","pendingCopies","enqueueCopy","copyFile","inflightCopies","flushPendingArtifactCopies","generateTimestampFolderName","now","pad","n","base","sanitizeFolderName","title","ID_SUFFIX_LENGTH","buildArtifactFolderName","testTitle","retryCount","copyArtifacts","attachments","runTimestamp","screenshot","video","trace","artifactSegments","artifactDir","relativeParts","destName","VISUAL_DIR","STEM_HASH_LENGTH","buildVisualFolderName","stem","hash","readDimensions","fd","openSync","bytesRead","readSync","closeSync","VISUAL_ROLES","DOM_ROLES","copyOneComparison","visualDir","relativePrefix","folder","destDir","paths","copiedAny","role","dom","measured","ratio","baselinePath","relative","copyVisualArtifacts","candidates","results","copied","VISUAL_DOM_ATTACHMENT","VISUAL_META_ATTACHMENT","VISUAL_SUFFIX_RE","parseVisualMeta","byStem","attachment","record","toMetaRecord","VALID_STATUSES","parseVisualDom","groupBySnapshotStem","groups","group","isCredibleComparison","deriveStatus","updatingSnapshots","failureMessage","selectMetricsTarget","failed","snapshotName","matches","parseVisualAttachments","updating","vouched","applyMetrics","foldByName","c","out","owner","merged","at","failedStems","stemOf","ANSI_PATTERN","stripAnsi","stripAnsiSafe","MANIFEST_SCHEMA_VERSION","classifyFileType","basename","classifyVisualFileType","scanVisualFolder","snapshotDir","relativeBase","stat","scanTestFolder","runFolder","testName","snapshot","scanRunFolder","testEntry","timestamp","buildArtifactManifest","currentRunTimestamp","legacyTests","legacySize","run","sum","MAX_WALK_DEPTH","findGitRoot","dir","depth","parent","ensureGitignore","gitRoot","gitignorePath","relativePath","l","appendFileSync","TIMED_OUT","CLOUD_INIT_TIMEOUT_MS","CLOUD_FINALIZE_TIMEOUT_MS","REPORT_FINALIZE_TIMEOUT_MS","CLOUD_DISPOSE_TIMEOUT_MS","withTimeout","operation","timeoutMs","label","buildUnifiedTimeline","allSteps","test","identity","buildTestIdentity","testResult","toTestResult","navSteps","buildNavigationSteps","apiSteps","buildApiCallSteps","compareSteps","recalculateNavigationDurations","step","steps","nav","assertions","call","linkedAssertions","response","parseBody","timeA","timeB","typeOrder","typeA","typeB","extractCallNumber","callId","navTime","nextStepTime","j","endTime","nearestRankPercentile","sorted","percentile","stripUrlQuery","rawUrl","getStatusRange","statusCode","buildEnrichedSummary","timelineLength","passed","flaky","skipped","timedout","allApiCalls","totalApiCalls","apiUrlSet","methodCounts","statusRanges","responseTimes","apiResponseTime","acc","totalAssertions","passedAssertions","failedAssertions","assertion","totalNavigations","navUrlSet","totalActionSteps","actionStepsByCategory","countActions","visualChecks","visualFailures","visualNew","visualDomChanges","comparison","padding","printConsoleSummary","quiet","top","bottom","blank","output","parts","methodParts","method","statusParts","catParts","catMap","catStr","INCLUDED_CATEGORIES","mapCategory","pwCategory","convertStep","testStartTime","children","videoOffsetMs","collectActionSteps","LOCALHOST_HOSTS","enforceHttps","endpoint","url","HEALTH_CHECK_TIMEOUT_MS","healthCheck","sleep","parseCloudError","error","details","detailMessages","d","exchangeToken","apiKey","retryAfter","waitMs","retryResponse","isAuthError","refreshAccessToken","currentRefreshToken","resolveRepo","accessToken","gitId","displayName","requestBody","responseBody","GIT_TIMEOUT_MS","execGit","command","cwd","execSync","collectGitMetadata","commitSha","commitMessage","commitAuthor","rawRemoteUrl","getRemoteUrl","remoteUrl","normalizeGitRemoteUrl","originUrl","remotes","firstRemote","normalized","deriveRepoDisplayName","normalizedUrl","segments","readPackageJsonName","raw","pkg","deriveNonGitProjectId","pkgName","ALLOWED","resolveSdkRunTypeFromEnv","init","putDeadlineMs","sizeBytes","scaled","Budget","totalMs","requestMs","GZIP_THRESHOLD_BYTES","RETRY_DELAYS_MS","MAX_RETRIES","DEFAULT_REQUEST_TIMEOUT_MS","MAX_BATCH_PAYLOAD_BYTES","RUN_NONAPI_SLUGS","deriveRunTestType","counts","proto","primaryProtocol","max","mostCommon","slug","buildUploadPayload","repoGitId","git","ci","cloudFarm","environment","runType","typeSignal","apiSignal","effectiveTests","bodyBytes","toApiReportCalls","records","toNetworkRequestsFromApiCalls","ts","toProtocolAssertions","fmt","expr","kind","detail","stripNulls","retryWithBackoff","onTokenRefresh","budget","attempt","slice","newToken","refreshedInit","gzipAsync","promisify","gzip","stripOversizeTests","payload","dropApiCalls","_c","_n","apiCalls","HEAVY_TEST_KEYS","stripToMinimalTests","heavy","k","stripAllTests","heavyRunKeys","prepareBody","maxBytes","headers","carrier","testCount","over","limitMb","gz","backing","uploadBatchRun","initRealtimeRun","uploadTestResult","cloudRunId","testData","finalizeRun","finalizeRunFireAndForget","startedAt","QUEUE_ENTRY_VERSION","FLUSH_BATCH_SIZE","errorMessage","isPermanentHttpFailure","writeToQueue","queueDirectory","runId","type","reason","targetEndpoint","filename","flushQueue","batches","batch","queueEntry","isValidQueueEntry","flushBody","prepared","flushHeaders","cleanupExpiredQueue","maxAge","queuedAt","queuedTime","TOKEN_REFRESH_BUFFER_MS","PROJECT_CACHE_TTL_MS","HEALTH_CHECK_INTERVAL_MS","FLUSH_TIMEOUT_MS","DASHBOARD_SETTINGS_URL","unrefTimeout","CloudClient","tokenResult","expiresAt","refreshResult","code","effectiveGitId","cached","cachePath","cache","currentKeyHash","repoId","cacheDir","queueDir","addStatusMix","extractTestData","annotations","isSkipped","navigations","apiAssertions","networkRequestsFile","networkRequestsCount","consoleLogsFile","consoleLogsCount","apiCallsFile","apiCallsCount","apiStatusMix","payloadAttachments","ATTACHMENT_NAME","attachmentParsed","att","isTestRelicFilePayload","isTestRelicDataPayload","payloadLogs","detectCloudFarm","explicitPlatform","normalizePlatform","ws","UPLOAD_CONCURRENCY","JSON_REQUEST_TIMEOUT_MS","CONTENT_TYPE_MAP","activeUploads","pendingUploads","acquireSlot","releaseSlot","getContentType","getFileSize","requestUploadUrl","request","putFileToPresignedUrl","presignedUrl","deadlineMs","nodeStream","webStream","Readable","confirmUpload","artifactId","uploadArtifact","maxSizeMb","urlResult","confirmed","uploadArtifacts","requests","promises","getEffectiveGitId","cloudClient","initCloudRun","uploadStrategy","testRunId","gitMeta","ciMeta","VISUAL_UPLOAD_ROLES","DEFAULT_UPLOAD_ARTIFACTS","DEFAULT_ARTIFACT_MAX_SIZE_MB","NO_ARTIFACTS","uploadRunArtifacts","artifactEntries","visualResult","requestToEntry","visualKeys","absPath","comparisons","vi","uploadResults","keysByTest","uploadResult","existing","perTest","perComparison","keys","uploaded","finalizeAndUpload","completedAt","totalDuration","outcome","noteFailure","tokenValid","gitMetaForFinalize","finalizeTypeSignal","finalizePayload","finalized","artifactKeyMap","visualKeyMap","artifactOutcome","batchGit","batchCi","batchGitId","enrichedReport","injectVisualKeys","injectArtifactKeys","testsWithVisualKeys","failure","queueGit","queueCi","queueGitId","queuePayload","withKeys","enrichedTimeline","changed","rewritten","keyMap","KNOWN_STRATEGIES","UNATTEMPTED_RECEIPT","describeLocalMode","buildInitialReceipt","strategy","isRealtime","applyFinalizeOutcome","receipt","acknowledged","readJsonlSync","item","errorHeadline","informative","first","second","mapPlaywrightStatus","generateTestId","suiteName","getSuiteName","getRetryStatus","passedIndex","statusRangeKey","statusMixOf","mix","firstFailedAssertion","stringifyAssertionValue","countAssertions","accumulateActionCategories","into","buildAggResponseTime","times","MAX_AGG_RESPONSE_SAMPLES","MAX_AGG_UNIQUE_URLS","buildAggContribution","apiCallsRaw","actions","urlSet","codeRaw","rtRaw","emptyStreamingAgg","detectSource","detectOsName","osPlatform","getBrowserDisplayName","use","browserName","channel","ch","bn","defaultBrowserType","dbt","getTestType","tags","inferred","tagTypes","normalizedPath","pathTypes","API_CONFIG_ANNOTATION","serializeApiConfig","_key","TestRelicReporter","testKey","contribution","_suite","updateSnapshots","randomUUID","totalTests","shutdownHandler","partialSummary","token","initialized","embeddedShutdownHandler","_result","lastResult","extracted","firstError","redact","pwFile","assertionLoc","useAssertionLoc","errorFile","errorLine","codeSnippet","specFile","baseTestType","testType","isFlaky","retryStatus","expectedStatus","actualStatus","artifacts","lastTitle","visual","browser","collectedTest","batchConsoleLogs","batchNetworkRequests","batchApiCallsRaw","batchApiCalls","batchNetworkFromApi","batchProtocolAssertions","streamId","isFinalAttempt","detailData","indexEntry","withDiff","fa","startedAtTime","hasNonSkippedTests","artifactManifest","manifestPath","timeline","lastByTestId","finalAttempts","cloudTests","client","pwResult","bestByTestKey","indexFinalCount","bestRetry","folded","m","cat","u","rt","validation","enrichedSummary","writer","settledSummary","_streamingSummary","usage","heapMB","heapRatio","ct","switchApiCallsRaw","networkRequests","consoleLogs"],"mappings":"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AAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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,EAAAA,CAAe,CAAA;AAAA;AAAA;AAAA;AAAA,8BAAA,CAAA,CAMfC,EAAAA,CAAW,CAAA;AAAA;AAAA;AAAA;ACRjB,8BAAA,CAAA,CAAA,IAAMC,EAAAA,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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACFxB,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;ACDnB,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;ACCnB,CAAA,CAAA,IAAMC,EAAAA,CAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACWrB,CAAA,CAAA,IAAMC,EAAAA,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;ACFlB,CAAA,CAAA,IAAMC,EAAAA,CAAS;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACJf,CAAA,CAAA,IAAMC,EAAAA,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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA,CA4TZC,EAAAA,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;AC3TlB,CAAA,CAAA,IAAMC,EAAAA,CAAS;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACEf,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,CAAA,CA0CbC,EAAAA,CAAU;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AC/ChB,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,CAAA,CAuIbC,EAAAA,CAAkB;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;ACvIxB,CAAA,CAAA,IAAMC,EAAAA,CAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","file":"reporter-entry.js","sourcesContent":["/**\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, readFileSync } from 'node:fs';\nimport { dirname, join, relative, resolve } from 'node:path';\nimport { platform as osPlatform } from 'node:os';\nimport type {\n ActionStep,\n ApiAssertion,\n ApiCallsByStatusRange,\n FailedAssertionSummary,\n ReporterConfig,\n TestRunReport,\n Summary,\n TimelineStep,\n TestResult as TRTestResult,\n TestArtifacts,\n VisualComparison,\n NavigationAnnotation,\n FailureDiagnostic,\n TestStatus,\n TestType,\n ReportMode,\n TestIndexEntry,\n TestDetailData,\n StreamingReportSummary,\n StreamingReportManifest,\n StreamingFileStats,\n CapturedNetworkRequest,\n ApiCallRecord,\n ConsoleLogEntry as CoreConsoleLogEntry,\n} from '@testrelic/core';\nimport { StreamingWriter, generateStreamingTestId, generateRetryGroupKey } from './streaming-writer.js';\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, copyVisualArtifacts, generateTimestampFolderName, flushPendingArtifactCopies } from './artifact-manager.js';\nimport { parseVisualAttachments } from './visual-capture.js';\nimport { stripAnsi } from './ansi.js';\nimport { buildArtifactManifest } from './artifact-index.js';\nimport { ensureGitignore } from './gitignore-manager.js';\nimport {\n withTimeout,\n CLOUD_INIT_TIMEOUT_MS,\n CLOUD_FINALIZE_TIMEOUT_MS,\n REPORT_FINALIZE_TIMEOUT_MS,\n CLOUD_DISPOSE_TIMEOUT_MS,\n} from './watchdog.js';\nimport { buildUnifiedTimeline } from './timeline-builder.js';\nimport type { TimelineTestData } from './timeline-builder.js';\nimport { buildEnrichedSummary, stripUrlQuery, nearestRankPercentile } from './summary-builder.js';\nimport { printConsoleSummary } from './console-summary.js';\nimport { collectActionSteps } from './action-step-collector.js';\nimport { CloudClient } from './cloud-client.js';\nimport { uploadTestResult, finalizeRunFireAndForget, toApiReportCalls, toNetworkRequestsFromApiCalls, toProtocolAssertions } from './cloud-upload.js';\nimport type { TestResultForUpload } from './cloud-upload.js';\nimport { extractTestData } from './test-data-extractor.js';\nimport { initCloudRun, finalizeAndUpload } from './cloud-reporter.js';\nimport type { ArtifactEntry } from './cloud-reporter.js';\nimport { buildInitialReceipt, applyFinalizeOutcome, UNATTEMPTED_RECEIPT } from './cloud-receipt.js';\nimport type { CloudUploadReceipt, WithCloudUpload } from './cloud-receipt.js';\n\n/** The streaming summary as this reporter writes it — always carrying the upload receipt. */\ntype SummaryWithReceipt = WithCloudUpload<StreamingReportSummary>;\n\n/** Read all entries from a JSONL file synchronously. */\nfunction readJsonlSync(filePath: string): unknown[] {\n try {\n const content = readFileSync(filePath, 'utf-8');\n return content\n .split('\\n')\n .filter(line => line.length > 0)\n .map(line => { try { return JSON.parse(line); } catch { return null; } })\n .filter((item): item is unknown => item !== null);\n } catch {\n return [];\n }\n}\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 PwProjectUse {\n browserName?: string;\n channel?: string;\n isMobile?: boolean;\n [key: string]: unknown;\n}\n\ninterface PwFullProject {\n use: PwProjectUse;\n [key: string]: unknown;\n}\n\ninterface PwSuiteNode {\n project?(): PwFullProject | undefined;\n parent?: PwSuiteNode;\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 parent?: PwSuiteNode;\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 steps?: readonly PwTestStep[];\n}\n\ninterface PwTestStep {\n title: string;\n category: string;\n startTime: Date;\n duration: number;\n error?: { message?: string };\n steps: readonly PwTestStep[];\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\n/**\n * The one line of a failure worth putting in a list.\n *\n * Playwright's first line is the matcher wrapper — identical across every\n * failure of that kind. The informative line is the one after it.\n */\nexport function errorHeadline(message: string | null | undefined): string | null {\n if (!message) return null;\n const lines = message\n .split('\\n')\n .map((line) => line.trim())\n .filter(Boolean);\n if (lines.length === 0) return null;\n const informative = lines.filter((line) => !/^Error:\\s*expect\\(/.test(line));\n if (informative.length === 0) return lines[0] ?? null;\n\n // Playwright's matcher output is \"Expected: 5\" then \"Received: 4\" on separate\n // lines. Returning only the first left the row saying what was expected and\n // never what actually happened, which is the half that matters. Pair them\n // when the second line is the received value.\n const first = informative[0] as string;\n const second = informative[1];\n if (second !== undefined && /^Received:/.test(second) && /^Expected:/.test(first)) {\n return `${first} · ${second}`;\n }\n return first;\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 is already filter(Boolean)'d: ['project', 'file.spec.ts', ...suites..., 'test title']\n // <= 3 elements means no describe block (project, file, test)\n if (titlePath.length <= 3) 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\n/** Status-range bucket for a response code (local copy that returns a plain\n * string union, so it does not depend on the `ApiCallsByStatusRange` keyof type). */\nfunction statusRangeKey(code: number | null): '2xx' | '3xx' | '4xx' | '5xx' | 'error' {\n if (code === null) return 'error';\n if (code >= 200 && code < 300) return '2xx';\n if (code >= 300 && code < 400) return '3xx';\n if (code >= 400 && code < 500) return '4xx';\n if (code >= 500 && code < 600) return '5xx';\n return 'error';\n}\n\n/** Tally a list of raw API call records into a status mix. */\nfunction statusMixOf(records: readonly Record<string, unknown>[]): ApiCallsByStatusRange {\n const mix: ApiCallsByStatusRange = { '2xx': 0, '3xx': 0, '4xx': 0, '5xx': 0, error: 0 };\n const out = mix as unknown as Record<string, number>;\n for (const r of records) {\n const code = typeof r.responseStatusCode === 'number' ? r.responseStatusCode : null;\n out[statusRangeKey(code)]++;\n }\n return mix;\n}\n\n/**\n * Flatten the first failed assertion for the index row.\n *\n * Values are arbitrary JSON; they are stringified once here so the client\n * never has to, and truncated so one huge response body cannot bloat the\n * index every row of the grid is built from.\n */\nfunction firstFailedAssertion(\n assertions: readonly ApiAssertion[] | null | undefined,\n): FailedAssertionSummary | undefined {\n if (!assertions) return undefined;\n for (const a of assertions) {\n if (a.status !== 'failed') continue;\n return {\n type: a.type,\n expected: stringifyAssertionValue(a.expected),\n actual: stringifyAssertionValue(a.actual),\n ...(a.expression ? { expression: a.expression } : {}),\n };\n }\n return undefined;\n}\n\n/** One assertion value as a short display string. */\nfunction stringifyAssertionValue(v: unknown): string {\n if (v === undefined) return 'undefined';\n if (v === null) return 'null';\n let s: string;\n if (typeof v === 'string') s = v;\n else {\n try { s = JSON.stringify(v) ?? String(v); } catch { s = String(v); }\n }\n return s.length > 200 ? s.slice(0, 200) + '\\u2026' : s;\n}\n\n/** How many assertions in a list landed on the given outcome. */\nfunction countAssertions(\n assertions: readonly ApiAssertion[] | null | undefined,\n outcome: 'passed' | 'failed',\n): number {\n if (!assertions) return 0;\n let n = 0;\n for (const a of assertions) if (a.status === outcome) n++;\n return n;\n}\n\n/** Recursively tally action steps by category into `into` (streaming aggregate). */\nfunction accumulateActionCategories(steps: readonly ActionStep[], into: Record<string, number>): void {\n for (const step of steps) {\n into[step.category] = (into[step.category] ?? 0) + 1;\n if (step.children.length > 0) accumulateActionCategories(step.children, into);\n }\n}\n\n/** Build API response-time stats from accumulated per-call times (null if none). */\nfunction buildAggResponseTime(times: number[]): Summary['apiResponseTime'] {\n if (times.length === 0) return null;\n const sorted = [...times].sort((a, b) => a - b);\n const sum = sorted.reduce((acc, v) => acc + v, 0);\n return {\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// Hard caps so both the per-test contributions AND the folded run-level aggregates\n// stay bounded. `responseTimes` would otherwise grow O(total API calls) and the\n// unique-URL sets O(URL cardinality) on a long crawl. The caps are applied in BOTH\n// places — per attempt in buildAggContribution (URLs deduped into a Set, response-time\n// samples capped) and again in the finalize fold — so a single mega-test retained in\n// `streamingTestAgg` can't blow up memory before finalize. The caps are generous, so\n// percentiles and unique counts stay exact for any realistic run and only clamp\n// pathological volumes; the folded aggregates are O(cap), not O(run size).\nconst MAX_AGG_RESPONSE_SAMPLES = 200_000;\nconst MAX_AGG_UNIQUE_URLS = 100_000;\n\n/** A single test attempt's contribution to the run-level streaming aggregates.\n * Stored per-test (highest retry wins) in `streamingTestAgg` and folded at finalize\n * for FINAL attempts only, so retries never inflate the run-level stats. */\ninterface StreamingAggContribution {\n retryIndex: number;\n apiCalls: number;\n apiUrls: string[];\n methodCounts: Record<string, number>;\n statusRanges: Record<string, number>;\n responseTimes: number[];\n passedAssertions: number;\n failedAssertions: number;\n assertions: number;\n navigations: number;\n navUrls: string[];\n networkRequests: number;\n consoleLogs: number;\n actionSteps: number;\n actionCategories: Record<string, number>;\n}\n\n/** Compute one test attempt's contribution to the streaming run aggregates from data\n * already in hand. `apiCallsRaw` is the parsed JSONL for the attempt; non-finite\n * status codes / response times are skipped so a NaN never poisons the stats. */\nfunction buildAggContribution(\n retryIndex: number,\n apiCallsRaw: Record<string, unknown>[] | undefined,\n apiAssertions: ReadonlyArray<{ status?: string }>,\n navigations: ReadonlyArray<{ url?: string | null }>,\n networkRequestsCount: number,\n consoleLogsCount: number,\n actions: readonly ActionStep[] | null,\n): StreamingAggContribution {\n const c: StreamingAggContribution = {\n retryIndex,\n apiCalls: apiCallsRaw?.length ?? 0,\n apiUrls: [],\n methodCounts: {},\n statusRanges: { '2xx': 0, '3xx': 0, '4xx': 0, '5xx': 0, error: 0 },\n responseTimes: [],\n passedAssertions: 0,\n failedAssertions: 0,\n assertions: apiAssertions.length,\n navigations: navigations.length,\n navUrls: [],\n networkRequests: networkRequestsCount,\n consoleLogs: consoleLogsCount,\n actionSteps: actions?.length ?? 0,\n actionCategories: {},\n };\n if (apiCallsRaw) {\n // Dedup URLs into a Set and cap response-time samples WITHIN this attempt's\n // contribution — not just at the finalize fold. The contribution is retained in\n // streamingTestAgg until finalize, so without per-test bounds a single long crawl\n // (e.g. 1M calls over 5 URLs) would retain 1M url strings here; deduping per test\n // keeps it O(unique URLs) — matching the pre-streaming Set-based accounting.\n const urlSet = new Set<string>();\n for (const call of apiCallsRaw) {\n const method = typeof call.method === 'string' ? call.method : 'OTHER';\n c.methodCounts[method] = (c.methodCounts[method] ?? 0) + 1;\n const url = typeof call.url === 'string' ? call.url : null;\n if (url && urlSet.size < MAX_AGG_UNIQUE_URLS) urlSet.add(stripUrlQuery(url));\n const codeRaw = typeof call.responseStatusCode === 'number' ? call.responseStatusCode\n : typeof call.statusCode === 'number' ? call.statusCode : null;\n const code = codeRaw !== null && Number.isFinite(codeRaw) ? codeRaw : null;\n c.statusRanges[statusRangeKey(code)]++;\n const rtRaw = typeof call.responseTimeMs === 'number' ? call.responseTimeMs\n : typeof call.durationMs === 'number' ? call.durationMs : null;\n if (rtRaw !== null && Number.isFinite(rtRaw) && c.responseTimes.length < MAX_AGG_RESPONSE_SAMPLES) {\n c.responseTimes.push(rtRaw);\n }\n }\n c.apiUrls = Array.from(urlSet);\n }\n for (const assertion of apiAssertions) {\n if (assertion.status === 'passed') c.passedAssertions++;\n else c.failedAssertions++;\n }\n const navUrlSet = new Set<string>();\n for (const nav of navigations) {\n if (nav.url && navUrlSet.size < MAX_AGG_UNIQUE_URLS) navUrlSet.add(nav.url);\n }\n c.navUrls = Array.from(navUrlSet);\n if (actions) accumulateActionCategories(actions, c.actionCategories);\n return c;\n}\n\n/** Empty run-level aggregate accumulator used by the finalize fold. */\nfunction emptyStreamingAgg(): {\n apiUrlSet: Set<string>;\n methodCounts: Record<string, number>;\n statusRanges: Record<string, number>;\n responseTimes: number[];\n passedAssertions: number;\n failedAssertions: number;\n navUrlSet: Set<string>;\n actionCategoryCounts: Record<string, number>;\n volApiCalls: number;\n volAssertions: number;\n volNavigations: number;\n volNetworkRequests: number;\n volConsoleLogs: number;\n volActionSteps: number;\n} {\n return {\n apiUrlSet: new Set<string>(),\n methodCounts: {},\n statusRanges: { '2xx': 0, '3xx': 0, '4xx': 0, '5xx': 0, error: 0 },\n responseTimes: [],\n passedAssertions: 0,\n failedAssertions: 0,\n navUrlSet: new Set<string>(),\n actionCategoryCounts: {},\n volApiCalls: 0,\n volAssertions: 0,\n volNavigations: 0,\n volNetworkRequests: 0,\n volConsoleLogs: 0,\n volActionSteps: 0,\n };\n}\n\nfunction detectSource(ci: import('@testrelic/core').CIMetadata | null): string {\n if (process.env.BROWSERSTACK_USERNAME) return 'BrowserStack';\n if (!ci) return 'Local';\n const map: Record<string, string> = {\n 'github-actions': 'GitHub Actions',\n 'gitlab-ci': 'GitLab CI',\n 'jenkins': 'Jenkins',\n 'circleci': 'CircleCI',\n 'bitbucket-pipelines': 'Bitbucket Pipelines',\n };\n return map[ci.provider] ?? 'CI';\n}\n\nfunction detectOsName(): string {\n const p = osPlatform();\n if (p === 'win32') return 'Windows';\n if (p === 'darwin') return 'macOS';\n return 'Linux';\n}\n\nfunction getBrowserDisplayName(test: Pick<PwTestCase, 'parent'>): string | undefined {\n const use = test.parent?.project?.()?.use;\n if (!use) return undefined;\n\n const { browserName, channel } = use;\n\n // Channel takes priority — it is a specific browser distribution (Chrome, Edge, etc.)\n if (channel) {\n const ch = channel.toLowerCase();\n if (ch === 'chrome' || ch === 'chrome-stable') return 'Chrome';\n if (ch.startsWith('chrome-beta')) return 'Chrome Beta';\n if (ch.startsWith('chrome-dev')) return 'Chrome Dev';\n if (ch.startsWith('chrome-canary')) return 'Chrome Canary';\n if (ch === 'msedge' || ch === 'msedge-stable') return 'Microsoft Edge';\n if (ch.startsWith('msedge-beta')) return 'Edge Beta';\n if (ch.startsWith('msedge-dev')) return 'Edge Dev';\n if (ch.startsWith('msedge-canary')) return 'Edge Canary';\n return channel; // fallback: show channel value as-is\n }\n\n if (browserName) {\n const bn = browserName.toLowerCase();\n if (bn === 'chromium') return 'Chromium';\n if (bn === 'firefox') return 'Firefox';\n if (bn === 'webkit') return 'WebKit';\n return browserName;\n }\n\n // defaultBrowserType is set by Playwright device presets (e.g. devices['Desktop Chrome'])\n // even though browserName is not explicitly set in project.use.\n const defaultBrowserType = use.defaultBrowserType as string | undefined;\n if (defaultBrowserType) {\n const dbt = defaultBrowserType.toLowerCase();\n if (dbt === 'chromium') return 'Chromium';\n if (dbt === 'firefox') return 'Firefox';\n if (dbt === 'webkit') return 'WebKit';\n return defaultBrowserType;\n }\n\n return undefined;\n}\n\nfunction getTestType(\n tags: string[],\n filePath: string,\n test: { parent?: PwSuiteNode },\n): TestType {\n // Step 1: Official Playwright project config — most authoritative signal,\n // requires zero changes to user test suites.\n // project().use.isMobile is set by all Playwright mobile device presets.\n // project().use.browserName is set when explicitly configured (e.g. devices['Desktop Chrome']).\n // NOTE: FullProject.use only contains user-specified values, NOT Playwright's resolved defaults.\n // A project with `use: {}` has browserName === undefined but still launches chromium by default.\n // Therefore any project that exists and is not mobile is treated as a browser/Web test.\n // 'api' type must come from explicit signals (tag @api or /api/ path segment).\n const project = test.parent?.project?.();\n let inferred: TestType = 'unknown';\n if (project !== undefined) {\n const use = project.use;\n if (use.isMobile === true) inferred = 'mobile';\n else inferred = 'e2e';\n }\n\n // Step 2: Explicit tag override — user intent beats project config.\n const tagTypes: TestType[] = ['e2e', 'api', 'unit', 'mobile'];\n for (const type of tagTypes) {\n if (tags.some((tag) => tag === `@${type}` || tag === type)) return type;\n }\n\n // Step 3: File path segment convention — opt-in folder structure.\n const normalizedPath = filePath.replace(/\\\\/g, '/');\n const pathTypes: TestType[] = ['e2e', 'api', 'unit'];\n for (const type of pathTypes) {\n if (normalizedPath.includes(`/${type}/`)) return type;\n }\n\n return inferred;\n}\n\n// Internal state for collected test data (v3: file-based heavy 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 visual: VisualComparison[] | null;\n apiAssertions: ApiAssertion[] | null;\n actions: ActionStep[] | null;\n // File-based heavy data references\n networkRequestsFile: string | null;\n networkRequestsCount: number;\n consoleLogsFile: string | null;\n consoleLogsCount: number;\n apiCallsFile: string | null;\n apiCallsCount: number;\n apiStatusMix: ApiCallsByStatusRange | null;\n // Environment metadata\n browser?: string;\n os?: string;\n source?: string;\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 private fixtureDataReceived = false;\n /** True once any test produced navigation data this run (pre-toggle-drop). */\n private navigationsCaptured = false;\n /** Set once the memory-pressure warning has fired, so it prints at most once per run. */\n private memoryPressureWarned = false;\n private testCount = 0;\n private cloudClient: CloudClient | null = null;\n private cloudRunId: string | null = null;\n private runTimestamp = '';\n\n /**\n * True when the run was started with `--update-snapshots`.\n *\n * A snapshot written under that flag was never compared against anything,\n * so it must not be reported as a pass. Nothing in the attachments\n * distinguishes a rewritten baseline from a first-time one, which is why\n * the flag has to be read from the run's config and carried down here.\n */\n private updatingSnapshots = false;\n\n /**\n * Whether the platform acknowledged this run — written into every summary as\n * `cloudUpload`. Seeded here so an interrupt before `onBegin` finishes still\n * writes a verdict rather than leaving the block missing; `onBegin` replaces it\n * with the real state, and the upload phase replaces it again with the outcome.\n */\n private cloudReceipt: CloudUploadReceipt = UNATTEMPTED_RECEIPT;\n\n // Artifact entries accumulated during onTestEnd for cloud upload (survives streaming cleanup)\n private pendingArtifactEntries: ArtifactEntry[] = [];\n\n // Environment metadata detected once in onBegin, applied to all tests\n private detectedOs = '';\n private detectedSource = '';\n\n // Lightweight per-test cloud upload buffer — persists through both embedded and streaming modes\n private cloudTestsBuffer: TestResultForUpload[] = [];\n\n // Streaming mode fields\n private activeReportMode: ReportMode = 'embedded';\n private streamingWriter: StreamingWriter | null = null;\n private testIndex: TestIndexEntry[] = [];\n private summaryCounters = {\n total: 0, passed: 0, failed: 0, flaky: 0,\n skipped: 0, timedOut: 0, interrupted: 0,\n totalApiCalls: 0, totalAssertions: 0, totalNavigations: 0,\n totalNetworkRequests: 0, totalConsoleLogs: 0, totalActionSteps: 0,\n // Visual totals for the run overview. Streaming builds its summary from\n // these counters rather than from buildEnrichedSummary, so they have to be\n // tallied here or the report's \"captured this run\" panel comes up empty.\n visualChecks: 0, visualFailures: 0, visualDomChanges: 0,\n };\n\n // Streaming-mode run-level aggregate inputs, stored PER TEST (highest-retry attempt\n // wins) and folded at finalize for FINAL attempts only — so the cloud payload built\n // from the streaming summary carries REAL API/assertion/navigation/action stats that\n // dedup retries exactly like the status counts (the per-attempt `isFinalAttempt`\n // flag can't dedup: at each onTestEnd the just-finished attempt is the last one in\n // `test.results`, so every attempt looks \"final\"). Keyed by `${filePath}::${titlePath}`\n // to match the bestByTestKey dedup used for status counts. One small entry per test.\n private streamingTestAgg = new Map<string, StreamingAggContribution>();\n\n /** Record a test attempt's aggregate contribution, keeping only the highest retry\n * per test so retries don't inflate the run-level stats (robust to out-of-order\n * attempt delivery: a later, lower retry never displaces a stored higher one). */\n private upsertAggContribution(testKey: string, contribution: StreamingAggContribution): void {\n const existing = this.streamingTestAgg.get(testKey);\n if (!existing || contribution.retryIndex > existing.retryIndex) {\n this.streamingTestAgg.set(testKey, contribution);\n }\n }\n\n constructor(options?: Partial<ReporterConfig>) {\n this.config = resolveConfig(options);\n this.apiConfig = resolveApiConfig(options);\n }\n\n async onBegin(config: PwFullConfig, _suite: PwSuite): Promise<void> {\n try {\n this.rootDir = config.rootDir;\n // SAFETY: Playwright's FullConfig does carry `updateSnapshots`, but this\n // reporter declares Playwright's types locally rather than importing\n // them, so the field arrives as `unknown` and is narrowed here.\n const updateSnapshots = config.updateSnapshots;\n // Only 'all' and 'changed' rewrite a baseline that already existed.\n // 'missing' is the DEFAULT for an ordinary run and writes a baseline only\n // where none was there — which is the 'new' case, not an update.\n this.updatingSnapshots = updateSnapshots === 'all' || updateSnapshots === 'changed';\n this.startedAt = new Date().toISOString();\n this.testRunId = this.config.testRunId ?? randomUUID();\n\n // Resolve report mode: auto detects based on suite size, streaming is default\n if (this.config.reportMode === 'auto') {\n const totalTests = _suite.allTests().length;\n this.activeReportMode = totalTests >= this.config.streamingThreshold ? 'streaming' : 'embedded';\n } else {\n this.activeReportMode = this.config.reportMode;\n }\n const totalTests = _suite.allTests().length;\n process.stderr.write(\n `[testrelic] Report mode: ${this.activeReportMode} (${totalTests} tests)\\n`,\n );\n\n // Initialize streaming writer if in streaming mode\n if (this.activeReportMode === 'streaming') {\n const outputDir = dirname(this.config.outputPath);\n this.streamingWriter = new StreamingWriter(outputDir);\n // Graceful shutdown: write partial index/summary and finalize cloud run on interrupt\n const shutdownHandler = (): void => {\n if (this.streamingWriter) {\n try {\n this.streamingWriter.writeIndex(this.testIndex as TestIndexEntry[]);\n const partialSummary: SummaryWithReceipt = {\n testRunId: this.testRunId ?? '',\n startedAt: this.startedAt,\n completedAt: new Date().toISOString(),\n totalDuration: Date.now() - new Date(this.startedAt).getTime(),\n ...this.summaryCounters,\n metadata: null,\n reportMode: 'streaming',\n files: [],\n enrichedSummary: null,\n validation: null,\n writeErrors: this.streamingWriter.getWriteErrors(),\n // A killed run gets an honest verdict too. The finalize below is\n // fire-and-forget, so it is NOT claimed as finalized here.\n cloudUpload: this.cloudReceipt,\n };\n this.streamingWriter.writeSummary(partialSummary);\n } catch { /* best effort */ }\n this.streamingWriter.dispose();\n }\n // Best-effort cloud finalize so the run doesn't stay stuck in \"Running\"\n try {\n if (this.cloudClient?.isCloudMode() && this.cloudRunId) {\n const token = this.cloudClient.getAccessToken();\n if (token) {\n finalizeRunFireAndForget(\n this.cloudClient.getEndpoint(),\n token,\n this.cloudRunId,\n this.startedAt,\n );\n }\n }\n } catch { /* best effort */ }\n };\n process.on('SIGTERM', shutdownHandler);\n process.on('SIGINT', shutdownHandler);\n }\n\n // Generate a timestamp-based folder name for this run's artifacts\n if (this.config.includeArtifacts) {\n const outputDir = dirname(this.config.outputPath);\n this.runTimestamp = generateTimestampFolderName(join(outputDir, 'artifacts'));\n\n // Auto-add artifact directory to .gitignore\n try {\n ensureGitignore(join(outputDir, 'artifacts'));\n } catch {\n // FR-026: gitignore failure never crashes the test run\n }\n }\n\n // Detect environment metadata once for the whole run\n const ci = detectCI();\n this.detectedOs = detectOsName();\n this.detectedSource = detectSource(ci);\n\n // Cloud integration: initialize client.\n //\n // This is the single most dangerous await in the reporter: Playwright does\n // not start ANY test until `onBegin` resolves, so a stalled auth/health/run-init\n // call here wedges the whole run before the first test. The individual fetches\n // carry AbortController deadlines, but the phase as a whole had no ceiling —\n // a watchdog bounds it regardless of which call misbehaves (no key, invalid\n // key, black-holed endpoint, proxy that accepts and never answers).\n const initialized = await withTimeout(\n async () => {\n this.cloudClient = new CloudClient(this.config.cloud);\n await this.cloudClient.initialize();\n return initCloudRun(\n this.cloudClient,\n this.config.cloud?.uploadStrategy,\n this.testRunId,\n this.startedAt,\n this.detectedSource,\n );\n },\n CLOUD_INIT_TIMEOUT_MS,\n 'Cloud initialization',\n );\n this.cloudRunId = initialized ?? null;\n if (initialized === undefined) {\n // Timed out or threw — degrade to local reporting and keep testing.\n this.cloudClient?.switchToLocalMode('init_error');\n }\n\n // Record the upload verdict as it stands BEFORE any test runs. `summary.json`\n // has only ever carried `testRunId`, which is generated locally and therefore\n // says nothing about whether the run reached the platform; this block does.\n this.cloudReceipt = buildInitialReceipt(\n this.cloudClient,\n this.config.cloud?.uploadStrategy,\n this.cloudRunId,\n );\n\n // For embedded mode, register a signal handler to finalize the cloud run\n // on interrupt (streaming mode already has its own handler above).\n if (this.activeReportMode !== 'streaming') {\n const embeddedShutdownHandler = (): void => {\n try {\n if (this.cloudClient?.isCloudMode() && this.cloudRunId) {\n const token = this.cloudClient.getAccessToken();\n if (token) {\n finalizeRunFireAndForget(\n this.cloudClient.getEndpoint(),\n token,\n this.cloudRunId,\n this.startedAt,\n );\n }\n }\n } catch { /* best effort */ }\n };\n process.on('SIGTERM', embeddedShutdownHandler);\n process.on('SIGINT', embeddedShutdownHandler);\n }\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 from attachments/annotations (v2: file-based heavy data)\n let extracted = extractTestData(\n lastResult.attachments,\n test.annotations,\n test.title,\n status === 'skipped',\n );\n if (extracted.navigations.length > 0 || extracted.networkRequestsCount > 0 || extracted.apiCallsCount > 0) {\n this.fixtureDataReceived = true;\n }\n // Track whether the fixture produced ANY navigation this run (pre-drop), so\n // onEnd can distinguish \"fixture active but nothing navigated\" from \"fixture\n // never ran\" and from \"navigation disabled\".\n if (extracted.navigations.length > 0) {\n this.navigationsCaptured = true;\n }\n // Master Test Navigation toggle: when disabled, drop navigation data before\n // it reaches any payload. Capture still runs in the worker (harmless); we\n // gate at the reporter boundary where the resolved config is available. A\n // single run-level notice is emitted in onEnd.\n if (!this.config.captureNavigation && extracted.navigations.length > 0) {\n extracted = { ...extracted, navigations: [] };\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\n // Playwright derives the error location from the throwing frame.\n // For a captured API assertion that frame is inside our own matcher\n // proxy, so the snippet showed SDK internals where the reader\n // expected their own test. The assertion we recorded knows the real\n // line; prefer it whenever Playwright's location left the spec file.\n const pwFile = firstError.location?.file ?? null;\n const assertionLoc = extracted.apiAssertions.find(\n (a) => a.status === 'failed',\n )?.location ?? null;\n const useAssertionLoc =\n assertionLoc !== null &&\n (pwFile === null || resolve(pwFile) !== resolve(test.location.file));\n\n const errorFile = useAssertionLoc ? assertionLoc.file : pwFile;\n const errorLine = useAssertionLoc\n ? assertionLoc.line\n : firstError.location?.line ?? null;\n\n let codeSnippet: string | null = null;\n if (this.config.includeCodeSnippets && errorLine !== null && errorFile) {\n codeSnippet = extractCodeSnippet(\n errorFile,\n errorLine,\n this.config.codeContextLines,\n );\n if (codeSnippet) codeSnippet = redact(codeSnippet);\n }\n\n failure = {\n // Playwright formats these for a terminal; the report is not one.\n message: redact(stripAnsi(firstError.message ?? 'Unknown error')),\n line: errorLine,\n code: codeSnippet,\n stack: this.config.includeStackTrace\n ? firstError.stack\n ? redact(stripAnsi(firstError.stack))\n : null\n : null,\n };\n }\n }\n\n const titlePath = test.titlePath().filter(Boolean);\n // Always POSIX separators. `relative` yields backslashes on Windows,\n // and this value is the test's identity in the report, the index, the\n // grid's group header and the cloud payload — so the same suite run on\n // Windows and on Linux produced two different filePaths for one test and\n // the cloud could not correlate them. (getTestType already had to strip\n // backslashes defensively, which was the tell.)\n const specFile = relative(this.rootDir || '.', test.location.file).replace(/\\\\/g, '/');\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 baseTestType = getTestType(tags, filePath, test);\n // Promote to an API test when the test exercised the API request fixture\n // (apiCallsCount > 0), even if it wasn't tagged @api or placed under an\n // api/ path — so a run of untagged API tests still opens the API\n // workspace. Never override a mobile classification.\n const testType =\n baseTestType !== 'mobile' && extracted.apiCallsCount > 0 ? 'api' : baseTestType;\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 // `testId` is required here, not cosmetic: the leaf title alone is not\n // unique across spec files, describe blocks or Playwright projects, and\n // colliding tests would otherwise share one artifact folder and\n // overwrite each other's video/screenshot.\n artifacts = copyArtifacts(lastResult.attachments, lastTitle, lastResult.retry, outputDir, this.runTimestamp || undefined, testId);\n }\n\n // Visual baseline comparisons. Playwright leaves these as attachments\n // suffixed `-expected` / `-actual` / `-diff`, which the artifact copier\n // above ignores because it matches three exact names. They are grouped\n // back into per-snapshot records here and copied under the same test\n // folder, so one attempt remains one directory.\n let visual: VisualComparison[] | null = null;\n if (this.config.includeArtifacts && status !== 'skipped' && lastResult.attachments) {\n try {\n const candidates = parseVisualAttachments(lastResult.attachments, {\n updatingSnapshots: this.updatingSnapshots,\n failureMessage: lastResult.errors\n .map((e) => e.message ?? '')\n .join('\\n'),\n });\n if (candidates.length > 0) {\n const outputDir = dirname(this.config.outputPath);\n const lastTitle = titlePath[titlePath.length - 1] ?? test.title;\n visual = copyVisualArtifacts(\n candidates,\n lastTitle,\n lastResult.retry,\n outputDir,\n this.runTimestamp || undefined,\n testId,\n );\n }\n } catch {\n // FR-026: a visual capture problem must never fail the test run.\n }\n }\n\n // Collect action steps from Playwright's step tree\n const actions = this.config.includeActionSteps && lastResult.steps\n ? collectActionSteps(lastResult.steps, lastResult.startTime)\n : null;\n\n const browser = getBrowserDisplayName(test);\n const collectedTest: CollectedTest = {\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: extracted.navigations,\n testId,\n filePath,\n suiteName,\n testType,\n isFlaky,\n retryStatus,\n expectedStatus,\n actualStatus,\n artifacts,\n visual,\n apiAssertions: extracted.apiAssertions,\n actions,\n // File-based heavy data references\n networkRequestsFile: extracted.networkRequestsFile,\n networkRequestsCount: extracted.networkRequestsCount,\n consoleLogsFile: extracted.consoleLogsFile,\n consoleLogsCount: extracted.consoleLogsCount,\n apiCallsFile: extracted.apiCallsFile,\n apiCallsCount: extracted.apiCallsCount,\n apiStatusMix: extracted.apiStatusMix,\n // Environment metadata\n browser,\n os: this.detectedOs || undefined,\n source: this.detectedSource || undefined,\n };\n\n // Read JSONL artifact files now — before the streaming writer moves them\n // and before heavy fields are nulled. These are included in the batch\n // payload as a reliable fallback for the fire-and-forget realtime upload.\n const batchConsoleLogs = extracted.consoleLogsFile && extracted.consoleLogsCount > 0\n ? (readJsonlSync(extracted.consoleLogsFile) as Record<string, unknown>[])\n : undefined;\n const batchNetworkRequests = extracted.networkRequestsFile && extracted.networkRequestsCount > 0\n ? (readJsonlSync(extracted.networkRequestsFile) as Record<string, unknown>[])\n : undefined;\n // API request/response + assertions for the batch fallback. Without these\n // the suite-end batch re-ingest overwrites the realtime apiReport with one\n // that has no calls/assertions, blanking the API-workspace inspector.\n const batchApiCallsRaw = extracted.apiCallsFile && extracted.apiCallsCount > 0\n ? (readJsonlSync(extracted.apiCallsFile) as Record<string, unknown>[])\n : undefined;\n const batchApiCalls = batchApiCallsRaw ? toApiReportCalls(batchApiCallsRaw) : undefined;\n // Pure-API tests have no browser networkRequestsFile — map apiCalls so the\n // batch fallback still populates network.json.\n const batchNetworkFromApi = !batchNetworkRequests && batchApiCallsRaw\n ? toNetworkRequestsFromApiCalls(batchApiCallsRaw)\n : undefined;\n const batchProtocolAssertions = extracted.apiAssertions && extracted.apiAssertions.length > 0\n ? toProtocolAssertions(extracted.apiAssertions as unknown as Record<string, unknown>[])\n : undefined;\n\n // Track cloud test data for the batch upload (used in batchUpload at suite end).\n // Actions, consoleLogs, and networkRequests are included so the batch acts as\n // a guaranteed fallback if the per-test fire-and-forget realtime upload fails.\n this.cloudTestsBuffer.push({\n testId,\n title,\n status,\n duration: lastResult.duration,\n suiteName,\n filePath,\n testType,\n isFlaky,\n startedAt,\n completedAt,\n tags,\n failure,\n retry: lastResult.retry,\n retryCount: test.results.length - 1,\n retryStatus,\n browser,\n os: this.detectedOs || undefined,\n source: this.detectedSource || undefined,\n ...(actions && actions.length > 0 ? { actions: actions as unknown as Record<string, unknown>[] } : {}),\n // Navigation entries — carried in the batch payload so Test Navigation\n // data reaches the server under the default 'batch' upload strategy (the\n // realtime per-test path that also carries navigations never runs there).\n ...(extracted.navigations && extracted.navigations.length > 0\n ? { navigations: extracted.navigations as unknown as Record<string, unknown>[] }\n : {}),\n ...(batchConsoleLogs ? { consoleLogs: batchConsoleLogs } : {}),\n ...(batchNetworkRequests\n ? { networkRequests: batchNetworkRequests }\n : batchNetworkFromApi\n ? { networkRequests: batchNetworkFromApi }\n : {}),\n ...(batchApiCalls ? { apiCalls: batchApiCalls } : {}),\n ...(batchProtocolAssertions ? { protocolAssertions: batchProtocolAssertions } : {}),\n // The comparisons themselves, not just the images. `visual` is already\n // in scope here and was simply never put on the payload, so the cloud\n // received the baseline/actual/diff PNGs as artifacts with nothing\n // saying which snapshot they belonged to or whether it passed.\n ...(visual && visual.length > 0\n ? { visual: visual as unknown as Record<string, unknown>[] }\n : {}),\n });\n\n // Realtime cloud upload — must happen BEFORE streaming writer moves JSONL\n // files and BEFORE heavy fields are nulled for memory release.\n try {\n const uploadStrategy = this.config.cloud?.uploadStrategy;\n if (this.cloudClient?.isCloudMode() && this.cloudRunId && (uploadStrategy === 'realtime' || uploadStrategy === 'both')) {\n const token = this.cloudClient.getAccessToken();\n if (token) {\n uploadTestResult(\n this.cloudClient.getEndpoint(),\n token,\n this.cloudRunId,\n this.buildCloudTestPayload(collectedTest),\n );\n }\n }\n } catch {\n // Realtime upload errors never crash tests\n }\n\n // Streaming mode: write detail to disk, keep only index in memory\n if (this.activeReportMode === 'streaming' && this.streamingWriter) {\n const streamId = generateStreamingTestId(filePath, titlePath, test.id ?? '', lastResult.retry);\n\n // Determine if this is a non-final retry\n const isFinalAttempt = lastResult.retry === test.results.length - 1;\n\n // Build metadata (small data kept inline)\n const detailData: TestDetailData = {\n id: streamId,\n navigations: extracted.navigations,\n apiAssertions: extracted.apiAssertions,\n actions: actions ?? [],\n artifacts,\n visual,\n failureDiagnostic: failure,\n hasNetworkFile: extracted.networkRequestsFile !== null,\n networkRequestsCount: extracted.networkRequestsCount,\n hasConsoleFile: extracted.consoleLogsFile !== null,\n consoleLogsCount: extracted.consoleLogsCount,\n hasApiCallsFile: extracted.apiCallsFile !== null,\n apiCallsCount: extracted.apiCallsCount,\n startedAt,\n };\n\n // Write metadata + move JSONL files to report directory (never loaded into memory)\n this.streamingWriter.writeTestDetail(streamId, detailData, {\n networkRequestsFile: extracted.networkRequestsFile,\n consoleLogsFile: extracted.consoleLogsFile,\n apiCallsFile: extracted.apiCallsFile,\n });\n\n // Build compact index entry\n const indexEntry: TestIndexEntry = {\n id: streamId,\n title: titlePath[titlePath.length - 1] ?? test.title,\n titlePath,\n filePath,\n status,\n duration: lastResult.duration,\n project: titlePath[1] ?? '',\n retryIndex: lastResult.retry,\n tags,\n hasNetworkData: extracted.networkRequestsCount > 0,\n hasConsoleData: extracted.consoleLogsCount > 0,\n hasArtifacts: artifacts !== null,\n hasActionSteps: (actions?.length ?? 0) > 0,\n visualCount: visual?.length ?? 0,\n visualFailedCount: visual?.filter((v) => v.status === 'failed').length ?? 0,\n domChangeCount: visual?.reduce((sum, v) => sum + (v.domChangeCount ?? 0), 0) ?? 0,\n // The first diff image, so the run overview can SHOW a visual\n // failure instead of only describing it.\n ...(() => {\n const withDiff = visual?.find((v) => v.diff);\n return withDiff?.diff ? { visualDiffPath: withDiff.diff } : {};\n })(),\n errorMessage: errorHeadline(failure?.message),\n networkCount: extracted.networkRequestsCount,\n consoleCount: extracted.consoleLogsCount,\n actionCount: actions?.length ?? 0,\n // The grid badges and filters on these, and it must never open a\n // test's detail file to draw a row.\n testType,\n apiCallCount: extracted.apiCallsCount,\n ...(extracted.apiStatusMix ? { apiStatusMix: extracted.apiStatusMix } : {}),\n assertionsPassed: countAssertions(extracted.apiAssertions, 'passed'),\n assertionsFailed: countAssertions(extracted.apiAssertions, 'failed'),\n ...(() => {\n const fa = firstFailedAssertion(extracted.apiAssertions);\n return fa ? { firstFailedAssertion: fa } : {};\n })(),\n isRetry: !isFinalAttempt,\n retryGroupKey: generateRetryGroupKey(filePath, titlePath, test.id ?? ''),\n };\n this.testIndex.push(indexEntry);\n\n // Update running counters (only count final attempts)\n if (isFinalAttempt) {\n this.summaryCounters.total++;\n switch (status) {\n case 'passed': this.summaryCounters.passed++; break;\n case 'failed': this.summaryCounters.failed++; break;\n case 'flaky': this.summaryCounters.flaky++; break;\n case 'skipped': this.summaryCounters.skipped++; break;\n case 'timedout': this.summaryCounters.timedOut++; break;\n default: this.summaryCounters.interrupted++; break;\n }\n }\n // Record this attempt's run-level aggregate contribution, keyed per test so\n // ONLY the final (highest-retry) attempt is folded at finalize — retries must\n // not inflate the volume counters or the API/assertion/navigation/action\n // breakdowns (the volume totals are derived from this map at finalize, in\n // lockstep with the status counts). `batchApiCallsRaw` was already read from\n // the JSONL for the cloud batch buffer, so there is no extra I/O here.\n this.upsertAggContribution(\n `${filePath}::${titlePath.join(' > ')}`,\n buildAggContribution(\n lastResult.retry,\n batchApiCallsRaw,\n extracted.apiAssertions,\n extracted.navigations,\n extracted.networkRequestsCount,\n extracted.consoleLogsCount,\n actions,\n ),\n );\n\n // Preserve artifact paths for cloud upload before releasing heavy data\n if (artifacts || visual) {\n this.pendingArtifactEntries.push({\n testId,\n artifacts: artifacts ?? {},\n visual,\n outputDir: dirname(this.config.outputPath),\n });\n }\n\n // Release references — heavy data is already on disk\n collectedTest.actions = null;\n collectedTest.apiAssertions = null;\n collectedTest.navigations = [];\n collectedTest.artifacts = null;\n collectedTest.failure = null;\n collectedTest.networkRequestsFile = null;\n collectedTest.consoleLogsFile = null;\n collectedTest.apiCallsFile = null;\n }\n\n // Only accumulate full test data in embedded mode.\n // In streaming mode, data is on disk — no need to hold it in memory.\n if (this.activeReportMode !== 'streaming') {\n this.collectedTests.push(collectedTest);\n }\n this.testCount++;\n\n // Memory pressure check\n this.checkMemoryPressure();\n } catch {\n // FR-026: never crash the test run\n }\n }\n\n async onEnd(_result: PwFullResult): Promise<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 // Warn if no test produced fixture data\n const hasNonSkippedTests = this.activeReportMode === 'streaming'\n ? this.testIndex.some((t) => t.status !== 'skipped')\n : this.collectedTests.some((t) => t.status !== 'skipped');\n if (!this.fixtureDataReceived && hasNonSkippedTests) {\n process.stderr.write(\n '\\n⚠ TestRelic: No fixture data received from any test.\\n' +\n ' To enable network logs, video sync, and Test Navigation tracking, import the TestRelic fixture:\\n' +\n \" import { test, expect } from '@testrelic/playwright-analytics/fixture';\\n\" +\n \" instead of:\\n\" +\n \" import { test, expect } from '@playwright/test';\\n\\n\",\n );\n }\n\n // Test Navigation diagnostics (one notice per run). The fixture-absence case\n // is already covered above; here we distinguish \"disabled by config\" from\n // \"fixture active but nothing navigated\".\n if (!this.config.captureNavigation) {\n process.stderr.write(\n '[testrelic] Test Navigation capture is disabled (captureNavigation: false). ' +\n 'Remove the option or set it to true to capture the navigation graph.\\n',\n );\n } else if (this.fixtureDataReceived && !this.navigationsCaptured && hasNonSkippedTests) {\n process.stderr.write(\n '[testrelic] Test Navigation: 0 navigation events captured this run. ' +\n 'Navigation is recorded automatically on page.goto()/clicks; if you expected a ' +\n 'navigation graph, ensure your tests drive page navigations (and use the ' +\n \"'@testrelic/playwright-analytics/fixture' import).\\n\",\n );\n }\n\n // Wait for all async artifact copies to complete before building manifest.\n // Bounded: a stuck copy (network drive, locked file) must not hold the run open.\n await withTimeout(\n () => flushPendingArtifactCopies(),\n REPORT_FINALIZE_TIMEOUT_MS,\n 'Artifact flush',\n );\n\n // Build artifact manifest before HTML report so it can be embedded\n let artifactManifest: import('@testrelic/core').ArtifactRunManifest | null = null;\n if (this.config.includeArtifacts && this.runTimestamp) {\n try {\n const outputDir = dirname(this.config.outputPath);\n artifactManifest = buildArtifactManifest(outputDir, this.runTimestamp);\n\n const manifestPath = join(outputDir, 'artifact-manifest.json');\n const manifestJson = JSON.stringify(artifactManifest);\n const tmpPath = manifestPath + '.tmp';\n writeFileSync(tmpPath, manifestJson, 'utf-8');\n renameSync(tmpPath, manifestPath);\n } catch {\n // FR-026: manifest failure never crashes the test run\n }\n }\n\n if (this.activeReportMode === 'streaming' && this.streamingWriter) {\n // --- Streaming mode: finalize on-disk report ---\n await withTimeout(\n () => this.finalizeStreamingReport(completedAt, totalDuration, _result, artifactManifest),\n CLOUD_FINALIZE_TIMEOUT_MS,\n 'Report finalization',\n );\n } else {\n // --- Embedded mode: original behavior ---\n const timeline = this.buildTimeline();\n // Deduplicate by testId, keeping only the last attempt (highest retry index)\n const lastByTestId = new Map<string, CollectedTest>();\n for (const t of this.collectedTests) {\n const existing = lastByTestId.get(t.testId);\n if (!existing || t.retry > existing.retry) lastByTestId.set(t.testId, t);\n }\n const finalAttempts = Array.from(lastByTestId.values());\n const summary = buildEnrichedSummary(finalAttempts, 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 await withTimeout(\n () => writeHtmlReport(report, this.config, artifactManifest),\n REPORT_FINALIZE_TIMEOUT_MS,\n 'HTML report write',\n );\n printConsoleSummary(summary, this.config.outputPath, this.config.htmlReportPath, this.config.quiet);\n\n // Cloud upload — bounded, so a slow or black-holed ingest endpoint cannot\n // hold `playwright test` open after every test has already finished.\n if (this.cloudClient) {\n const artifactEntries = this.collectArtifactEntries();\n const cloudTests = this.cloudTestsBuffer.slice();\n this.cloudTestsBuffer.length = 0;\n const outcome = await withTimeout(\n () => finalizeAndUpload(\n this.cloudClient as CloudClient,\n this.config.cloud,\n this.config.cloud?.uploadStrategy,\n this.testRunId,\n this.cloudRunId,\n report,\n completedAt,\n totalDuration,\n summary,\n artifactEntries,\n cloudTests,\n ),\n CLOUD_FINALIZE_TIMEOUT_MS,\n 'Cloud upload',\n );\n // Embedded mode writes no summary.json, so this receipt is not persisted —\n // it is still folded so the reporter's own state stays truthful and the\n // verdict is available to anything reading it in-process.\n this.cloudReceipt = applyFinalizeOutcome(this.cloudReceipt, outcome, this.testRunId);\n }\n }\n } catch {\n // FR-026: never crash the test run\n } finally {\n // Teardown must happen on EVERY path — including when a cloud phase above\n // timed out and therefore never reached its own `dispose()`. `dispose()`\n // clears the health-check interval; leaving it armed would keep Node's\n // event loop alive and stop `playwright test` from exiting after a run\n // that otherwise completed fine.\n await this.disposeCloudClient();\n }\n }\n\n /** Best-effort, bounded cloud-client teardown. Never throws, always settles. */\n private async disposeCloudClient(): Promise<void> {\n const client = this.cloudClient;\n if (!client) return;\n this.cloudClient = null;\n await withTimeout(() => client.dispose(), CLOUD_DISPOSE_TIMEOUT_MS, 'Cloud client shutdown');\n }\n\n /**\n * Collect artifact entries from all collected tests for cloud upload.\n * In streaming mode, uses the separately accumulated pendingArtifactEntries\n * since collectedTests artifacts are nulled after writing to disk.\n */\n private collectArtifactEntries(): ArtifactEntry[] {\n if (this.activeReportMode === 'streaming') {\n return this.pendingArtifactEntries;\n }\n const outputDir = dirname(this.config.outputPath);\n const entries: ArtifactEntry[] = [];\n for (const t of this.collectedTests) {\n if (t.artifacts || t.visual) {\n entries.push({\n testId: t.testId,\n artifacts: t.artifacts ?? {},\n visual: t.visual,\n outputDir,\n });\n }\n }\n return entries;\n }\n\n private async finalizeStreamingReport(\n completedAt: string,\n totalDuration: number,\n pwResult: PwFullResult,\n artifactManifest: import('@testrelic/core').ArtifactRunManifest | null,\n ): Promise<void> {\n if (!this.streamingWriter) return;\n\n // Reset ALL counters. Both status and volume are re-derived here from the FINAL\n // attempt of each test — status from the index dedup below, volume from the\n // streamingTestAgg fold — so retries inflate neither (the per-attempt increments\n // were removed; this map/index is the single source of truth).\n this.summaryCounters = {\n total: 0, passed: 0, failed: 0, flaky: 0, skipped: 0, timedOut: 0, interrupted: 0,\n totalApiCalls: 0, totalAssertions: 0, totalNavigations: 0,\n totalNetworkRequests: 0, totalConsoleLogs: 0, totalActionSteps: 0,\n visualChecks: 0, visualFailures: 0, visualDomChanges: 0,\n };\n\n const bestByTestKey = new Map<string, number>();\n const fileMap = new Map<string, StreamingFileStats & { total: number; passed: number; failed: number; flaky: number; skipped: number; timedOut: number }>();\n\n // Pass 1 of 1: find best retry, mark entries, count statuses, build file stats\n for (const entry of this.testIndex) {\n const testKey = `${entry.filePath}::${entry.titlePath.join(' > ')}`;\n const existing = bestByTestKey.get(testKey);\n if (existing === undefined || entry.retryIndex > existing) {\n bestByTestKey.set(testKey, entry.retryIndex);\n }\n }\n let indexFinalCount = 0;\n for (const entry of this.testIndex) {\n const testKey = `${entry.filePath}::${entry.titlePath.join(' > ')}`;\n const bestRetry = bestByTestKey.get(testKey)!;\n (entry as { isRetry: boolean }).isRetry = entry.retryIndex < bestRetry;\n if (entry.isRetry) continue;\n\n indexFinalCount++;\n this.summaryCounters.total++;\n switch (entry.status) {\n case 'passed': this.summaryCounters.passed++; break;\n case 'failed': this.summaryCounters.failed++; break;\n case 'flaky': this.summaryCounters.flaky++; break;\n case 'skipped': this.summaryCounters.skipped++; break;\n case 'timedout': this.summaryCounters.timedOut++; break;\n default: this.summaryCounters.interrupted++; break;\n }\n\n // Visual totals ride the same dedup: counted per final attempt only.\n this.summaryCounters.visualChecks += entry.visualCount ?? 0;\n this.summaryCounters.visualFailures += entry.visualFailedCount ?? 0;\n this.summaryCounters.visualDomChanges += entry.domChangeCount ?? 0;\n\n // Build file stats inline\n let stats = fileMap.get(entry.filePath);\n if (!stats) {\n stats = { filePath: entry.filePath, total: 0, passed: 0, failed: 0, flaky: 0, skipped: 0, timedOut: 0 };\n fileMap.set(entry.filePath, stats);\n }\n stats.total++;\n switch (entry.status) {\n case 'passed': stats.passed++; break;\n case 'failed': stats.failed++; break;\n case 'flaky': stats.flaky++; break;\n case 'skipped': stats.skipped++; break;\n case 'timedout': stats.timedOut++; break;\n }\n }\n const files: StreamingFileStats[] = Array.from(fileMap.values());\n\n // Fold the per-test aggregate contributions into the run-level breakdowns + volume\n // totals. streamingTestAgg already holds ONLY the highest-retry attempt per test,\n // so this dedups retries exactly like the status counts above. Per-test URLs are\n // already deduped and response-times capped (buildAggContribution); the fold caps\n // again so the run-level accumulators stay O(cap) regardless of run size.\n const folded = emptyStreamingAgg();\n for (const c of this.streamingTestAgg.values()) {\n folded.volApiCalls += c.apiCalls;\n folded.volAssertions += c.assertions;\n folded.volNavigations += c.navigations;\n folded.volNetworkRequests += c.networkRequests;\n folded.volConsoleLogs += c.consoleLogs;\n folded.volActionSteps += c.actionSteps;\n for (const m in c.methodCounts) folded.methodCounts[m] = (folded.methodCounts[m] ?? 0) + c.methodCounts[m];\n folded.statusRanges['2xx'] += c.statusRanges['2xx'];\n folded.statusRanges['3xx'] += c.statusRanges['3xx'];\n folded.statusRanges['4xx'] += c.statusRanges['4xx'];\n folded.statusRanges['5xx'] += c.statusRanges['5xx'];\n folded.statusRanges.error += c.statusRanges.error;\n folded.passedAssertions += c.passedAssertions;\n folded.failedAssertions += c.failedAssertions;\n for (const cat in c.actionCategories) folded.actionCategoryCounts[cat] = (folded.actionCategoryCounts[cat] ?? 0) + c.actionCategories[cat];\n for (const u of c.apiUrls) { if (folded.apiUrlSet.size >= MAX_AGG_UNIQUE_URLS) break; folded.apiUrlSet.add(u); }\n for (const u of c.navUrls) { if (folded.navUrlSet.size >= MAX_AGG_UNIQUE_URLS) break; folded.navUrlSet.add(u); }\n for (const rt of c.responseTimes) { if (folded.responseTimes.length >= MAX_AGG_RESPONSE_SAMPLES) break; folded.responseTimes.push(rt); }\n }\n this.summaryCounters.totalApiCalls = folded.volApiCalls;\n this.summaryCounters.totalAssertions = folded.volAssertions;\n this.summaryCounters.totalNavigations = folded.volNavigations;\n this.summaryCounters.totalNetworkRequests = folded.volNetworkRequests;\n this.summaryCounters.totalConsoleLogs = folded.volConsoleLogs;\n this.summaryCounters.totalActionSteps = folded.volActionSteps;\n\n // Write test index (full for server API, compact for HTML embedding)\n this.streamingWriter.writeIndex(this.testIndex);\n this.streamingWriter.writeCompactIndex(this.testIndex);\n\n // Cross-validate summary counters against index and Playwright\n const validation = {\n reporterTotal: this.summaryCounters.total,\n indexTotal: indexFinalCount,\n playwrightStatus: pwResult.status,\n isValid: this.summaryCounters.total === indexFinalCount,\n };\n if (!validation.isValid) {\n process.stderr.write(\n `[testrelic] WARNING: Summary count mismatch — reporter: ${validation.reporterTotal}, index: ${validation.indexTotal}\\n`,\n );\n }\n\n // Build enriched summary from the folded per-test aggregates (final attempts only).\n const enrichedSummary: Summary = {\n total: this.summaryCounters.total,\n passed: this.summaryCounters.passed,\n failed: this.summaryCounters.failed,\n flaky: this.summaryCounters.flaky,\n skipped: this.summaryCounters.skipped,\n timedout: this.summaryCounters.timedOut,\n totalApiCalls: this.summaryCounters.totalApiCalls,\n uniqueApiUrls: folded.apiUrlSet.size,\n apiCallsByMethod: { ...folded.methodCounts },\n apiCallsByStatusRange: {\n '2xx': folded.statusRanges['2xx'],\n '3xx': folded.statusRanges['3xx'],\n '4xx': folded.statusRanges['4xx'],\n '5xx': folded.statusRanges['5xx'],\n error: folded.statusRanges.error,\n },\n apiResponseTime: buildAggResponseTime(folded.responseTimes),\n totalAssertions: this.summaryCounters.totalAssertions,\n passedAssertions: folded.passedAssertions,\n failedAssertions: folded.failedAssertions,\n totalNavigations: this.summaryCounters.totalNavigations,\n uniqueNavigationUrls: folded.navUrlSet.size,\n // Final attempts only — retry index rows are excluded (they were counted before).\n totalTimelineSteps: indexFinalCount,\n totalActionSteps: this.summaryCounters.totalActionSteps,\n actionStepsByCategory: { ...folded.actionCategoryCounts },\n // The HTML report is built from THIS object, not from the streaming\n // summary written to disk — so the visual totals have to be on both or\n // the run overview shows counters the JSON already knows.\n visualChecks: this.summaryCounters.visualChecks,\n visualFailures: this.summaryCounters.visualFailures,\n visualDomChanges: this.summaryCounters.visualDomChanges,\n };\n\n // Write streaming summary.\n //\n // `testRunId` is a locally generated UUID — it is written on every run, offline\n // ones included, so its presence is NOT evidence the run reached the platform.\n // `cloudUpload` is that evidence. Written here with the pre-upload verdict and\n // rewritten below once the upload settles, so a process killed mid-upload leaves\n // the conservative verdict on disk instead of a claim it cannot back up.\n const summary: SummaryWithReceipt = {\n testRunId: this.testRunId,\n startedAt: this.startedAt,\n completedAt,\n totalDuration,\n ...this.summaryCounters,\n metadata: this.config.metadata,\n reportMode: 'streaming',\n files,\n enrichedSummary,\n validation,\n writeErrors: this.streamingWriter.getWriteErrors(),\n cloudUpload: this.cloudReceipt,\n };\n this.streamingWriter.writeSummary(summary);\n\n // Write manifest\n const manifest: StreamingReportManifest = {\n schemaVersion: '2.0',\n generatedAt: new Date().toISOString(),\n reportMode: 'streaming',\n summaryFile: 'summary.json',\n indexFile: 'index.json',\n testsDir: 'tests',\n artifactsDir: 'artifacts',\n testCount: this.testIndex.length,\n totalSizeBytes: this.streamingWriter.computeTotalSize(),\n };\n this.streamingWriter.writeManifest(manifest);\n\n // Generate lightweight HTML report with embedded summary + index\n await writeHtmlReport(\n this.buildStreamingReport(summary, enrichedSummary, completedAt, totalDuration),\n this.config,\n artifactManifest,\n );\n\n // Print console summary\n printConsoleSummary(enrichedSummary, this.config.outputPath, this.config.htmlReportPath, this.config.quiet);\n\n const reportDir = this.streamingWriter.getReportDir();\n process.stderr.write(\n `[testrelic] Streaming report written to ${reportDir}\\n` +\n `[testrelic] ${this.testIndex.length} test detail files on disk\\n` +\n `[testrelic] View report: npx testrelic serve ${reportDir}\\n`,\n );\n\n if (this.streamingWriter.getWriteErrors().length > 0) {\n process.stderr.write(\n `[testrelic] WARNING: ${this.streamingWriter.getWriteErrors().length} write error(s) during streaming\\n`,\n );\n }\n\n // Cloud upload for streaming mode.\n //\n // The writer is disposed only after the summary rewrite below, so hold it in a\n // local and dispose in `finally` — the reorder must not be able to skip teardown.\n const writer = this.streamingWriter;\n try {\n if (this.cloudClient) {\n const report = this.buildStreamingReport(summary, enrichedSummary, completedAt, totalDuration);\n const artifactEntries = this.collectArtifactEntries();\n const cloudTests = this.cloudTestsBuffer.slice();\n this.cloudTestsBuffer.length = 0;\n const outcome = await finalizeAndUpload(\n this.cloudClient,\n this.config.cloud,\n this.config.cloud?.uploadStrategy,\n this.testRunId,\n this.cloudRunId,\n report,\n completedAt,\n totalDuration,\n enrichedSummary,\n artifactEntries,\n cloudTests,\n );\n\n // Rewrite the summary with what the platform actually did. `writeSummary` is\n // atomic (tmp + rename), so summary.json is never torn between the two writes.\n // The local report was already complete before this ran — a cloud failure here\n // still leaves a usable report, now with a verdict that says so.\n this.cloudReceipt = applyFinalizeOutcome(this.cloudReceipt, outcome, this.testRunId);\n const settledSummary: SummaryWithReceipt = { ...summary, cloudUpload: this.cloudReceipt };\n try {\n writer.writeSummary(settledSummary);\n } catch {\n // FR-026: a failed rewrite leaves the earlier, more conservative verdict.\n }\n }\n } finally {\n writer.dispose();\n }\n\n // Explicit cleanup: release all references so GC can reclaim memory.\n // Mirrors Playwright's InternalReporter pattern of nulling suite refs after onEnd.\n this.testIndex.length = 0;\n this.collectedTests.length = 0;\n this.pendingArtifactEntries.length = 0;\n this.cloudTestsBuffer.length = 0;\n this.streamingWriter = null;\n this.cloudClient = null;\n }\n\n /** Build a lightweight TestRunReport from streaming data (for HTML generation and cloud). */\n private buildStreamingReport(\n _streamingSummary: StreamingReportSummary,\n enrichedSummary: Summary,\n completedAt: string,\n totalDuration: number,\n ): TestRunReport {\n // In streaming mode, timeline is empty in the report — details are served by the server\n return {\n schemaVersion: SCHEMA_VERSION,\n testRunId: this.testRunId,\n startedAt: this.startedAt,\n completedAt,\n totalDuration,\n summary: enrichedSummary,\n ci: detectCI(),\n metadata: this.config.metadata,\n timeline: [],\n shardRunIds: null,\n };\n }\n\n printsToStdio(): boolean {\n return false;\n }\n\n /** Check heap usage and warn or auto-switch to streaming mode. */\n private checkMemoryPressure(): void {\n try {\n const usage = process.memoryUsage();\n const heapMB = usage.heapUsed / 1024 / 1024;\n // Only check pressure when heap is substantial (avoids false positives in tests)\n if (heapMB < 200) return;\n const heapRatio = usage.heapUsed / usage.heapTotal;\n if (heapRatio > 0.9 && this.activeReportMode === 'embedded') {\n process.stderr.write(\n `[testrelic] WARNING: High memory (${Math.round(heapMB)} MB). ` +\n `Auto-switching to streaming mode.\\n`,\n );\n this.switchToStreamingMode();\n } else if (heapRatio > 0.8 && heapMB >= 500 && !this.memoryPressureWarned) {\n // Require an absolute floor in addition to the ratio: heapTotal grows in\n // steps, so a modest heapUsed can transiently read as a high ratio right\n // after a growth step. Also fire at most once per run — this is advisory,\n // not the auto-switch trigger above, so it doesn't need to repeat per test.\n this.memoryPressureWarned = true;\n process.stderr.write(\n `[testrelic] WARNING: Memory pressure detected (${Math.round(heapMB)} MB used).\\n`,\n );\n }\n } catch {\n // Never crash due to memory check\n }\n }\n\n /** Switch from embedded to streaming mode mid-run. */\n private switchToStreamingMode(): void {\n try {\n if (this.activeReportMode === 'streaming') return;\n this.activeReportMode = 'streaming';\n const outputDir = dirname(this.config.outputPath);\n this.streamingWriter = new StreamingWriter(outputDir);\n\n // Flush any already-collected tests to disk and release from memory\n for (const ct of this.collectedTests) {\n const streamId = generateStreamingTestId(ct.filePath, ct.titlePath, ct.testId, ct.retry);\n // Read the apiCalls JSONL for the run-level breakdowns BEFORE writeTestDetail\n // (below) moves the file to the report dir — after that the path is gone.\n // Bounded by the pre-switch test count; we are already flushing each test here.\n const switchApiCallsRaw = ct.apiCallsFile && ct.apiCallsCount > 0\n ? (readJsonlSync(ct.apiCallsFile) as Record<string, unknown>[])\n : undefined;\n const detailData: TestDetailData = {\n id: streamId,\n navigations: ct.navigations ?? [],\n apiAssertions: ct.apiAssertions ?? [],\n actions: ct.actions ?? [],\n artifacts: ct.artifacts,\n failureDiagnostic: ct.failure,\n hasNetworkFile: ct.networkRequestsFile !== null,\n networkRequestsCount: ct.networkRequestsCount,\n hasConsoleFile: ct.consoleLogsFile !== null,\n consoleLogsCount: ct.consoleLogsCount,\n hasApiCallsFile: ct.apiCallsFile !== null,\n apiCallsCount: ct.apiCallsCount,\n startedAt: ct.startedAt,\n };\n this.streamingWriter.writeTestDetail(streamId, detailData, {\n networkRequestsFile: ct.networkRequestsFile,\n consoleLogsFile: ct.consoleLogsFile,\n apiCallsFile: ct.apiCallsFile,\n });\n\n const indexEntry: TestIndexEntry = {\n id: streamId,\n title: ct.titlePath[ct.titlePath.length - 1] ?? ct.title,\n titlePath: ct.titlePath,\n filePath: ct.filePath,\n status: ct.status,\n duration: ct.duration,\n project: ct.titlePath[1] ?? '',\n retryIndex: ct.retry,\n tags: ct.tags,\n hasNetworkData: ct.networkRequestsCount > 0,\n hasConsoleData: ct.consoleLogsCount > 0,\n hasArtifacts: ct.artifacts !== null,\n hasActionSteps: (ct.actions?.length ?? 0) > 0,\n errorMessage: ct.failure?.message?.split('\\n')[0] ?? null,\n networkCount: ct.networkRequestsCount,\n consoleCount: ct.consoleLogsCount,\n actionCount: ct.actions?.length ?? 0,\n testType: ct.testType,\n apiCallCount: ct.apiCallsCount,\n ...(ct.apiStatusMix\n ? { apiStatusMix: ct.apiStatusMix }\n : switchApiCallsRaw\n ? { apiStatusMix: statusMixOf(switchApiCallsRaw) }\n : {}),\n assertionsPassed: countAssertions(ct.apiAssertions, 'passed'),\n assertionsFailed: countAssertions(ct.apiAssertions, 'failed'),\n ...(() => {\n const fa = firstFailedAssertion(ct.apiAssertions);\n return fa ? { firstFailedAssertion: fa } : {};\n })(),\n isRetry: false,\n retryGroupKey: generateRetryGroupKey(ct.filePath, ct.titlePath, ct.testId),\n };\n this.testIndex.push(indexEntry);\n\n // Update running counters\n this.summaryCounters.total++;\n switch (ct.status) {\n case 'passed': this.summaryCounters.passed++; break;\n case 'failed': this.summaryCounters.failed++; break;\n case 'flaky': this.summaryCounters.flaky++; break;\n case 'skipped': this.summaryCounters.skipped++; break;\n case 'timedout': this.summaryCounters.timedOut++; break;\n default: this.summaryCounters.interrupted++; break;\n }\n // Feed the run-level aggregates for this pre-switch test so the streaming\n // summary's breakdowns stay in lockstep with its volume totals (both are\n // derived from streamingTestAgg at finalize). Without this, pre-switch tests\n // would contribute to the totals but not the method/status/assertion/category\n // breakdowns — totals and breakdowns would contradict each other.\n this.upsertAggContribution(\n `${ct.filePath}::${ct.titlePath.join(' > ')}`,\n buildAggContribution(\n ct.retry,\n switchApiCallsRaw,\n ct.apiAssertions ?? [],\n ct.navigations ?? [],\n ct.networkRequestsCount,\n ct.consoleLogsCount,\n ct.actions ?? null,\n ),\n );\n }\n\n // Release all collected tests from memory\n this.collectedTests.length = 0;\n } catch {\n // Never crash due to mode switch\n }\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: null,\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: null,\n actions: t.actions,\n consoleLogs: null,\n }));\n\n return buildUnifiedTimeline(testData, {\n navigationTypes: this.config.navigationTypes,\n });\n }\n\n\n private readJsonlFile<T>(filePath: string | null): T[] | null {\n if (!filePath) return null;\n const items = readJsonlSync(filePath);\n return items.length > 0 ? (items as T[]) : null;\n }\n\n private toTestResult(test: CollectedTest, options?: { loadFiles?: boolean }): TRTestResult {\n let networkRequests: CapturedNetworkRequest[] | null = null;\n let consoleLogs: CoreConsoleLogEntry[] | null = null;\n let apiCalls: ApiCallRecord[] | null = null;\n\n // For cloud upload, read the JSONL files so the data is sent to the backend\n if (options?.loadFiles) {\n networkRequests = this.readJsonlFile<CapturedNetworkRequest>(test.networkRequestsFile);\n consoleLogs = this.readJsonlFile<CoreConsoleLogEntry>(test.consoleLogsFile);\n apiCalls = this.readJsonlFile<ApiCallRecord>(test.apiCallsFile);\n }\n\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 visual: test.visual,\n networkRequests,\n apiCalls,\n apiAssertions: test.apiAssertions,\n actions: test.actions ?? null,\n consoleLogs,\n };\n }\n\n /**\n * Build a full payload for cloud upload that includes console logs,\n * network requests, navigations, and actions read from JSONL files.\n * Called before streaming writer moves files and before fields are nulled.\n */\n private buildCloudTestPayload(test: CollectedTest): Record<string, unknown> {\n const payload: Record<string, unknown> = {\n title: test.title,\n status: test.status,\n duration: test.duration,\n startedAt: test.startedAt,\n completedAt: test.completedAt,\n retry: test.retry,\n retryCount: test.retryCount,\n retryStatus: test.retryStatus,\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 expectedStatus: test.expectedStatus,\n actualStatus: test.actualStatus,\n artifacts: test.artifacts,\n visual: test.visual ?? [],\n actions: test.actions ?? [],\n navigations: test.navigations ?? [],\n steps: test.actions ?? [],\n browser: test.browser,\n os: test.os,\n source: test.source,\n };\n\n // Captured API assertions → canonical `protocolAssertions` shape the\n // inspector renders (the server persists `protocolAssertions`, not the raw\n // `apiAssertions` capture field — see toProtocolAssertions).\n if (test.apiAssertions && test.apiAssertions.length > 0) {\n payload.protocolAssertions = toProtocolAssertions(\n test.apiAssertions as unknown as Record<string, unknown>[],\n );\n }\n\n if (test.consoleLogsFile) {\n payload.consoleLogs = readJsonlSync(test.consoleLogsFile);\n }\n if (test.networkRequestsFile) {\n payload.networkRequests = readJsonlSync(test.networkRequestsFile);\n }\n\n // For API tests: apiCallsFile has request/response data captured by ApiRequestTracker.\n // Send `apiCalls` in the canonical `apiReport.apiCalls` shape (what the API\n // workspace inspector reads) and — if no browser network data — also map to\n // `networkRequests` so the backend stores it in network.json for the\n // generic Inspector UI.\n if (test.apiCallsFile) {\n const apiCalls = readJsonlSync(test.apiCallsFile) as Record<string, unknown>[];\n payload.apiCalls = toApiReportCalls(apiCalls);\n if (!payload.networkRequests || (payload.networkRequests as unknown[]).length === 0) {\n payload.networkRequests = toNetworkRequestsFromApiCalls(apiCalls);\n }\n }\n\n return payload;\n }\n\n private writeReport(report: TestRunReport): void {\n try {\n const json = JSON.stringify(report);\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 * StreamingWriter — writes test data to disk incrementally.\n *\n * v3 format: each test gets a directory with separate files:\n * tests/<testId>/meta.json — lightweight metadata\n * tests/<testId>/network.jsonl — network requests (moved from temp)\n * tests/<testId>/console.jsonl — console logs (moved from temp)\n * tests/<testId>/api-calls.jsonl — API calls (moved from temp)\n *\n * Heavy data files are moved (renamed) from the worker's temp directory,\n * never loaded into the reporter's memory.\n */\n\nimport { createHash } from 'node:crypto';\nimport { mkdirSync, writeFileSync, renameSync, unlinkSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type {\n TestIndexEntry,\n TestDetailData,\n StreamingReportSummary,\n StreamingReportManifest,\n StreamingWriteError,\n} from '@testrelic/core';\n\nconst REPORT_DIR_NAME = '.testrelic-report';\n\n/** Generate a deterministic test ID from test identity fields. */\nexport function generateStreamingTestId(\n filePath: string,\n titlePath: readonly string[],\n project: string,\n retryIndex: number,\n): string {\n const input = `${filePath}|${titlePath.join('|')}|${project}|${retryIndex}`;\n return createHash('sha256').update(input).digest('hex').substring(0, 12);\n}\n\n/**\n * Generate a key shared by every attempt of the same test, so retries can be\n * correlated with their final attempt without re-sending the full titlePath.\n * Same inputs as {@link generateStreamingTestId} minus `retryIndex`.\n */\nexport function generateRetryGroupKey(\n filePath: string,\n titlePath: readonly string[],\n project: string,\n): string {\n const input = `${filePath}|${titlePath.join('|')}|${project}`;\n return createHash('sha256').update(input).digest('hex').substring(0, 12);\n}\n\nexport class StreamingWriter {\n private readonly reportDir: string;\n private readonly testsDir: string;\n private readonly writeErrors: StreamingWriteError[] = [];\n private totalBytesWritten = 0;\n\n constructor(outputDir: string) {\n this.reportDir = join(outputDir, REPORT_DIR_NAME);\n this.testsDir = join(this.reportDir, 'tests');\n mkdirSync(this.testsDir, { recursive: true });\n }\n\n /** Get the report directory path. */\n getReportDir(): string {\n return this.reportDir;\n }\n\n /** Get accumulated write errors. */\n getWriteErrors(): readonly StreamingWriteError[] {\n return this.writeErrors;\n }\n\n /**\n * Write a single test's detail data to disk.\n *\n * Creates a per-test directory containing:\n * - meta.json: lightweight metadata (navigations, assertions, actions, artifacts, failure)\n * - network.jsonl: moved from temp (if provided)\n * - console.jsonl: moved from temp (if provided)\n * - api-calls.jsonl: moved from temp (if provided)\n *\n * @param testId — Deterministic test ID\n * @param data — Metadata (inline small data)\n * @param files — Paths to JSONL files to move into the test directory\n */\n writeTestDetail(\n testId: string,\n data: TestDetailData,\n files?: {\n networkRequestsFile?: string | null;\n consoleLogsFile?: string | null;\n apiCallsFile?: string | null;\n },\n ): boolean {\n try {\n const testDir = join(this.testsDir, testId);\n mkdirSync(testDir, { recursive: true });\n\n // Write metadata\n const metaPath = join(testDir, 'meta.json');\n const tmpPath = metaPath + '.tmp';\n const json = JSON.stringify(data);\n writeFileSync(tmpPath, json, 'utf-8');\n renameSync(tmpPath, metaPath);\n this.totalBytesWritten += Buffer.byteLength(json, 'utf-8');\n\n // Move JSONL data files from temp to test directory\n if (files?.networkRequestsFile) {\n this.moveFile(files.networkRequestsFile, join(testDir, 'network.jsonl'));\n }\n if (files?.consoleLogsFile) {\n this.moveFile(files.consoleLogsFile, join(testDir, 'console.jsonl'));\n }\n if (files?.apiCallsFile) {\n this.moveFile(files.apiCallsFile, join(testDir, 'api-calls.jsonl'));\n }\n\n return true;\n } catch (err) {\n const errorMsg = err instanceof Error ? err.message : String(err);\n this.writeErrors.push({\n testId,\n error: errorMsg,\n timestamp: new Date().toISOString(),\n });\n process.stderr.write(\n `[testrelic] Failed to write test detail for ${testId}: ${errorMsg}\\n`,\n );\n return false;\n }\n }\n\n /**\n * Move a file from source to destination.\n * Falls back to copy+delete if rename fails (cross-device move).\n */\n private moveFile(src: string, dest: string): void {\n try {\n renameSync(src, dest);\n } catch {\n // Cross-device: copy then delete\n try {\n const { copyFileSync } = require('node:fs') as typeof import('node:fs');\n copyFileSync(src, dest);\n try { unlinkSync(src); } catch { /* best effort cleanup */ }\n } catch (err) {\n process.stderr.write(\n `[testrelic] Failed to move file ${src} → ${dest}: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n }\n }\n }\n\n /** Write the test index to disk. */\n writeIndex(entries: readonly TestIndexEntry[]): void {\n const filePath = join(this.reportDir, 'index.json');\n const tmpPath = filePath + '.tmp';\n const json = JSON.stringify(entries);\n writeFileSync(tmpPath, json, 'utf-8');\n renameSync(tmpPath, filePath);\n this.totalBytesWritten += Buffer.byteLength(json, 'utf-8');\n }\n\n /**\n * Write a compact index for HTML embedding (strips titlePath/project to save ~100 bytes/entry).\n */\n writeCompactIndex(entries: readonly TestIndexEntry[]): void {\n const filePath = join(this.reportDir, 'index-compact.json');\n const tmpPath = filePath + '.tmp';\n const compact = entries.map(({ titlePath: _tp, project: _p, ...rest }) => rest);\n const json = JSON.stringify(compact);\n writeFileSync(tmpPath, json, 'utf-8');\n renameSync(tmpPath, filePath);\n this.totalBytesWritten += Buffer.byteLength(json, 'utf-8');\n }\n\n /** Write the summary to disk. */\n writeSummary(summary: StreamingReportSummary): void {\n const filePath = join(this.reportDir, 'summary.json');\n const tmpPath = filePath + '.tmp';\n const json = JSON.stringify(summary);\n writeFileSync(tmpPath, json, 'utf-8');\n renameSync(tmpPath, filePath);\n this.totalBytesWritten += Buffer.byteLength(json, 'utf-8');\n }\n\n /** Write the manifest to disk. */\n writeManifest(manifest: StreamingReportManifest): void {\n const filePath = join(this.reportDir, 'manifest.json');\n const tmpPath = filePath + '.tmp';\n const json = JSON.stringify(manifest);\n writeFileSync(tmpPath, json, 'utf-8');\n renameSync(tmpPath, filePath);\n this.totalBytesWritten += Buffer.byteLength(json, 'utf-8');\n }\n\n /** Get total bytes written (tracked incrementally — no filesystem scan). */\n computeTotalSize(): number {\n return this.totalBytesWritten;\n }\n\n /** Cleanup — no-op for now, but available for future resource release. */\n dispose(): void {\n // Reserved for future cleanup\n }\n}\n","/**\n * Cloud configuration discovery, parsing, and merging.\n *\n * Discovers `.testrelic/testrelic-config.json` config files, resolves environment variable\n * references, and merges configuration from multiple sources.\n *\n * Priority order (highest wins):\n * 1. Environment variables (TESTRELIC_API_KEY, TESTRELIC_CLOUD_ENDPOINT, etc.)\n * 2. Reporter options in playwright.config.ts\n * 3. .testrelic/testrelic-config.json config file\n * 4. Built-in defaults\n */\n\nimport { readFileSync, existsSync, statSync } from 'node:fs';\nimport { join, dirname, resolve } from 'node:path';\nimport type { CloudConfig, UploadStrategy, CloudReporterOptions } from '@testrelic/core';\nimport { isValidCloudConfig, isValidEndpointUrl } from '@testrelic/core';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst CONFIG_DIR = '.testrelic';\nconst CONFIG_FILENAME = 'testrelic-config.json';\nconst LEGACY_CONFIG_FILENAME = '.testrelic';\nconst MAX_WALK_UP_LEVELS = 5;\n\nconst DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\n\nconst DEFAULT_CLOUD_CONFIG: CloudConfig = Object.freeze({\n apiKey: null,\n endpoint: 'https://platform.testrelic.ai/api/v1',\n uploadStrategy: 'batch',\n timeout: 30000,\n uploadBudgetMs: 120000,\n projectName: null,\n queueMaxAge: 604800000, // 7 days\n queueDirectory: `${CONFIG_DIR}/queue`,\n uploadArtifacts: true,\n artifactMaxSizeMb: 50,\n});\n\n/** Duration string multipliers. */\nconst DURATION_UNITS: Record<string, number> = {\n s: 1000,\n m: 60 * 1000,\n h: 60 * 60 * 1000,\n d: 24 * 60 * 60 * 1000,\n};\n\n// ---------------------------------------------------------------------------\n// Config File Discovery\n// ---------------------------------------------------------------------------\n\n/**\n * Search for `.testrelic/testrelic-config.json` starting from `startDir`,\n * walking up the directory tree up to MAX_WALK_UP_LEVELS levels.\n * Falls back to the legacy flat `.testrelic` file (emits a deprecation warning).\n * Returns the absolute path if found, or null.\n */\nexport function discoverConfigFile(startDir: string): string | null {\n let currentDir = resolve(startDir);\n for (let i = 0; i <= MAX_WALK_UP_LEVELS; i++) {\n const candidate = join(currentDir, CONFIG_DIR, CONFIG_FILENAME);\n if (existsSync(candidate)) {\n try {\n if (statSync(candidate).isFile()) return candidate;\n } catch {\n // ignore stat errors, continue walking\n }\n }\n\n // Backward-compat: fall back to legacy flat `.testrelic` file\n const legacyCandidate = join(currentDir, LEGACY_CONFIG_FILENAME);\n if (existsSync(legacyCandidate)) {\n try {\n if (statSync(legacyCandidate).isFile()) {\n process.stderr.write(\n '[testrelic] Deprecation: config file \".testrelic\" has moved to \".testrelic/testrelic-config.json\". ' +\n 'Please migrate your config file to the new location.\\n',\n );\n return legacyCandidate;\n }\n } catch {\n // ignore stat errors\n }\n }\n\n const parentDir = dirname(currentDir);\n if (parentDir === currentDir) break; // filesystem root\n currentDir = parentDir;\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Config File Parsing\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a `.testrelic/testrelic-config.json` (or legacy `.testrelic`) JSON config file.\n * Applies prototype pollution prevention per Constitution Principle VIII.\n * Returns null if the file cannot be read or parsed.\n */\nexport function parseConfigFile(filePath: string): Record<string, unknown> | null {\n try {\n const content = readFileSync(filePath, 'utf-8');\n const parsed: unknown = JSON.parse(content);\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n return null;\n }\n // Check for prototype pollution at top level and nested objects\n if (!isSafeObject(parsed as Record<string, unknown>)) {\n return null;\n }\n return parsed as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\n/** Recursively check for dangerous keys. */\nfunction isSafeObject(obj: Record<string, unknown>): boolean {\n for (const key of Object.keys(obj)) {\n if (DANGEROUS_KEYS.has(key)) return false;\n const val = obj[key];\n if (typeof val === 'object' && val !== null && !Array.isArray(val)) {\n if (!isSafeObject(val as Record<string, unknown>)) return false;\n }\n }\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Environment Variable Resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve environment variable references in string values.\n * Supports `$VAR_NAME` and `${VAR_NAME}` syntax.\n * Returns the resolved string, or null if the env var is not defined.\n */\nexport function resolveEnvVar(value: string): string | null {\n // Match ${VAR_NAME} syntax\n const bracketMatch = /^\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}$/.exec(value);\n if (bracketMatch) {\n return process.env[bracketMatch[1]] ?? null;\n }\n // Match $VAR_NAME syntax (entire string must be the reference)\n const dollarMatch = /^\\$([A-Za-z_][A-Za-z0-9_]*)$/.exec(value);\n if (dollarMatch) {\n return process.env[dollarMatch[1]] ?? null;\n }\n return value;\n}\n\n/**\n * Resolve all string values starting with `$` in a config object.\n * Returns a new object with resolved values.\n */\nexport function resolveEnvVars(obj: Record<string, unknown>): Record<string, unknown> {\n const result = Object.create(null) as Record<string, unknown>;\n for (const key of Object.keys(obj)) {\n const val = obj[key];\n if (typeof val === 'string' && val.startsWith('$')) {\n result[key] = resolveEnvVar(val);\n } else if (typeof val === 'object' && val !== null && !Array.isArray(val)) {\n result[key] = resolveEnvVars(val as Record<string, unknown>);\n } else {\n result[key] = val;\n }\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Duration Parsing\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a duration string like \"7d\", \"24h\", \"30m\" into milliseconds.\n * Returns null if the string cannot be parsed.\n */\nexport function parseDuration(value: string): number | null {\n const match = /^(\\d+)\\s*([smhd])$/.exec(value.trim());\n if (!match) return null;\n const amount = parseInt(match[1], 10);\n const unit = match[2];\n const multiplier = DURATION_UNITS[unit];\n if (!multiplier || amount <= 0) return null;\n return amount * multiplier;\n}\n\n// ---------------------------------------------------------------------------\n// Config Merging\n// ---------------------------------------------------------------------------\n\n/**\n * Merge cloud configuration from multiple sources.\n *\n * Priority (highest wins):\n * 1. Environment variables\n * 2. Reporter options (from playwright.config.ts)\n * 3. Config file (.testrelic/testrelic-config.json)\n * 4. Built-in defaults\n */\nexport function mergeCloudConfig(\n fileConfig: Record<string, unknown> | null,\n reporterOptions: CloudReporterOptions | undefined,\n): CloudConfig {\n const target = Object.create(null) as Record<string, unknown>;\n\n // Start with defaults\n Object.assign(target, DEFAULT_CLOUD_CONFIG);\n\n // Layer 3: Config file values\n if (fileConfig) {\n const cloud = fileConfig.cloud as Record<string, unknown> | undefined;\n if (cloud && typeof cloud === 'object') {\n if (typeof cloud.endpoint === 'string') target.endpoint = cloud.endpoint;\n if (typeof cloud.upload === 'string') target.uploadStrategy = cloud.upload;\n if (typeof cloud.timeout === 'number') target.timeout = cloud.timeout;\n if (typeof cloud.uploadBudgetMs === 'number') target.uploadBudgetMs = cloud.uploadBudgetMs;\n // apiKey from config file (should be an env var reference, already resolved)\n if (typeof cloud.apiKey === 'string' && cloud.apiKey.length > 0) {\n target.apiKey = cloud.apiKey;\n }\n }\n // Support both new 'testrelic-repo' key and legacy 'project' key (deprecated)\n const repoSection = (fileConfig['testrelic-repo'] ?? fileConfig.project) as\n | Record<string, unknown>\n | undefined;\n if (repoSection && typeof repoSection === 'object') {\n if (typeof repoSection.name === 'string') target.projectName = repoSection.name;\n }\n if (fileConfig.project && !fileConfig['testrelic-repo']) {\n process.stderr.write(\n '[testrelic] Deprecation: \"project\" key in config is deprecated. Rename it to \"testrelic-repo\".\\n',\n );\n }\n const queue = fileConfig.queue as Record<string, unknown> | undefined;\n if (queue && typeof queue === 'object') {\n if (typeof queue.maxAge === 'string') {\n const ms = parseDuration(queue.maxAge as string);\n if (ms !== null) target.queueMaxAge = ms;\n }\n if (typeof queue.directory === 'string') target.queueDirectory = queue.directory;\n }\n }\n\n // Layer 2: Reporter options\n if (reporterOptions) {\n if (typeof reporterOptions.apiKey === 'string' && reporterOptions.apiKey.length > 0) {\n // Resolve env var reference if present\n const resolved = reporterOptions.apiKey.startsWith('$')\n ? resolveEnvVar(reporterOptions.apiKey)\n : reporterOptions.apiKey;\n if (resolved) target.apiKey = resolved;\n }\n if (typeof reporterOptions.endpoint === 'string') target.endpoint = reporterOptions.endpoint;\n if (typeof reporterOptions.upload === 'string') target.uploadStrategy = reporterOptions.upload;\n if (typeof reporterOptions.timeout === 'number') target.timeout = reporterOptions.timeout;\n if (typeof reporterOptions.uploadBudgetMs === 'number') target.uploadBudgetMs = reporterOptions.uploadBudgetMs;\n if (typeof reporterOptions.projectName === 'string') target.projectName = reporterOptions.projectName;\n if (typeof reporterOptions.queueMaxAge === 'string') {\n const ms = parseDuration(reporterOptions.queueMaxAge);\n if (ms !== null) target.queueMaxAge = ms;\n }\n if (typeof reporterOptions.queueDirectory === 'string') target.queueDirectory = reporterOptions.queueDirectory;\n if (typeof reporterOptions.uploadArtifacts === 'boolean') target.uploadArtifacts = reporterOptions.uploadArtifacts;\n if (typeof reporterOptions.artifactMaxSizeMb === 'number') target.artifactMaxSizeMb = reporterOptions.artifactMaxSizeMb;\n }\n\n // Layer 1: Environment variable overrides (highest priority)\n const envApiKey = process.env.TESTRELIC_API_KEY;\n if (envApiKey && envApiKey.length > 0) {\n target.apiKey = envApiKey;\n }\n\n const envEndpoint = process.env.TESTRELIC_CLOUD_ENDPOINT;\n if (envEndpoint && isValidEndpointUrl(envEndpoint)) {\n target.endpoint = envEndpoint;\n }\n\n const envUpload = process.env.TESTRELIC_UPLOAD_STRATEGY;\n if (envUpload && ['batch', 'realtime', 'both'].includes(envUpload)) {\n target.uploadStrategy = envUpload as UploadStrategy;\n }\n\n const envTimeout = process.env.TESTRELIC_CLOUD_TIMEOUT;\n if (envTimeout) {\n const parsed = parseInt(envTimeout, 10);\n if (!isNaN(parsed) && parsed >= 1000 && parsed <= 120000) {\n target.timeout = parsed;\n }\n }\n\n const config = Object.freeze(target) as unknown as CloudConfig;\n\n // Validate before returning — fall back to defaults on invalid config\n if (!isValidCloudConfig(config)) {\n return DEFAULT_CLOUD_CONFIG;\n }\n\n return config;\n}\n\nexport { DEFAULT_CLOUD_CONFIG };\n","/**\n * Config resolution with prototype pollution prevention.\n * Merges user options into defaults, freezes the result.\n */\n\nimport type { ReporterConfig, NavigationType, CloudConfig, ReportMode } from '@testrelic/core';\nimport { isValidConfig, createError, ErrorCode } from '@testrelic/core';\nimport { discoverConfigFile, parseConfigFile, resolveEnvVars, mergeCloudConfig } from './cloud-config.js';\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 /** Master on/off for Test Navigation capture. Default true. When false the\n * reporter drops navigation data before upload and logs a one-time notice. */\n readonly captureNavigation: 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 readonly includeActionSteps: boolean;\n readonly captureConsoleLogs: boolean;\n readonly cloud: CloudConfig | null;\n readonly reportMode: ReportMode;\n readonly streamingThreshold: number;\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.captureNavigation = options?.captureNavigation ?? 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 target.includeActionSteps = options?.includeActionSteps ?? true;\n target.captureConsoleLogs = options?.captureConsoleLogs ?? true;\n\n // Cloud config: merge from config file + reporter options + env vars\n const fileConfigPath = discoverConfigFile(process.cwd());\n const fileConfig = fileConfigPath ? parseConfigFile(fileConfigPath) : null;\n const resolvedFileConfig = fileConfig ? resolveEnvVars(fileConfig) : null;\n const cloudConfig = mergeCloudConfig(resolvedFileConfig, options?.cloud);\n target.cloud = cloudConfig.apiKey ? cloudConfig : null;\n target.reportMode = options?.reportMode ?? 'streaming';\n target.streamingThreshold = options?.streamingThreshold ?? 0;\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.7.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 runUrl: env.GITHUB_SERVER_URL && env.GITHUB_REPOSITORY && env.GITHUB_RUN_ID\n ? `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}`\n : 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 runUrl: env.CI_PIPELINE_URL ?? 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 runUrl: env.BUILD_URL ?? null,\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 runUrl: env.CIRCLE_BUILD_URL ?? null,\n };\n}\n\nfunction detectBitbucketPipelines(env: Record<string, string | undefined>): CIMetadata | null {\n if (!env.BITBUCKET_PIPELINE_UUID) return null;\n const runUrl = env.BITBUCKET_WORKSPACE && env.BITBUCKET_REPO_SLUG && env.BITBUCKET_PIPELINE_UUID\n ? `https://bitbucket.org/${env.BITBUCKET_WORKSPACE}/${env.BITBUCKET_REPO_SLUG}/pipelines/results/${env.BITBUCKET_PIPELINE_UUID}`\n : null;\n return {\n provider: 'bitbucket-pipelines',\n buildId: env.BITBUCKET_BUILD_NUMBER ?? null,\n commitSha: env.BITBUCKET_COMMIT ?? null,\n branch: env.BITBUCKET_BRANCH ?? null,\n runUrl,\n };\n}\n\nconst detectors: DetectFn[] = [\n detectGitHubActions,\n detectGitLabCI,\n detectJenkins,\n detectCircleCI,\n detectBitbucketPipelines,\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.test-row-retries{background:var(--bg-2);border-bottom:1px solid var(--bd-s)}\n.test-row-retry-item{padding-left:42px;border-bottom:1px solid var(--bd-s)}\n.test-row-retry-item:last-child{border-bottom:none}\n.test-row-retry-item .test-row-title{font-size:13px;color:var(--fg-2);font-weight:400}\n.test-row-retry-item .status-indicator{width:8px;height:8px}\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.copy-prompt-btn{margin-left:auto;display:inline-flex;align-items:center;gap:5px;padding:4px 10px;font-size:11px;font-weight:500;font-family:inherit;color:var(--fg-1);background:var(--bg-3);border:1px solid var(--bd);border-radius:6px;cursor:pointer;transition:all .15s;white-space:nowrap}\n.copy-prompt-btn:hover{background:var(--bg-2);color:var(--fg);border-color:var(--bd-m)}\n.copy-prompt-btn.copied{color:#34d399;border-color:rgba(52,211,153,0.3);background:rgba(52,211,153,0.08)}\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{border:1px solid var(--bd);border-radius:8px;overflow:hidden;background:var(--bg-1)}\n.ndt-toolbar{display:flex;align-items:center;gap:4px;padding:4px 8px;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:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\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;background:var(--bg)}\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:4px 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-row.seekable{cursor:pointer}\n.ndt-row--active{background:rgba(3,183,156,0.1) !important;border-left:3px solid #03b79c}\n.ndt-row--active .ndt-url{color:#2dd4a8}\n.ndt-offset{color:#03b79c !important;font-weight:600}\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/* ── Action Steps ── */\n.actions-header{font-size:11px;font-weight:700;color:var(--fg-1);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px;display:flex;align-items:center;gap:6px}\n.actions-header svg{opacity:.6}\n.actions-count{font-weight:400;color:var(--fg-2);font-size:10px}\n.actions-timeline{display:flex;flex-direction:column;gap:0}\n.action-connector{width:2px;height:6px;background:var(--bd-m);margin-left:18px}\n.action-step{display:flex;align-items:center;gap:8px;padding:6px 12px;border-radius:6px;font-size:12px;transition:background .1s}\n.action-step:hover{background:var(--hvr)}\n.action-badge{font-size:9px;font-weight:600;padding:2px 7px;border-radius:4px;text-transform:uppercase;letter-spacing:.03em;flex-shrink:0;white-space:nowrap}\n.action-badge-ui{background:rgba(59,130,246,0.12);color:#60a5fa}\n.action-badge-assertion{background:rgba(34,197,94,0.12);color:#22c55e}\n.action-badge-assertion.failed{background:rgba(239,68,68,0.12);color:#ef4444}\n.action-badge-custom{background:rgba(107,114,128,0.1);color:var(--fg-2)}\n.action-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--fg)}\n.action-duration{font-size:10px;color:var(--fg-2);flex-shrink:0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n.action-failed{background:rgba(239,68,68,0.04)}\n.action-failed:hover{background:rgba(239,68,68,0.08)}\n.action-error{font-size:11px;color:#ef4444;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre-wrap;word-break:break-word;line-height:1.5;padding:4px 12px 8px;margin-bottom:2px}\n.action-step.seekable{cursor:pointer}\n.action-step.seekable:hover{background:rgba(3,183,156,0.06)}\n.action-offset{font-size:9px;color:#03b79c;font-weight:600;flex-shrink:0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;padding:1px 5px;border-radius:3px;background:rgba(3,183,156,0.08)}\n.action-active{background:rgba(3,183,156,0.12);border-left:3px solid #03b79c;transition:background .2s,border-left .2s}\n.action-active .action-title{color:#2dd4a8}\n.action-active .action-offset{background:rgba(3,183,156,0.2)}\n.action-step.seeking{animation:trSeekFlash .4s ease-out}\n@keyframes trSeekFlash{0%{background:rgba(3,183,156,0.15)}100%{background:transparent}}\n\n/* ── Console/Logs Panel ── */\n.console-panel{border:1px solid var(--bd);border-radius:8px;overflow:hidden;background:var(--bg-1)}\n.console-header{font-size:11px;font-weight:700;color:var(--fg-1);text-transform:uppercase;letter-spacing:.06em;display:flex;align-items:center;gap:6px;padding:8px 12px;border-bottom:1px solid var(--bd);background:var(--bg-2)}\n.console-header svg{opacity:.6}\n.console-count{font-weight:400;color:var(--fg-2);font-size:10px}\n.console-error-count{color:#f87171;font-weight:500;font-size:10px;text-transform:none;letter-spacing:0;background:rgba(248,113,113,0.1);padding:1px 6px;border-radius:3px}\n.console-warn-count{color:#fbbf24;font-weight:500;font-size:10px;text-transform:none;letter-spacing:0;background:rgba(251,191,36,0.1);padding:1px 6px;border-radius:3px}\n.console-toolbar{display:flex;gap:3px;padding:4px 8px;border-bottom:1px solid var(--bd);flex-wrap:wrap;background:var(--bg-2)}\n.console-filter-btn{font-size:10px;padding:2px 8px;border-radius:4px;border:1px solid transparent;background:transparent;color:var(--fg-2);cursor:pointer;transition:all .15s;font-family:inherit}\n.console-filter-btn.active{background:var(--bg-3);color:var(--fg-1);border-color:var(--bd-m);font-weight:600}\n.console-filter-btn:hover{background:var(--hvr);color:var(--fg-1)}\n.console-list{max-height:400px;overflow-y:auto;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;background:var(--bg)}\n.console-list::-webkit-scrollbar{width:5px}.console-list::-webkit-scrollbar-thumb{background:var(--scroll-thumb);border-radius:4px}\n.console-entry{display:flex;align-items:baseline;gap:6px;padding:2px 10px;border-bottom:1px solid var(--bd-xs);line-height:20px}\n.console-entry:hover{background:var(--hvr)}\n.console-level-badge{font-weight:600;font-size:9px;min-width:32px;text-transform:uppercase;letter-spacing:.03em}\n.console-time{color:var(--fg-2);font-size:10px;white-space:nowrap;min-width:65px}\n.console-text{flex:1;word-break:break-word;white-space:pre-wrap;color:var(--fg)}\n.console-loc{color:var(--fg-2);font-size:10px;white-space:nowrap;margin-left:auto;max-width:180px;overflow:hidden;text-overflow:ellipsis}\n.console-entry.seekable{cursor:pointer}\n.console-entry.seekable:hover{background:rgba(3,183,156,0.06)}\n.console-active{background:rgba(3,183,156,0.12) !important;border-left:3px solid #03b79c}\n.console-active .console-text{color:#2dd4a8}\n.console-offset{color:#03b79c;font-size:9px;font-weight:600;min-width:42px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n.console-level-error,.console-level-stderr{background:rgba(248,113,113,0.06)}\n.console-level-warn{background:rgba(251,191,36,0.06)}\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/* Loading Overlay */\n.loading-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:var(--bg);z-index:9999;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px}\n.loading-spinner{width:36px;height:36px;border:3px solid var(--bd-l);border-top-color:var(--fg-1);border-radius:50%;animation:spin .8s linear infinite}\n.loading-text{font:13px/1 -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;color:var(--fg-1)}\n@keyframes spin{to{transform:rotate(360deg)}}\n/* Global scrollbar styling */\n*{scrollbar-width:thin;scrollbar-color:var(--scroll-thumb) transparent}\n::-webkit-scrollbar{width:6px;height:6px}\n::-webkit-scrollbar-track{background:transparent}\n::-webkit-scrollbar-thumb{background:var(--scroll-thumb);border-radius:3px}\n::-webkit-scrollbar-thumb:hover{background:var(--fg-2)}\n::-webkit-scrollbar-corner{background:transparent}\n/* Virtual grid sentinel */\n.vg-sentinel{height:1px}\n/* ── Streaming: detail loading / error states ── */\n.detail-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:48px 20px;gap:14px}\n.detail-loading .loading-spinner{width:28px;height:28px;border:3px solid var(--bd-l);border-top-color:var(--fg-1);border-radius:50%;animation:spin .8s linear infinite}\n.detail-loading .loading-text{font-size:12px;color:var(--fg-2)}\n.detail-error{padding:32px 20px;text-align:center;color:var(--fg-2);font-size:13px;line-height:1.7}\n.detail-error .failure-message{margin-bottom:16px;padding:12px;background:rgba(239,68,68,0.06);border:1px solid rgba(239,68,68,0.15);border-radius:8px;font-size:12px;color:#f87171;text-align:left;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre-wrap;word-break:break-word}\n.detail-error code{font-size:11px;padding:2px 6px;background:var(--bg-2);border-radius:4px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n.server-offline-banner{padding:8px 14px;background:rgba(251,191,36,0.08);border:1px solid rgba(251,191,36,0.2);border-radius:6px;font-size:11px;color:#fbbf24;text-align:center;margin-bottom:12px}\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,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\"/g,'&quot;').replace(/'/g,'&#39;')}\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,visual:t.visual||[],networkRequests:t.networkRequests||[],steps:[],apiCalls:[],actions:t.actions||[],consoleLogs:t.consoleLogs||[],\n apiAssertions:t.apiAssertions||[],\n apiCallCount:0,apiStatusMix:{'2xx':0,'3xx':0,'4xx':0,'5xx':0,'error':0},\n assertionsPassed:0,assertionsFailed:0,\n device:t.device||null,aiDefects:t.aiDefects||[]\n };\n var _as=testMap[t.testId].apiAssertions;\n for(var _ai=0;_ai<_as.length;_ai++){\n if(_as[_ai].status==='failed')testMap[t.testId].assertionsFailed++;\n else testMap[t.testId].assertionsPassed++;\n }\n }\n if(entry.type==='api_call'){\n var _mt=testMap[t.testId];\n _mt.apiCallCount++;\n var _code=entry.response&&typeof entry.response.status==='number'?entry.response.status:null;\n var _bucket=_code===null?'error':(_code>=500?'5xx':(_code>=400?'4xx':(_code>=300?'3xx':(_code>=200?'2xx':'error'))));\n _mt.apiStatusMix[_bucket]++;\n _mt.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||entry.timestamp,duration:entry.duration||entry.durationOnUrl||0,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 /* ── Streaming mode: build tests from compact index, grouping retries under the final attempt ── */\n if(data.reportMode==='streaming'&&data.index&&data.index.length>0){\n tests=[];testMap={};\n var _retryGroups={};\n for(var xi=0;xi<data.index.length;xi++){\n var ix=data.index[xi];\n var gk=ix.retryGroupKey||ix.id;\n if(!_retryGroups[gk])_retryGroups[gk]={final:null,attempts:[]};\n if(ix.isRetry){_retryGroups[gk].attempts.push(ix);}\n else{_retryGroups[gk].final=ix;}\n }\n for(var gk2 in _retryGroups){\n if(!_retryGroups.hasOwnProperty(gk2))continue;\n var grp=_retryGroups[gk2];\n // Defensive: if every attempt in a group was marked isRetry (shouldn't happen),\n // fall back to the highest retryIndex so the test still surfaces.\n var final=grp.final;\n if(!final&&grp.attempts.length>0){\n final=grp.attempts.reduce(function(a,b){return b.retryIndex>a.retryIndex?b:a;});\n grp.attempts=grp.attempts.filter(function(a){return a!==final;});\n }\n if(!final)continue;\n var ix2=final;\n var st={\n testId:ix2.id,title:ix2.title,status:ix2.status,duration:ix2.duration,\n filePath:ix2.filePath,tags:ix2.tags||[],isFlaky:ix2.status==='flaky',\n testType:ix2.testType||'e2e',retryCount:grp.attempts.length,suiteName:'',\n startedAt:'',completedAt:'',\n networkRequests:[],steps:[],apiCalls:[],actions:[],consoleLogs:[],\n artifacts:null,failure:null,retryStatus:null,expectedStatus:null,actualStatus:null,\n hasNetworkData:ix2.hasNetworkData,hasConsoleData:ix2.hasConsoleData,\n hasActionSteps:ix2.hasActionSteps,hasArtifacts:ix2.hasArtifacts,\n errorMessage:ix2.errorMessage,networkCount:ix2.networkCount||0,\n consoleCount:ix2.consoleCount||0,actionCount:ix2.actionCount||0,\n /* Carried from the index so the grid and the filter bar can count\n visual comparisons without opening every test's detail file. */\n visualCount:ix2.visualCount||0,visualFailedCount:ix2.visualFailedCount||0,\n domChangeCount:ix2.domChangeCount||0,visualDiffPath:ix2.visualDiffPath||null,\n /* API and mobile evidence, same rule: the grid must never open a\n test's detail file to draw a row. */\n apiCallCount:ix2.apiCallCount||0,apiStatusMix:ix2.apiStatusMix||null,\n assertionsPassed:ix2.assertionsPassed||0,assertionsFailed:ix2.assertionsFailed||0,\n firstFailedAssertion:ix2.firstFailedAssertion||null,\n device:ix2.device||null,stepKinds:ix2.stepKinds||null,\n deviceLogErrors:ix2.deviceLogErrors||0,hasCrashLog:!!ix2.hasCrashLog,\n hasVideo:!!ix2.hasVideo,aiDefectCount:ix2.aiDefectCount||0,\n retryAttempts:grp.attempts.slice().sort(function(a,b){return a.retryIndex-b.retryIndex;}).map(function(a){\n return {testId:a.id,retryIndex:a.retryIndex,status:a.status,duration:a.duration,errorMessage:a.errorMessage};\n })\n };\n testMap[st.testId]=st;\n tests.push(st);\n // Register each retry attempt in testMap too (keyed by its own id) so opening\n // its drawer has a title/meta to merge with the fetched detail — it never\n // appears in the grid itself, only as a nested row under the final attempt.\n for(var ai=0;ai<st.retryAttempts.length;ai++){\n var ra=st.retryAttempts[ai];\n testMap[ra.testId]={\n testId:ra.testId,title:st.title+' (attempt '+(ra.retryIndex+1)+')',status:ra.status,\n duration:ra.duration,filePath:st.filePath,tags:st.tags,isFlaky:false,\n testType:st.testType,retryCount:0,suiteName:'',startedAt:'',completedAt:'',\n networkRequests:[],steps:[],apiCalls:[],actions:[],consoleLogs:[],\n artifacts:null,failure:null,retryStatus:'attempt '+(ra.retryIndex+1)+' of '+(st.retryAttempts.length+1),\n expectedStatus:null,actualStatus:null,errorMessage:ra.errorMessage,\n networkCount:0,consoleCount:0,actionCount:0\n };\n }\n }\n tests.sort(function(a,b){\n if(a.filePath!==b.filePath)return a.filePath<b.filePath?-1:1;\n return 0;\n });\n }\n\n /* ── Streaming detail cache (LRU, max 50 entries) ── */\n var _detailCache={};\n var _detailCacheKeys=[];\n var _detailCacheMax=50;\n function cacheDetail(testId,data){\n if(_detailCache[testId]){\n var ki=_detailCacheKeys.indexOf(testId);\n if(ki>-1)_detailCacheKeys.splice(ki,1);\n }else if(_detailCacheKeys.length>=_detailCacheMax){\n var evict=_detailCacheKeys.shift();\n delete _detailCache[evict];\n }\n _detailCache[testId]=data;\n _detailCacheKeys.push(testId);\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 drawerBodyEl=document.getElementById('drawer-body');\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 /* renderSummary lives in html-js-layout: the six equal stat chips were\n replaced by the verdict band, which collapses empty statuses. */\n\n /* Longest test in the run, so each bar reads as \"relative to the slowest\".\n Recomputed per grid render because filtering changes the set. */\n var _maxDur=1;\n function rowBarPct(d){\n var pct=Math.round(((d||0)/_maxDur)*100);\n /* A bar too short to see says nothing; floor it at something visible. */\n return Math.max(4,Math.min(100,pct));\n }\n\n /* Small square icons: what was captured, before you open the test. */\n\n /* Both report modes reach these renderers with different shapes: the\n streaming index carries counters, the embedded testMap carries the arrays.\n Reading only the counters made the evidence column, the needs-attention\n chips and the diff thumbnail vanish in embedded mode. */\n function evVisualCount(t){return t.visual?t.visual.length:(t.visualCount||0);}\n function evVisualFailed(t){\n if(t.visual){var n=0;for(var i=0;i<t.visual.length;i++)if(t.visual[i].status==='failed')n++;return n;}\n return t.visualFailedCount||0;\n }\n function evDomChanges(t){\n if(t.visual){var n=0;for(var i=0;i<t.visual.length;i++)n+=(t.visual[i].domChangeCount||0);return n;}\n return t.domChangeCount||0;\n }\n function evHasArtifacts(t){\n if(t.artifacts)return !!(t.artifacts.screenshot||t.artifacts.video||t.artifacts.trace);\n return !!t.hasArtifacts;\n }\n function evNetworkCount(t){return t.networkRequests?t.networkRequests.length:(t.networkCount||0);}\n function evConsoleCount(t){return t.consoleLogs?t.consoleLogs.length:(t.consoleCount||0);}\n function evDiffPath(t){\n if(t.visualDiffPath)return t.visualDiffPath;\n if(t.visual){for(var i=0;i<t.visual.length;i++){if(t.visual[i].diff)return t.visual[i].diff;}}\n return null;\n }\n\n function rowEvidence(t){\n var h='<div class=\"trow-evidence\">';\n var any=false;\n if(typeof apiRowEvidence==='function'){\n var _ae=apiRowEvidence(t);\n if(_ae){h+=_ae;any=true;}\n }\n if(typeof mobileRowEvidence==='function'){\n var _me=mobileRowEvidence(t);\n if(_me){h+=_me;any=true;}\n }\n var _vis=evVisualCount(t),_visFail=evVisualFailed(t),_dom=evDomChanges(t);\n var _net=evNetworkCount(t),_con=evConsoleCount(t);\n if(_vis>0){\n any=true;\n h+='<span class=\"tev'+(_visFail>0?' tev-visual':'')+'\" title=\"'+(_visFail>0?'Visual comparison failed':'Visual comparison')+'\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/><path d=\"M12 3v18\"/></svg></span>';\n }\n if(_dom>0){\n any=true;\n h+='<span class=\"tev tev-dom\" title=\"'+_dom+' DOM elements changed\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M8 3H5a2 2 0 00-2 2v3M16 3h3a2 2 0 012 2v3M8 21H5a2 2 0 01-2-2v-3M16 21h3a2 2 0 002-2v-3\"/></svg></span>';\n }\n if(evHasArtifacts(t)){\n any=true;\n h+='<span class=\"tev\" title=\"Video and trace\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polygon points=\"5 3 19 12 5 21\"/></svg></span>';\n }\n if(_net>0){\n any=true;\n h+='<span class=\"tev\" title=\"'+_net+' network requests\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M3 12h4l3 8 4-16 3 8h4\"/></svg></span>';\n }\n if(_con>0){\n any=true;\n h+='<span class=\"tev\" title=\"'+_con+' console messages\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M4 17l6-6-6-6M12 19h8\"/></svg></span>';\n }\n /* Keep the column even when empty so the rows stay aligned. */\n if(!any)h+='<span style=\"width:18px\"></span>';\n return h+'</div>';\n }\n\n /* ── Virtual Grid: progressive chunked rendering ── */\n var _vgGroups=[];\n var _vgRenderedTo=0;\n var _vgChunkSize=10;\n var _vgSentinel=null;\n var _vgObserver=null;\n\n function _vgBuildGroups(filtered){\n var groups=[];var lastFile='';var g=null;\n for(var i=0;i<filtered.length;i++){\n var t=filtered[i];\n if(t.filePath!==lastFile){\n g={filePath:t.filePath,tests:[]};\n groups.push(g);\n lastFile=t.filePath;\n }\n g.tests.push(t);\n }\n return groups;\n }\n\n function _vgRenderGroupHtml(g){\n var h='<div class=\"file-group\">';\n h+='<div class=\"file-group-header\">'+esc(g.filePath)+'</div>';\n h+='<div class=\"file-group-card\">';\n for(var i=0;i<g.tests.length;i++){\n var t=g.tests[i];\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 tri-inline\">';\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+=rowEvidence(t);\n h+='<div class=\"trow-time\"><div class=\"trow-bar\"><span class=\"tb-'+t.status+'\" style=\"width:'+rowBarPct(t.duration)+'%\"></span></div>';\n h+='<span class=\"test-row-dur\">'+fmtDur(t.duration)+'</span></div>';\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 if(t.retryAttempts&&t.retryAttempts.length>0){\n h+='<div class=\"test-row-retries\">';\n for(var ri=0;ri<t.retryAttempts.length;ri++){\n var ra=t.retryAttempts[ri];\n h+='<div class=\"test-row test-row-retry-item\" data-testid=\"'+esc(ra.testId)+'\">';\n h+='<div class=\"status-indicator si-'+ra.status+'\"></div>';\n h+='<div class=\"test-row-info\"><div class=\"test-row-title\">Attempt '+(ra.retryIndex+1)+' &mdash; '+esc(ra.status)+(ra.errorMessage?': '+esc(ra.errorMessage):'')+'</div></div>';\n h+='<span class=\"test-row-dur\">'+fmtDur(ra.duration)+'</span>';\n h+='<svg class=\"test-row-arrow\" width=\"14\" height=\"14\" 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 h+='</div>';\n }\n }\n h+='</div></div>';\n return h;\n }\n\n function _vgRenderChunk(){\n if(_vgRenderedTo>=_vgGroups.length){\n if(_vgSentinel)_vgSentinel.style.display='none';\n return;\n }\n var end=Math.min(_vgRenderedTo+_vgChunkSize,_vgGroups.length);\n var frag=document.createDocumentFragment();\n var tmp=document.createElement('div');\n var h='';\n for(var i=_vgRenderedTo;i<end;i++){h+=_vgRenderGroupHtml(_vgGroups[i]);}\n tmp.innerHTML=h;\n while(tmp.firstChild)frag.appendChild(tmp.firstChild);\n /* Insert before sentinel */\n if(_vgSentinel&&_vgSentinel.parentNode){\n _vgSentinel.parentNode.insertBefore(frag,_vgSentinel);\n }\n _vgRenderedTo=end;\n if(_vgRenderedTo>=_vgGroups.length&&_vgSentinel){\n _vgSentinel.style.display='none';\n }\n }\n\n function _vgSetupObserver(){\n if(_vgObserver){_vgObserver.disconnect();_vgObserver=null;}\n if(!_vgSentinel)return;\n if(typeof IntersectionObserver==='undefined'){\n /* Fallback: render everything */\n while(_vgRenderedTo<_vgGroups.length)_vgRenderChunk();\n return;\n }\n _vgObserver=new IntersectionObserver(function(entries){\n if(entries[0].isIntersecting)_vgRenderChunk();\n },{rootMargin:'600px'});\n _vgObserver.observe(_vgSentinel);\n }\n\n /* ── Render: Test Grid ── */\n function renderTestGrid(){\n if(_vgObserver){_vgObserver.disconnect();_vgObserver=null;}\n var filtered=getFilteredTests();\n /* The bar on each row is relative to the slowest test CURRENTLY SHOWN, so\n filtering to the slow ones still spreads them across the full width. */\n _maxDur=1;\n for(var md=0;md<filtered.length;md++){if(filtered[md].duration>_maxDur)_maxDur=filtered[md].duration;}\n if(filtered.length===0){testGridEl.innerHTML='<div class=\"no-tests\">No tests match your filters</div>';_vgGroups=[];return;}\n _vgGroups=_vgBuildGroups(filtered);\n _vgRenderedTo=0;\n /* Render initial batch directly, then progressively load the rest */\n var initCount=Math.min(_vgChunkSize,_vgGroups.length);\n var h='';\n for(var i=0;i<initCount;i++){h+=_vgRenderGroupHtml(_vgGroups[i]);}\n _vgRenderedTo=initCount;\n h+='<div class=\"vg-sentinel\" id=\"vg-sentinel\"></div>';\n testGridEl.innerHTML=h;\n _vgSentinel=document.getElementById('vg-sentinel');\n if(_vgRenderedTo<_vgGroups.length){\n _vgSetupObserver();\n }else if(_vgSentinel){\n _vgSentinel.style.display='none';\n }\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,hasT=!!artifacts.trace;\n if(!hasS&&!hasV&&!hasT)return '<div class=\"artifact-empty\">No artifacts captured</div>';\n var tabs=[];\n if(hasS)tabs.push('screenshot');\n if(hasV)tabs.push('video');\n if(hasT)tabs.push('trace');\n\n var h='';\n if(tabs.length>1){\n h+='<div class=\"seg-ctrl\" data-seg-group=\"artifacts\">';\n for(var ti=0;ti<tabs.length;ti++){\n var t=tabs[ti],ico='';\n if(t==='screenshot')ico='<svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/><circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\"/><path d=\"M21 15l-5-5L5 21\"/></svg>';\n else if(t==='video')ico='<svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polygon points=\"5 3 19 12 5 21\"/></svg>';\n else ico='<svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M4 4h16v6H4zM4 14h16v6H4z\"/></svg>';\n var label=t==='screenshot'?'Screenshot':(t==='video'?'Video':'Trace');\n h+='<button class=\"seg-btn'+(ti===0?' active':'')+'\" data-seg=\"'+t+'\">'+ico+label+'</button>';\n }\n h+='</div>';\n }\n\n /* No lazy loading. A lazily-loaded image inside a display:none pane is\n never fetched by Chrome, and every pane but the first starts hidden —\n the screenshot simply never appeared. The same bug was fixed for the\n visual comparison images and left standing here. */\n if(hasS){\n h+='<div class=\"seg-pane'+(tabs[0]==='screenshot'?' active':'')+'\" data-seg-pane=\"screenshot\">';\n h+='<img class=\"artifact-col-img\" src=\"'+esc(artifacts.screenshot)+'\" alt=\"Screenshot\">';\n h+='<div class=\"artifact-col-caption\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7\"/></svg>Click to enlarge</div></div>';\n }\n if(hasV){\n h+='<div class=\"seg-pane'+(tabs[0]==='video'?' active':'')+'\" data-seg-pane=\"video\">';\n h+='<div class=\"artifact-col-video\"><video controls preload=\"metadata\" src=\"'+esc(artifacts.video)+'\"></video></div>';\n h+='<div class=\"artifact-col-caption\">'+esc(artifacts.video.split('/').pop())+'</div></div>';\n }\n /* The trace is the thing a failure is actually debugged from, and the\n report had no way to reach it even though it was captured and copied. */\n if(hasT){\n h+='<div class=\"seg-pane'+(tabs[0]==='trace'?' active':'')+'\" data-seg-pane=\"trace\">';\n h+='<div class=\"trace-open\"><a class=\"trace-link\" href=\"'+esc(artifacts.trace)+'\" download>';\n h+='<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M12 3v12M7 10l5 5 5-5M5 21h14\"/></svg>';\n h+='Download '+esc(artifacts.trace.split('/').pop())+'</a>';\n h+='<div class=\"trace-hint\">Open it with <code>npx playwright show-trace &lt;file&gt;</code>, or drop it on <code>trace.playwright.dev</code>.</div>';\n h+='</div></div>';\n }\n return h;\n }\n\n /* ── Render: Drawer Content ── */\n function renderDrawer(testId,detail){\n var t=detail||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 /* ── Stacked sections: 1) Artifacts 2) Timeline / Network 3) API Calls ── */\n var hasArt=t.artifacts&&(t.artifacts.screenshot||t.artifacts.video||t.artifacts.trace);\n var hasVisual=t.visual&&t.visual.length>0;\n var hasSteps=t.steps&&t.steps.length>0;\n var hasApiCalls=t.apiCalls&&t.apiCalls.length>0;\n var hasActions=t.actions&&t.actions.length>0;\n var hasConsoleLogs=t.consoleLogs&&t.consoleLogs.length>0;\n var hasNetworkReqs=t.networkRequests&&t.networkRequests.length>0;\n var hasVideo=!!(t.artifacts&&t.artifacts.video);\n /* Compute base time for video offset calculation (console/network) */\n var baseTime=0;\n if(hasVideo&&t.startedAt){baseTime=Date.parse(t.startedAt);}\n if(!baseTime&&hasVideo&&t.consoleLogs&&t.consoleLogs.length>0&&t.consoleLogs[0].timestamp){baseTime=Date.parse(t.consoleLogs[0].timestamp);}\n\n var hasFailure=(t.status==='failed'||t.status==='flaky')&&t.failure;\n var hasDom=false;\n if(t.visual){for(var dv=0;dv<t.visual.length;dv++){if(t.visual[dv].domChangeCount)hasDom=true;}}\n\n /* What the rail is allowed to offer — built from what actually rendered,\n so a jump can never land on a section that is not there. */\n var present={};\n if(hasFailure)present.failure={};\n if(hasVisual)present.visual={n:t.visual.length};\n if(hasDom)present.dom={n:(function(){var n=0;for(var q=0;q<t.visual.length;q++)n+=t.visual[q].domChangeCount||0;return n;})()};\n if(hasArt)present.artifacts={};\n if(hasActions)present.actions={n:t.actions.length};\n if(hasSteps)present.timeline={n:t.steps.length};\n var _asserts=t.apiAssertions||[];\n var hasAssertions=_asserts.length>0;\n if(hasAssertions){\n var _af=0;\n for(var _q=0;_q<_asserts.length;_q++)if(_asserts[_q].status==='failed')_af++;\n present.assertions={n:_asserts.length,bad:_af>0};\n }\n if(hasApiCalls)present.api={n:t.apiCalls.length};\n if(!hasSteps&&hasConsoleLogs)present.console={n:t.consoleLogs.length};\n if(!hasSteps&&hasNetworkReqs)present.network={n:t.networkRequests.length};\n\n var anySection=hasArt||hasVisual||hasSteps||hasApiCalls||hasActions||hasConsoleLogs||hasNetworkReqs||hasAssertions;\n\n h+='<div class=\"drawer-layout\">';\n h+='<div class=\"drawer-rail\">'+renderDrawerRail(present,t)+'</div>';\n h+='<div class=\"drawer-main\">';\n\n if(hasFailure)h+='<div class=\"drawer-section\" data-section=\"failure\">'+renderFailure(t.failure,t.filePath,t.title,t.tags)+'</div>';\n else if(t.status==='failed')h+='<div class=\"drawer-section\" data-section=\"failure\" style=\"padding:14px;background:var(--bg-2);border:1px solid var(--bd);border-radius:10px;font-size:12px;color:var(--fg-2);font-style:italic\">No diagnostic information available</div>';\n\n if(anySection){\n h+='<div class=\"drawer-sections\">';\n if(hasVisual)h+='<div class=\"drawer-section\" data-section=\"visual\">'+renderVisualSection(t.visual)+'</div>';\n if(hasArt)h+='<div class=\"drawer-section\" data-section=\"artifacts\">'+renderArtifactColumn(t.artifacts)+'</div>';\n if(hasActions)h+='<div class=\"drawer-section\" data-section=\"actions\">'+renderActionsColumn(t.actions,hasVideo)+'</div>';\n if(hasSteps){var _pgMeta=t._networkTotal?{testId:t._testId,page:t._networkPage||1,total:t._networkTotal,pageSize:t._networkPageSize||500}:null;h+='<div class=\"drawer-section\" data-section=\"timeline\">'+renderTimelineColumn(t.steps,t.networkRequests,t.consoleLogs,t.testType,hasVideo,baseTime,_pgMeta)+'</div>';}\n if(!hasSteps&&hasNetworkReqs&&typeof renderNetworkDevTools==='function'){var _pgMeta2=t._networkTotal?{testId:t._testId,page:t._networkPage||1,total:t._networkTotal,pageSize:t._networkPageSize||500}:null;h+='<div class=\"drawer-section\" data-section=\"network\">'+renderNetworkDevTools(t.networkRequests,false,0,_pgMeta2)+'</div>';}\n if(!hasSteps&&hasConsoleLogs)h+='<div class=\"drawer-section\" data-section=\"console\">'+renderConsoleColumn(t.consoleLogs,t.testType,hasVideo,baseTime)+'</div>';\n /* Assertions before the calls: the assertion is what failed, the call\n is only where it happened. */\n if(hasAssertions&&typeof renderAssertionsSection==='function')\n h+='<div class=\"drawer-section\" data-section=\"assertions\">'+renderAssertionsSection(_asserts,t.apiCalls)+'</div>';\n if(hasApiCalls)h+='<div class=\"drawer-section\" data-section=\"api\">'+renderApiCallsColumn(t.apiCalls,_asserts)+'</div>';\n h+='</div>';\n }\n\n h+='</div></div>';\n\n h+='</div>';\n drawerBodyEl.innerHTML=h;\n /* Wire up video ↔ action step sync if both are present */\n if(typeof syncActionsWithVideo==='function')syncActionsWithVideo();\n }\n\n /* ── Drawer open / close ── */\n /* Fetch (streaming) and render one test into the detail view. The routing\n around it lives in html-js-layout; this only fills the page. */\n function loadAndRenderTest(testId){\n if(data.reportMode==='streaming'){\n if(_detailCache[testId]){renderDrawer(testId,_detailCache[testId]);return;}\n drawerBodyEl.innerHTML='<div class=\"detail-loading\"><div class=\"loading-spinner\"></div><div class=\"loading-text\">Loading test details...</div></div>';\n if(window.__trStreamingFetchDetail){\n window.__trStreamingFetchDetail(testId,function(err,detail){\n if(err){\n var t=testMap[testId];\n var emsg=t&&t.errorMessage?'<div class=\"failure-message\">'+esc(t.errorMessage)+'</div>':'';\n drawerBodyEl.innerHTML='<div class=\"detail-error\">'+emsg+'<p>Server unavailable &mdash; run <code>npx testrelic serve &lt;path&gt;</code> to view full details</p></div>';\n return;\n }\n /* Merge index entry with server detail metadata */\n var idx=testMap[testId]||{};\n var merged={};\n var k;for(k in idx){if(idx.hasOwnProperty(k))merged[k]=idx[k];}\n merged.actions=detail.actions||[];\n merged.apiAssertions=detail.apiAssertions||[];\n merged.artifacts=detail.artifacts||null;\n merged.visual=detail.visual||[];\n merged.failure=detail.failureDiagnostic||detail.failure||null;\n merged.steps=detail.navigations||[];\n if(detail.startedAt)merged.startedAt=detail.startedAt;\n /* Mark counts for lazy loading */\n merged._networkCount=detail.networkRequestsCount||idx.networkCount||0;\n merged._consoleCount=detail.consoleLogsCount||idx.consoleCount||0;\n merged._apiCallsCount=detail.apiCallsCount||0;\n merged._testId=testId;\n /* Initially empty — loaded on demand */\n merged.networkRequests=[];\n merged.consoleLogs=[];\n merged.apiCalls=[];\n cacheDetail(testId,merged);\n renderDrawer(testId,merged);\n /* Lazy-load heavy data once the page is showing */\n loadHeavyData(testId,merged);\n });\n }else{\n var t=testMap[testId];\n var emsg=t&&t.errorMessage?'<div class=\"failure-message\">'+esc(t.errorMessage)+'</div>':'';\n drawerBodyEl.innerHTML='<div class=\"detail-error\">'+emsg+'<p>Server unavailable &mdash; run <code>npx testrelic serve &lt;path&gt;</code> to view full details</p></div>';\n }\n return;\n }\n renderDrawer(testId);\n }\n\n /* Lazy-load heavy data (network, console, API calls) after drawer renders */\n function loadHeavyData(testId,merged){\n var fetchData=window.__trFetchTestData;\n if(!fetchData)return;\n var pending=0;\n var loaded=0;\n function onLoaded(){loaded++;if(loaded>=pending){renderDrawer(testId,merged);}}\n /* Load first page of network requests */\n if(merged._networkCount>0){\n pending++;\n fetchData(testId,'network',1,500,function(err,result){\n if(!err&&result){\n merged.networkRequests=result.items;\n merged._networkTotal=result.total;\n merged._networkPage=1;\n merged._networkPageSize=result.pageSize;\n merged._networkLoading=false;\n }\n onLoaded();\n });\n }\n /* Load first page of console logs */\n if(merged._consoleCount>0){\n pending++;\n fetchData(testId,'console',1,500,function(err,result){\n if(!err&&result){\n merged.consoleLogs=result.items;\n merged._consoleTotal=result.total;\n merged._consolePage=1;\n }\n onLoaded();\n });\n }\n /* Load first page of API calls */\n if(merged._apiCallsCount>0){\n pending++;\n fetchData(testId,'api-calls',1,500,function(err,result){\n if(!err&&result){\n merged.apiCalls=result.items;\n merged._apiCallsTotal=result.total;\n merged._apiCallsPage=1;\n }\n onLoaded();\n });\n }\n }\n\n /* ── Search (debounced) ── */\n var _searchTimer=0;\n function doSearch(q){\n clearTimeout(_searchTimer);\n _searchTimer=setTimeout(function(){searchQuery=q;applyFilters();},150);\n }\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,emptyMsg){\n if(!headers)return '<div class=\"ndt-body-empty\">'+(emptyMsg||('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\">'+(emptyMsg||('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\">Body truncated</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 var NDT_ROW_H=32;\n var NDT_DETAIL_H=280;\n var NDT_VBUF=30;\n\n function ndtVideoOffset(req,baseTime){\n if(!baseTime||!req.startedAt)return -1;\n var t=Date.parse(req.startedAt);\n if(isNaN(t))return -1;\n var off=(t-baseTime)/1000;\n return off>=0?off:-1;\n }\n\n function renderNetworkDevTools(reqs,hasVideo,baseTime,pageMeta){\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 /* Virtualized request list */\n h+='<div class=\"ndt-list ndt-vlist\" data-ndt-list=\"'+uid+'\" data-ndt-uid=\"'+uid+'\" style=\"overflow-y:auto;max-height:400px;position:relative\">';\n h+='<div class=\"ndt-vspacer\" style=\"height:'+(reqs.length*NDT_ROW_H)+'px;position:relative\"></div>';\n h+='</div></div>';\n\n /* Pre-compute video offsets */\n var ndtVideoOffsets=[];\n if(hasVideo&&baseTime){\n for(var noi=0;noi<reqs.length;noi++){ndtVideoOffsets.push(ndtVideoOffset(reqs[noi],baseTime));}\n }\n\n /* Store reqs data for virtual rendering */\n setTimeout(function(){\n var el=document.getElementById(uid);if(!el)return;\n var list=el.querySelector('.ndt-vlist');if(!list)return;\n var spacer=list.querySelector('.ndt-vspacer');\n var allReqs=reqs;\n var filteredIdxs=null;\n var openIdx=-1;\n var openTab='general';\n var highlightIdx=-1;\n\n function getVisible(){return filteredIdxs||allReqs.map(function(_,i){return i});}\n\n var lastDetailH=NDT_DETAIL_H;\n var NDT_TABS=['general','req-headers','req-body','res-headers','res-body'];\n\n function renderDetailWithTab(r,ri,activeTab){\n var html=renderNdtDetail(r,ri);\n /* Replace the default active tab/pane with the tracked one */\n if(activeTab&&activeTab!=='general'){\n /* Remove default active from general tab button and pane */\n html=html.replace('data-ndt-tab=\"general\" data-ndt-target','data-ndt-tab=\"general\" data-ndt-target');\n /* Swap active classes: remove from general, add to activeTab */\n var parts=html;\n /* Tabs: remove active from all, add to target */\n for(var t=0;t<NDT_TABS.length;t++){\n var tn=NDT_TABS[t];\n var isAct=tn===activeTab;\n parts=parts.replace(new RegExp('class=\"ndt-detail-tab'+(tn==='general'?' active':'')+'\" data-ndt-tab=\"'+tn+'\"'),'class=\"ndt-detail-tab'+(isAct?' active':'')+'\" data-ndt-tab=\"'+tn+'\"');\n }\n /* Panes: remove active from all, add to target */\n for(var p=0;p<NDT_TABS.length;p++){\n var pn=NDT_TABS[p];\n var isPAct=pn===activeTab;\n parts=parts.replace(new RegExp('class=\"ndt-detail-pane'+(pn==='general'?' active':'')+'\" data-ndt-pane=\"'+pn+'\"'),'class=\"ndt-detail-pane'+(isPAct?' active':'')+'\" data-ndt-pane=\"'+pn+'\"');\n }\n html=parts;\n }\n return html.replace('class=\"ndt-detail\"','class=\"ndt-detail show\"');\n }\n\n function paint(){\n var vis=getVisible();\n /* Find the visual index of the open row (if any) */\n var openVi=-1;\n if(openIdx>=0){for(var oi=0;oi<vis.length;oi++){if(vis[oi]===openIdx){openVi=oi;break;}}}\n var detailExtra=openVi>=0?lastDetailH:0;\n var totalH=vis.length*NDT_ROW_H+detailExtra;\n spacer.style.height=totalH+'px';\n var st=list.scrollTop;var vh=list.clientHeight;\n var startRow=Math.max(0,Math.floor(st/NDT_ROW_H)-NDT_VBUF);\n var endRow=Math.min(vis.length,Math.ceil((st+vh+detailExtra)/NDT_ROW_H)+NDT_VBUF);\n var out='';\n for(var vi=startRow;vi<endRow;vi++){\n var ri=vis[vi];var r=allReqs[ri];\n var isFail=r.statusCode>=400||r.statusCode===0;\n var isOpen=ri===openIdx;\n var isActive=ri===highlightIdx;\n var off=ndtVideoOffsets[ri];\n var seekable=off>=0;\n /* Rows after the open row shift down by the detail panel height */\n var rowTop=vi*NDT_ROW_H+(openVi>=0&&vi>openVi?lastDetailH:0);\n out+='<div class=\"ndt-row'+(isFail?' ndt-row--fail':'')+(isOpen?' ndt-row--open':'')+(seekable?' seekable':'')+(isActive?' ndt-row--active':'')+'\" data-ndt-idx=\"'+ri+'\" data-ndt-type=\"'+esc(r.resourceType)+'\" data-ndt-url=\"'+esc(r.url.toLowerCase())+'\"'+(seekable?' data-offset=\"'+off.toFixed(2)+'\"':'')+' style=\"position:absolute;top:'+rowTop+'px;left:0;right:0;height:'+NDT_ROW_H+'px\">';\n out+='<span class=\"ndt-status '+ndtStatusClass(r.statusCode)+'\">'+r.statusCode+'</span>';\n out+='<span class=\"ndt-method\">'+esc(r.method)+'</span>';\n out+='<span class=\"ndt-url\" title=\"'+esc(r.url)+'\">'+esc(urlPath(r.url))+'</span>';\n out+='<span class=\"ndt-type\">'+esc(RES_LABELS[r.resourceType]||r.resourceType)+'</span>';\n out+='<span class=\"ndt-size\">'+fmtBytes(r.responseSize||0)+'</span>';\n if(seekable){out+='<span class=\"ndt-time ndt-offset\">'+off.toFixed(1)+'s</span>';}\n else{out+='<span class=\"ndt-time\">'+fmtDur(r.responseTimeMs)+'</span>';}\n out+='</div>';\n if(isOpen){out+='<div style=\"position:absolute;top:'+(rowTop+NDT_ROW_H)+'px;left:0;right:0;z-index:1\" class=\"ndt-detail-wrap\">'+renderDetailWithTab(r,ri,openTab)+'</div>';}\n }\n spacer.innerHTML=out;\n /* Measure actual detail height and adjust layout without full repaint */\n if(openVi>=0){\n var detailWrap=spacer.querySelector('.ndt-detail-wrap');\n if(detailWrap){\n var measuredH=detailWrap.offsetHeight;\n if(measuredH>0&&Math.abs(measuredH-lastDetailH)>2){\n lastDetailH=measuredH;\n /* Update spacer height and row positions without re-rendering */\n var newTotalH=vis.length*NDT_ROW_H+lastDetailH;\n spacer.style.height=newTotalH+'px';\n var rows=spacer.querySelectorAll('.ndt-row');\n for(var rr=0;rr<rows.length;rr++){\n var rrIdx=parseInt(rows[rr].getAttribute('data-ndt-idx'),10);\n /* Find visual index for this row */\n for(var rv=startRow;rv<endRow;rv++){if(vis[rv]===rrIdx){\n if(rv>openVi){rows[rr].style.top=(rv*NDT_ROW_H+lastDetailH)+'px';}\n break;\n }}\n }\n detailWrap.style.top=((openVi*NDT_ROW_H)+NDT_ROW_H)+'px';\n }\n }\n }\n var countEl=el.querySelector('.ndt-count');\n if(countEl)countEl.textContent=vis.length+(vis.length!==allReqs.length?' / '+allReqs.length:'')+' requests';\n }\n\n var rafPending=false;\n /* Infinite scroll: pagination state */\n var _pgTestId=pageMeta&&pageMeta.testId?pageMeta.testId:null;\n var _pgPage=pageMeta&&pageMeta.page?pageMeta.page:1;\n var _pgTotal=pageMeta&&pageMeta.total?pageMeta.total:allReqs.length;\n var _pgPageSize=pageMeta&&pageMeta.pageSize?pageMeta.pageSize:500;\n var _pgLoading=false;\n\n function updateFilterBtns(){\n var filterDiv=el.querySelector('.ndt-filter');if(!filterDiv)return;\n var activeFilter='all';\n var activeBtn=filterDiv.querySelector('.ndt-filter-btn.active');\n if(activeBtn)activeFilter=activeBtn.getAttribute('data-ndt-filter')||'all';\n var btnH='<button class=\"ndt-filter-btn'+(activeFilter==='all'?' 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<allReqs.length;ci++){if(allReqs[ci].resourceType===types[ti])cnt++;}\n if(cnt>0)btnH+='<button class=\"ndt-filter-btn'+(activeFilter===types[ti]?' active':'')+'\" data-ndt-filter=\"'+types[ti]+'\">'+typeLabels[types[ti]]+' <span style=\"opacity:.6\">'+cnt+'</span></button>';\n }\n filterDiv.innerHTML=btnH;\n }\n\n function loadNextPage(){\n if(_pgLoading||!_pgTestId)return;\n if(allReqs.length>=_pgTotal)return;\n _pgLoading=true;\n var fetchData=window.__trFetchTestData;\n if(!fetchData){_pgLoading=false;return;}\n fetchData(_pgTestId,'network',_pgPage+1,_pgPageSize,function(err,result){\n _pgLoading=false;\n if(err||!result||!result.items)return;\n _pgPage++;\n for(var i=0;i<result.items.length;i++){\n allReqs.push(result.items[i]);\n if(hasVideo&&baseTime){ndtVideoOffsets.push(ndtVideoOffset(result.items[i],baseTime));}\n }\n _pgTotal=result.total;\n /* Re-apply filter to include new items */\n if(filteredIdxs!==null){\n var filterDiv=el.querySelector('.ndt-filter');\n var activeBtn=filterDiv?filterDiv.querySelector('.ndt-filter-btn.active'):null;\n var ft=activeBtn?activeBtn.getAttribute('data-ndt-filter'):'all';\n var searchEl=el.querySelector('.ndt-search');\n var q=searchEl?searchEl.value.toLowerCase():'';\n if(ft&&ft!=='all'||q){\n filteredIdxs=[];\n for(var fi=0;fi<allReqs.length;fi++){\n var r=allReqs[fi];\n if(ft&&ft!=='all'&&r.resourceType!==ft)continue;\n if(q&&r.url.toLowerCase().indexOf(q)<0)continue;\n filteredIdxs.push(fi);\n }\n }else{filteredIdxs=null;}\n }\n updateFilterBtns();\n paint();\n });\n }\n\n list.addEventListener('scroll',function(){\n if(!rafPending){rafPending=true;requestAnimationFrame(function(){rafPending=false;paint();});}\n /* Infinite scroll: load more when near bottom */\n if(list.scrollTop+list.clientHeight>=list.scrollHeight-200){loadNextPage();}\n },{passive:true});\n\n /* Click handler for rows */\n list.addEventListener('click',function(e){\n var tab=e.target.closest('.ndt-detail-tab');\n if(tab){\n /* Handle tab switch within detail panel — DOM-only, no repaint */\n e.stopPropagation();\n var tgt=tab.getAttribute('data-ndt-target');\n var pane=tab.getAttribute('data-ndt-tab');\n openTab=pane;\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 tab.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 /* Re-measure after tab switch since pane height may differ */\n requestAnimationFrame(function(){\n var detailWrap=spacer.querySelector('.ndt-detail-wrap');\n if(detailWrap){\n var mh=detailWrap.offsetHeight;\n if(mh>0&&Math.abs(mh-lastDetailH)>2){\n lastDetailH=mh;\n /* Adjust layout without full repaint to preserve tab state */\n var vis=getVisible();\n var ovi=-1;\n for(var oi=0;oi<vis.length;oi++){if(vis[oi]===openIdx){ovi=oi;break;}}\n spacer.style.height=(vis.length*NDT_ROW_H+lastDetailH)+'px';\n var rows=spacer.querySelectorAll('.ndt-row');\n for(var rr=0;rr<rows.length;rr++){\n var rrIdx=parseInt(rows[rr].getAttribute('data-ndt-idx'),10);\n for(var rv=0;rv<vis.length;rv++){if(vis[rv]===rrIdx){\n if(rv>ovi){rows[rr].style.top=(rv*NDT_ROW_H+lastDetailH)+'px';}\n break;\n }}\n }\n }\n }\n });\n }\n return;\n }\n var row=e.target.closest('.ndt-row');if(!row)return;\n e.stopPropagation();\n var idx=parseInt(row.getAttribute('data-ndt-idx'),10);\n openIdx=openIdx===idx?-1:idx;\n openTab='general';\n lastDetailH=NDT_DETAIL_H;\n paint();\n });\n\n /* Store filter/search handler on element */\n el.__ndtFilter=function(filterType,searchVal){\n var q=(searchVal||'').toLowerCase();\n if((!filterType||filterType==='all')&&!q){filteredIdxs=null;}\n else{\n filteredIdxs=[];\n for(var i=0;i<allReqs.length;i++){\n var r=allReqs[i];\n if(filterType&&filterType!=='all'&&r.resourceType!==filterType)continue;\n if(q&&r.url.toLowerCase().indexOf(q)<0)continue;\n filteredIdxs.push(i);\n }\n }\n openIdx=-1;\n list.scrollTop=0;\n paint();\n };\n\n /* Video sync: scroll to matching request by time */\n if(ndtVideoOffsets.length){\n el.__videoSync=function(seconds){\n var vis=getVisible();\n var matchVi=-1;\n for(var i=vis.length-1;i>=0;i--){\n if(ndtVideoOffsets[vis[i]]>=0&&ndtVideoOffsets[vis[i]]<=seconds){matchVi=i;break;}\n }\n if(matchVi<0){highlightIdx=-1;paint();return;}\n var newHL=vis[matchVi];\n if(newHL===highlightIdx)return;\n highlightIdx=newHL;\n var syncOpenVi=-1;\n if(openIdx>=0){var svis=getVisible();for(var si=0;si<svis.length;si++){if(svis[si]===openIdx){syncOpenVi=si;break;}}}\n var rowTop=matchVi*NDT_ROW_H+(syncOpenVi>=0&&matchVi>syncOpenVi?lastDetailH:0);\n var vh=list.clientHeight;\n if(rowTop<list.scrollTop||rowTop>list.scrollTop+vh-NDT_ROW_H){\n list.scrollTop=rowTop-vh/2;\n }\n paint();\n };\n }\n\n paint();\n },0);\n return h;\n }\n\n function renderTimelineColumn(steps,networkRequests,consoleLogs,testType,hasVideo,baseTime,pageMeta){\n if(!steps||steps.length===0)return '<div class=\"artifact-empty\">No navigation data</div>';\n var hasReqs=networkRequests&&networkRequests.length>0;\n var hasCLogs=consoleLogs&&consoleLogs.length>0;\n var totalNet=pageMeta&&pageMeta.total?pageMeta.total:(hasReqs?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 if(hasCLogs)h+='<button class=\"seg-btn\" data-seg=\"console\"><svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"4 17 10 11 4 5\"/><line x1=\"12\" y1=\"19\" x2=\"20\" y2=\"19\"/></svg>Console ('+consoleLogs.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'+(totalNet?' ('+totalNet+')':'')+'</button>';\n h+='</div>';\n h+='<div class=\"seg-pane active\" data-seg-pane=\"timeline\">'+renderNavTimeline(steps)+'</div>';\n if(hasCLogs)h+='<div class=\"seg-pane\" data-seg-pane=\"console\">'+renderConsoleColumn(consoleLogs,testType,hasVideo,baseTime)+'</div>';\n h+='<div class=\"seg-pane\" data-seg-pane=\"network\">'+renderNetworkDevTools(networkRequests,hasVideo,baseTime,pageMeta)+'</div>';\n return h;\n }\n\n function renderFailure(failure,filePath,testName,tags){\n if(!failure)return '';\n var sid='s-'+Math.random().toString(36).substr(2,8);\n var cpid='cp-'+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<button class=\"copy-prompt-btn\" id=\"'+cpid+'\"><svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\"/><path d=\"M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1\"/></svg>Copy Prompt</button></div>';\n /* Split the call log off the message. It is the longest part by far and\n almost never what you want first — leaving it expanded is what pushed\n the visual comparison two screens down. */\n var fullMsg=stripAnsi(failure.message||'');\n var logAt=fullMsg.indexOf('Call log:');\n var headMsg=logAt>=0?fullMsg.slice(0,logAt).trim():fullMsg;\n var callLog=logAt>=0?fullMsg.slice(logAt).trim():'';\n h+='<div class=\"failure-message\">'+esc(headMsg)+'</div>';\n if(callLog){\n var lid='cl-'+Math.random().toString(36).substr(2,8);\n var lines=callLog.split(String.fromCharCode(10)).length-1;\n h+='<button class=\"stack-toggle-btn\" data-stack=\"'+lid+'\">Show call log ('+lines+' steps)</button>';\n h+='<div class=\"stack-trace\" id=\"'+lid+'\">'+esc(callLog)+'</div>';\n }\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>';\n /* Wire up Copy Prompt button after render */\n setTimeout(function(){\n var btn=document.getElementById(cpid);\n if(!btn)return;\n btn.addEventListener('click',function(e){\n e.stopPropagation();\n var nl=String.fromCharCode(10);\n var ticks=String.fromCharCode(96,96,96);\n var msg=failure.message?stripAnsi(failure.message):'Unknown error';\n var tname=testName||'Unknown test';\n var tline=(failure.line!==null&&failure.line!==undefined)?String(failure.line):'unknown';\n var ttags=(tags&&tags.length>0)?tags.join(', '):'none';\n var fp=filePath||'unknown';\n /* Escape regex special chars in test name for --grep */\n function escGrep(s){return s.replace(/[.*+?^]/g,'\\\\\\\\$&').replace(/[()]/g,'\\\\\\\\$&');}\n var tnameEsc=escGrep(tname);\n var p='You are a Playwright test repair agent. A test in this codebase has failed. Your job is to fix the code and verify the fix passes.'+nl+nl;\n p+='## Failed Test'+nl+nl;\n p+='- **File:** '+fp+nl;\n p+='- **Test Name:** '+tname+nl;\n p+='- **Line:** '+tline+nl;\n p+='- **Tags:** '+ttags+nl+nl;\n p+='## Error'+nl+nl;\n p+=msg+nl+nl;\n if(failure.code){\n p+='## Failing Code'+nl+nl;\n p+=ticks+'typescript'+nl+stripAnsi(failure.code)+nl+ticks+nl+nl;\n }\n if(failure.stack){\n p+='## Stack Trace'+nl+nl;\n p+=ticks+nl+stripAnsi(failure.stack)+nl+ticks+nl+nl;\n }\n p+='## Your Task'+nl+nl;\n p+='1. Analyze the error and identify the root cause'+nl;\n p+='2. Edit '+ticks+fp+ticks+' to fix the failing assertion or logic '+String.fromCharCode(8212)+' do not delete the test'+nl;\n p+='3. Run the specific test to verify the fix: '+ticks+'npx playwright test '+fp+' --grep \"'+tnameEsc+'\" --reporter=line'+ticks+nl;\n p+='4. If the test passes, run the full file to confirm no regressions: '+ticks+'npx playwright test '+fp+' --reporter=line'+ticks+nl;\n p+='5. Stop only when the test exits with status '+ticks+'passed'+ticks+nl+nl;\n p+='## Constraints'+nl+nl;\n p+='- Only modify '+ticks+fp+ticks+nl;\n p+='- Do not alter shared fixtures, helpers, or '+ticks+'playwright.config.ts'+ticks+nl;\n p+='- Do not skip or remove the test'+nl;\n navigator.clipboard.writeText(p).then(function(){\n btn.innerHTML='<svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M20 6L9 17l-5-5\"/></svg>Copied!';\n btn.classList.add('copied');\n setTimeout(function(){btn.innerHTML='<svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\"/><path d=\"M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1\"/></svg>Copy Prompt';btn.classList.remove('copied');},2000);\n });\n });\n },0);\n 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 /* Response size, derived. ApiCallRecord has no size field — the body is\n what we have, so measure it and say so rather than render an empty cell. */\n function apiBodyBytes(ac){\n var b=ac.responseBody;\n if(b===null||b===undefined)return null;\n var s=(typeof b==='string')?b:JSON.stringify(b);\n if(!s)return 0;\n /* base64 bodies decode to 3 bytes per 4 characters. */\n if(ac.isBinary)return Math.floor(s.length*3/4);\n return s.length;\n }\n\n function renderCookieTable(cookies,emptyLabel){\n if(!cookies||cookies.length===0)return '<div class=\"ndt-body-empty\">'+esc(emptyLabel)+'</div>';\n var h='<div class=\"ck-tbl\">';\n for(var i=0;i<cookies.length;i++){\n var c=cookies[i];\n h+='<span class=\"ck-k\">'+esc(c.name)+'</span>';\n h+='<span class=\"ck-v\">'+esc(c.value);\n if(c.attributes)h+=' <span class=\"ck-attr\">'+esc(c.attributes)+'</span>';\n h+='</span>';\n }\n return h+'</div>';\n }\n\n /* The assertions made against one call. */\n function assertionsForCall(assertions,callId){\n var out=[];\n if(!assertions||!callId)return out;\n for(var i=0;i<assertions.length;i++){\n if(assertions[i].callId===callId)out.push(assertions[i]);\n }\n return out;\n }\n\n function renderApiCallDetail(ac,idx,callAsserts){\n var did='ndt-d-api-'+idx;\n callAsserts=callAsserts||[];\n var reqHeaders=ac.requestHeaders||null;\n var reqBody=ac.requestBody||null;\n var resHeaders=ac.responseHeaders||null;\n var resBody=ac.responseBody||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 var hasCookies=(ac.requestCookies&&ac.requestCookies.length)||(ac.responseCookies&&ac.responseCookies.length);\n if(hasCookies)h+='<button class=\"ndt-detail-tab\" data-ndt-tab=\"cookies\" data-ndt-target=\"'+did+'\">Cookies</button>';\n if(callAsserts.length>0)h+='<button class=\"ndt-detail-tab\" data-ndt-tab=\"asserts\" data-ndt-target=\"'+did+'\">Assertions <span style=\"opacity:.6\">'+callAsserts.length+'</span></button>';\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.responseStatusCode!=null?ac.responseStatusCode:0;\n var st=ac.responseStatusText||'';\n if(ac.error){\n /* The call never got a response. Rendering \"0\" here read as a status. */\n h+='<span class=\"ndt-general-k\">Result</span><span class=\"ndt-general-v ndt-err-note\">Request failed &mdash; '+esc(String(ac.error))+'</span>';\n }else{\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 }\n h+='<span class=\"ndt-general-k\">Response Time</span><span class=\"ndt-general-v\">'+(ac.responseTimeMs!=null?fmtDur(ac.responseTimeMs):'N/A')+'</span>';\n var _bz=apiBodyBytes(ac);\n if(_bz!==null)h+='<span class=\"ndt-general-k\">Response Size</span><span class=\"ndt-general-v\">'+fmtBytes(_bz)+(ac.isBinary?' (binary)':'')+'</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','Request headers not available &mdash; only explicitly passed headers are captured for API tests')+'</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,ac.isBinary,ct,ac.error?'No response \\u2014 the request failed':'No response body captured')+'</div>';\n if(hasCookies){\n h+='<div class=\"ndt-detail-pane\" data-ndt-pane=\"cookies\">';\n h+='<div class=\"ndt-headers-label\">Request</div>'+renderCookieTable(ac.requestCookies,'No cookies sent');\n h+='<div class=\"ndt-headers-label\">Response</div>'+renderCookieTable(ac.responseCookies,'No cookies set');\n h+='</div>';\n }\n if(callAsserts.length>0){\n h+='<div class=\"ndt-detail-pane\" data-ndt-pane=\"asserts\">'+renderCallAssertions(callAsserts)+'</div>';\n }\n h+='</div>';\n return h;\n }\n\n /* The assertions on one call, inside that call's detail pane. The run-level\n section above lists them all; this answers \"what did this call fail?\". */\n function renderCallAssertions(list){\n var h='<div class=\"asec\">';\n for(var i=0;i<list.length;i++){\n var a=list[i],failed=a.status==='failed';\n h+='<div class=\"arow-item'+(failed?' is-failed':'')+'\">';\n h+=failed\n ? '<svg style=\"flex-shrink:0\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#ef4444\" stroke-width=\"2.5\"><path d=\"M18 6L6 18M6 6l12 12\"/></svg>'\n : '<svg style=\"flex-shrink:0\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#22c55e\" stroke-width=\"2.5\"><path d=\"M20 6L9 17l-5-5\"/></svg>';\n h+='<span class=\"atype\">'+esc(String(a.type||'custom'))+'</span>';\n h+='<span class=\"aexpr\">'+esc(typeof assertionExpression==='function'?assertionExpression(a):(a.expression||''))+'</span>';\n h+='</div>';\n if(failed&&typeof fmtAssertValue==='function'){\n h+='<div class=\"adiff\">';\n h+='<span class=\"adiff-i\"><span class=\"adiff-k\">expected</span><span class=\"adiff-v adiff-exp\">'+esc(fmtAssertValue(a.expected))+'</span></span>';\n h+='<span class=\"adiff-i\"><span class=\"adiff-k\">received</span><span class=\"adiff-v adiff-act\">'+esc(fmtAssertValue(a.actual))+'</span></span>';\n h+='</div>';\n }\n }\n return h+'</div>';\n }\n\n function renderApiCallsColumn(apiCalls,assertions){\n if(!apiCalls||apiCalls.length===0)return '<div class=\"artifact-empty\">No API calls captured</div>';\n assertions=assertions||[];\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.responseStatusCode!=null?ac.responseStatusCode:0;\n var isFail=sc>=400||sc===0||!!ac.error;\n var callAsserts=assertionsForCall(assertions,ac.id||ac.callId);\n var aFail=0;\n for(var aj=0;aj<callAsserts.length;aj++)if(callAsserts[aj].status==='failed')aFail++;\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)+'\" title=\"'+(ac.error?esc(String(ac.error)):'')+'\">'+(ac.error?'ERR':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 /* Was: two permanently empty cells. Now the two things a reader of an\n API row actually wants — did its assertions hold, and how big was it. */\n if(callAsserts.length===0){\n h+='<span class=\"ndt-type\"><span class=\"ndt-assert ndt-assert-none\">&mdash;</span></span>';\n }else if(aFail>0){\n h+='<span class=\"ndt-type\"><span class=\"ndt-assert ndt-assert-bad\">'+aFail+' \\u2717</span></span>';\n }else{\n h+='<span class=\"ndt-type\"><span class=\"ndt-assert ndt-assert-ok\">'+callAsserts.length+' \\u2713</span></span>';\n }\n var bz=apiBodyBytes(ac);\n h+='<span class=\"ndt-size\">'+(bz===null?'':fmtBytes(bz))+'</span>';\n h+='<span class=\"ndt-time\">'+(ac.responseTimeMs!=null?fmtDur(ac.responseTimeMs):'')+'</span>';\n h+='</div>';\n h+=renderApiCallDetail(ac,i,callAsserts);\n }\n h+='</div></div>';\n return h;\n }\n\n function ndtApplyFilters(ndt,filterType,searchVal){\n /* Use virtual filter handler if available */\n if(ndt.__ndtFilter){\n var searchInput=ndt.querySelector('.ndt-search');\n var q=(searchVal!==null&&searchVal!==undefined)?searchVal:(searchInput?searchInput.value:'');\n ndt.__ndtFilter(filterType,q);\n return;\n }\n /* Fallback for non-virtual lists (API calls panel) */\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 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:{},visual:{},evidence:{}};\n\n/* Evidence filters: one predicate per chip, so API and mobile runs share the\n mechanism and a chip only appears when something in the run matches it. */\nvar EVIDENCE_FILTERS=[\n {v:'api-error',label:'4xx/5xx seen',test:function(t){\n var m=t.apiStatusMix;\n if(!m)return false;\n return (m['4xx']||0)+(m['5xx']||0)+(m['error']||0)>0;\n }},\n {v:'assert-failed',label:'Failed assertion',test:function(t){return (t.assertionsFailed||0)>0;}},\n {v:'crash',label:'Crash logged',test:function(t){return !!t.hasCrashLog;}},\n {v:'device-error',label:'Device log errors',test:function(t){return (t.deviceLogErrors||0)>0;}},\n {v:'video',label:'Has recording',test:function(t){return !!t.hasVideo||!!(t.artifacts&&t.artifacts.video);}},\n {v:'ai-defect',label:'AI defects',test:function(t){return (t.aiDefectCount||0)>0;}}\n];\n\nfunction _evidenceCounts(){\n var counts={};\n for(var f=0;f<EVIDENCE_FILTERS.length;f++)counts[EVIDENCE_FILTERS[f].v]=0;\n for(var i=0;i<tests.length;i++){\n for(var j=0;j<EVIDENCE_FILTERS.length;j++){\n if(EVIDENCE_FILTERS[j].test(tests[i]))counts[EVIDENCE_FILTERS[j].v]++;\n }\n }\n return counts;\n}\n\n/* Visual counts reach the grid differently in the two report modes: the\n embedded report inlines the comparisons on each test, while the streaming\n index carries only counts so the grid never has to open a detail file.\n Both are read here so the filter works the same either way. */\nfunction _visCount(t){return t.visual?t.visual.length:(t.visualCount||0);}\nfunction _visFailed(t){\n if(t.visual){var n=0;for(var i=0;i<t.visual.length;i++)if(t.visual[i].status==='failed')n++;return n;}\n return t.visualFailedCount||0;\n}\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 var visualCounts={has:0,failed:0};\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 if(_visCount(t)>0){visualCounts.has++;if(_visFailed(t)>0)visualCounts.failed++;}\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 hasVisual=Object.keys(_filterState.visual).length>0;\n var hasEvidenceSel=Object.keys(_filterState.evidence).length>0;\n var hasAny=hasStatus||hasType||hasSpec||hasVisual||hasEvidenceSel||!!searchQuery;\n var hasAdvanced=hasType||hasSpec||hasVisual||hasEvidenceSel;\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 /* Visual section — only when the run actually produced comparisons */\n if(visualCounts.has>0){\n d+='<div class=\"filter-drawer-section\"><span class=\"filter-drawer-section-label\">Visual</span>';\n d+='<div class=\"filter-chips\">';\n var visOpts=[{v:'has',label:'Has visual',cnt:visualCounts.has},{v:'failed',label:'Visual failed',cnt:visualCounts.failed}];\n for(var vi=0;vi<visOpts.length;vi++){\n var vo=visOpts[vi];\n var vcls='filter-chip'+(!!_filterState.visual[vo.v]?' active':'')+(vo.cnt===0?' filter-chip--dimmed':'');\n d+='<button class=\"'+vcls+'\" data-dimension=\"visual\" data-value=\"'+vo.v+'\">'+vo.label+' <span class=\"chip-count\">'+vo.cnt+'</span></button>';\n }\n d+='</div></div>';\n }\n\n /* Evidence section — a chip per predicate that anything in the run meets */\n var evCounts=_evidenceCounts();\n var evAny=false;\n for(var ek in evCounts){if(evCounts.hasOwnProperty(ek)&&evCounts[ek]>0)evAny=true;}\n if(evAny){\n d+='<div class=\"filter-drawer-section\"><span class=\"filter-drawer-section-label\">Evidence</span>';\n d+='<div class=\"filter-chips\">';\n for(var ei=0;ei<EVIDENCE_FILTERS.length;ei++){\n var ef=EVIDENCE_FILTERS[ei],ecnt=evCounts[ef.v];\n if(ecnt===0)continue;\n var ecls='filter-chip'+(!!_filterState.evidence[ef.v]?' active':'');\n d+='<button class=\"'+ecls+'\" data-dimension=\"evidence\" data-value=\"'+ef.v+'\">'+ef.label+' <span class=\"chip-count\">'+ecnt+'</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:{},visual:{},evidence:{}};\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 hasVisual=Object.keys(_filterState.visual).length>0;\n var hasEvidence=Object.keys(_filterState.evidence).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(hasVisual){\n /* OR within the dimension, matching every other filter here. */\n var visOk=(_filterState.visual.has&&_visCount(t)>0)||(_filterState.visual.failed&&_visFailed(t)>0);\n if(!visOk)continue;\n }\n if(hasEvidence){\n var evOk=false;\n for(var ef2=0;ef2<EVIDENCE_FILTERS.length;ef2++){\n var cand=EVIDENCE_FILTERS[ef2];\n if(_filterState.evidence[cand.v]&&cand.test(t)){evOk=true;break;}\n }\n if(!evOk)continue;\n }\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 for(var dim in _filterState){if(_filterState.hasOwnProperty(dim))_filterState[dim]={};}\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){openTest(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 /* Virtual lists handle their own click events */\n var vlist=ndtRow.closest('.ndt-vlist');\n if(vlist)return;\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 /* Console log level filter */\n var consoleFilterBtn=target.closest('.console-filter-btn');\n if(consoleFilterBtn){\n var toolbar=consoleFilterBtn.closest('.console-toolbar');\n if(toolbar){\n var btns=toolbar.querySelectorAll('.console-filter-btn');\n for(var bi=0;bi<btns.length;bi++)btns[bi].classList.remove('active');\n consoleFilterBtn.classList.add('active');\n var filterLevel=consoleFilterBtn.getAttribute('data-console-filter');\n var panel=consoleFilterBtn.closest('.console-panel');\n /* Use virtual filter if available */\n if(panel&&panel.__conFilter){panel.__conFilter(filterLevel);return;}\n var list=panel?panel.querySelector('.console-list'):null;\n if(list){\n var entries=list.querySelectorAll('.console-entry');\n for(var ei=0;ei<entries.length;ei++){\n if(filterLevel==='all'){entries[ei].style.display='';}\n else{entries[ei].style.display=entries[ei].getAttribute('data-console-level')===filterLevel?'':'none';}\n }\n }\n }\n return;\n }\n\n /* Action step click → seek video + highlight */\n var actionStep=target.closest('.action-step.seekable');\n if(actionStep){\n var offset=parseFloat(actionStep.getAttribute('data-offset'));\n if(!isNaN(offset)){\n var video=document.querySelector('.drawer-body video');\n if(video){video.currentTime=offset;video.play();}\n /* Clear previous active and highlight clicked */\n var prev=document.querySelectorAll('.action-active');\n for(var ai=0;ai<prev.length;ai++)prev[ai].classList.remove('action-active');\n actionStep.classList.add('action-active');\n actionStep.classList.add('seeking');\n setTimeout(function(){actionStep.classList.remove('seeking');},400);\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('#detail-back')){closeTest();return;}\n if(target.closest('#detail-prev')){stepTest(-1);return;}\n if(target.closest('#detail-next')){stepTest(1);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();closeTest();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,'&quot;')+'\" alt=\"Screenshot\">';\n var b=document.createElement('button');b.className='lightbox-close';b.innerHTML='&times;';\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 /* ── Video ↔ All Panels Sync ── */\n var _videoSyncCleanup=null;\n function syncActionsWithVideo(){\n /* Clean up previous listener */\n if(_videoSyncCleanup){_videoSyncCleanup();_videoSyncCleanup=null;}\n var video=document.querySelector('.drawer-body video');\n if(!video)return;\n\n /* Action steps sync */\n var steps=document.querySelectorAll('.action-step.seekable');\n var offsets=[];\n for(var i=0;i<steps.length;i++){\n var o=parseFloat(steps[i].getAttribute('data-offset'));\n if(!isNaN(o))offsets.push({el:steps[i],offset:o});\n }\n offsets.sort(function(a,b){return a.offset-b.offset;});\n\n /* Collect console and network panels with video sync */\n var consolePanels=document.querySelectorAll('.console-panel');\n var ndtPanels=document.querySelectorAll('.ndt');\n\n var lastActive=null;\n function onTimeUpdate(){\n var ct=video.currentTime;\n\n /* Sync action steps */\n if(offsets.length){\n var match=null;\n for(var i=offsets.length-1;i>=0;i--){\n if(offsets[i].offset<=ct){match=offsets[i].el;break;}\n }\n if(match!==lastActive){\n if(lastActive)lastActive.classList.remove('action-active');\n if(match){\n match.classList.add('action-active');\n match.scrollIntoView({block:'nearest',behavior:'smooth'});\n }\n lastActive=match;\n }\n }\n\n /* Sync console panels */\n for(var ci=0;ci<consolePanels.length;ci++){\n if(consolePanels[ci].__videoSync)consolePanels[ci].__videoSync(ct);\n }\n\n /* Sync network panels */\n for(var ni=0;ni<ndtPanels.length;ni++){\n if(ndtPanels[ni].__videoSync)ndtPanels[ni].__videoSync(ct);\n }\n }\n video.addEventListener('timeupdate',onTimeUpdate);\n video.addEventListener('seeked',onTimeUpdate);\n _videoSyncCleanup=function(){\n video.removeEventListener('timeupdate',onTimeUpdate);\n video.removeEventListener('seeked',onTimeUpdate);\n if(lastActive)lastActive.classList.remove('action-active');\n };\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 * Client-side JavaScript: Action Steps Rendering.\n * Renders the chronological action steps timeline in the drawer,\n * with category badges, duration, video seek, and nested step support.\n */\n\nexport const JS_ACTIONS = `\n /* ── Category badge helpers ── */\n function actionBadgeClass(cat){\n if(cat==='assertion')return 'action-badge-assertion';\n if(cat==='custom_step')return 'action-badge-custom';\n return 'action-badge-ui';\n }\n function actionBadgeLabel(cat){\n if(cat==='assertion')return 'assert';\n if(cat==='custom_step')return 'step';\n return 'action';\n }\n\n /* ── Render a single action step ── */\n function renderActionItem(a,hasVideo,depth){\n var h='<div class=\"action-step'+(a.status==='failed'?' action-failed':'')+(hasVideo&&a.videoOffset!==null?' seekable':'')+'\" data-offset=\"'+(a.videoOffset!==null?a.videoOffset:'')+'\" style=\"padding-left:'+(12+depth*16)+'px\">';\n h+='<span class=\"action-badge '+actionBadgeClass(a.category)+(a.status==='failed'?' failed':'')+'\">'+actionBadgeLabel(a.category)+'</span>';\n h+='<span class=\"action-title\" title=\"'+esc(a.title)+'\">'+esc(a.title)+'</span>';\n h+='<span class=\"action-duration\">'+fmtDur(a.duration)+'</span>';\n if(hasVideo&&a.videoOffset!==null){\n h+='<span class=\"action-offset\">@ '+a.videoOffset.toFixed(1)+'s</span>';\n }\n h+='</div>';\n if(a.status==='failed'&&a.error){\n h+='<div class=\"action-error\" style=\"padding-left:'+(28+depth*16)+'px\">'+esc(a.error)+'</div>';\n }\n if(a.children&&a.children.length>0){\n for(var c=0;c<a.children.length;c++){\n h+=renderActionItem(a.children[c],hasVideo,depth+1);\n }\n }\n return h;\n }\n\n /* ── Render the full actions column ── */\n function renderActionsColumn(actions,hasVideo){\n if(!actions||actions.length===0)return '';\n var h='<div class=\"actions-header\"><svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M12 20V10M18 20V4M6 20v-4\"/></svg>Actions <span class=\"actions-count\">'+actions.length+'</span></div>';\n h+='<div class=\"actions-timeline\">';\n for(var i=0;i<actions.length;i++){\n if(i>0)h+='<div class=\"action-connector\"></div>';\n h+=renderActionItem(actions[i],hasVideo,0);\n }\n h+='</div>';\n return h;\n }\n`;\n","/**\n * Client-side JavaScript: Console/Log Rendering.\n * Renders browser console logs (E2E) and terminal logs (API) in the drawer.\n */\n\nexport const JS_CONSOLE = `\n /* ── Console Log Level Colors & Labels ── */\n var LOG_COLORS={log:'#9ca3af',warn:'#fbbf24',error:'#f87171',info:'#60a5fa',debug:'#a78bfa',stdout:'#9ca3af',stderr:'#f87171'};\n var LOG_LABELS={log:'LOG',warn:'WARN',error:'ERR',info:'INFO',debug:'DBG',stdout:'OUT',stderr:'ERR'};\n\n function consoleVideoOffset(entry,baseTime){\n if(!baseTime||!entry.timestamp)return -1;\n var t=Date.parse(entry.timestamp);\n if(isNaN(t))return -1;\n var off=(t-baseTime)/1000;\n return off>=0?off:-1;\n }\n\n function renderConsoleLogEntry(entry,hasVideo,baseTime){\n var off=hasVideo?consoleVideoOffset(entry,baseTime):-1;\n var seekable=off>=0;\n var h='<div class=\"console-entry console-level-'+esc(entry.level)+(seekable?' seekable':'')+'\" data-console-level=\"'+esc(entry.level)+'\"'+(seekable?' data-offset=\"'+off.toFixed(2)+'\"':'')+'>';\n h+='<span class=\"console-level-badge\" style=\"color:'+LOG_COLORS[entry.level]+'\">'+LOG_LABELS[entry.level]+'</span>';\n if(seekable){h+='<span class=\"console-offset\">'+off.toFixed(1)+'s</span>';}\n else{h+='<span class=\"console-time\">'+fmtDate(entry.timestamp).split(', ').pop()+'</span>';}\n h+='<span class=\"console-text\">'+esc(stripAnsi(entry.text))+'</span>';\n if(entry.location){var short=entry.location.split('/').pop()||entry.location;h+='<span class=\"console-loc\" title=\"'+esc(entry.location)+'\">'+esc(short)+'</span>';}\n h+='</div>';\n return h;\n }\n\n var CON_ROW_H=24;\n var CON_VBUF=40;\n\n function renderConsoleColumn(logs,testType,hasVideo,baseTime){\n if(!logs||logs.length===0)return '';\n var isApi=testType==='api';\n var label=isApi?'Terminal Logs':'Console';\n\n var counts={};\n for(var i=0;i<logs.length;i++){var l=logs[i].level;counts[l]=(counts[l]||0)+1;}\n\n var uid='clog-'+Math.random().toString(36).substr(2,6);\n var h='<div class=\"console-panel\" id=\"'+uid+'\">';\n h+='<div class=\"console-header\">';\n h+='<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"4 17 10 11 4 5\"/><line x1=\"12\" y1=\"19\" x2=\"20\" y2=\"19\"/></svg>';\n h+=' '+label+' <span class=\"console-count\">'+logs.length+'</span>';\n if(counts.error||counts.stderr){var ec=(counts.error||0)+(counts.stderr||0);h+=' <span class=\"console-error-count\">'+ec+' error'+(ec>1?'s':'')+'</span>';}\n if(counts.warn)h+=' <span class=\"console-warn-count\">'+counts.warn+' warning'+(counts.warn>1?'s':'')+'</span>';\n h+='</div>';\n\n h+='<div class=\"console-toolbar\">';\n h+='<button class=\"console-filter-btn active\" data-console-filter=\"all\">All</button>';\n var levels=isApi?['stdout','stderr']:['log','info','warn','error','debug'];\n for(var li=0;li<levels.length;li++){\n var lv=levels[li];\n if(counts[lv])h+='<button class=\"console-filter-btn\" data-console-filter=\"'+lv+'\">'+LOG_LABELS[lv]+' <span style=\"opacity:.6\">'+counts[lv]+'</span></button>';\n }\n h+='</div>';\n\n /* Pre-compute video offsets for all entries */\n var videoOffsets=[];\n if(hasVideo&&baseTime){\n for(var oi=0;oi<logs.length;oi++){videoOffsets.push(consoleVideoOffset(logs[oi],baseTime));}\n }\n\n /* Use virtualization for large log lists */\n if(logs.length>200){\n h+='<div class=\"console-list console-vlist\" style=\"overflow-y:auto;max-height:400px;position:relative\">';\n h+='<div class=\"console-vspacer\" style=\"height:'+(logs.length*CON_ROW_H)+'px;position:relative\"></div>';\n h+='</div>';\n h+='</div>';\n\n setTimeout(function(){\n var panel=document.getElementById(uid);if(!panel)return;\n var list=panel.querySelector('.console-vlist');if(!list)return;\n var spacer=list.querySelector('.console-vspacer');\n var allLogs=logs;\n var filteredIdxs=null;\n var highlightIdx=-1;\n\n function getVis(){return filteredIdxs||allLogs.map(function(_,i){return i});}\n\n function paint(){\n var vis=getVis();\n spacer.style.height=(vis.length*CON_ROW_H)+'px';\n var st=list.scrollTop;var vh=list.clientHeight;\n var s0=Math.max(0,Math.floor(st/CON_ROW_H)-CON_VBUF);\n var s1=Math.min(vis.length,Math.ceil((st+vh)/CON_ROW_H)+CON_VBUF);\n var out='';\n for(var vi=s0;vi<s1;vi++){\n var ri=vis[vi];var entry=allLogs[ri];\n var off=videoOffsets[ri];\n var seekable=off>=0;\n var isActive=ri===highlightIdx;\n out+='<div class=\"console-entry console-level-'+esc(entry.level)+(seekable?' seekable':'')+(isActive?' console-active':'')+'\" data-console-level=\"'+esc(entry.level)+'\"'+(seekable?' data-offset=\"'+off.toFixed(2)+'\"':'')+' data-vidx=\"'+ri+'\" style=\"position:absolute;top:'+(vi*CON_ROW_H)+'px;left:0;right:0;height:'+CON_ROW_H+'px\">';\n out+='<span class=\"console-level-badge\" style=\"color:'+LOG_COLORS[entry.level]+'\">'+LOG_LABELS[entry.level]+'</span>';\n if(seekable){out+='<span class=\"console-offset\">'+off.toFixed(1)+'s</span>';}\n else{out+='<span class=\"console-time\">'+fmtDate(entry.timestamp).split(', ').pop()+'</span>';}\n out+='<span class=\"console-text\">'+esc(stripAnsi(entry.text))+'</span>';\n if(entry.location){var short=entry.location.split('/').pop()||entry.location;out+='<span class=\"console-loc\" title=\"'+esc(entry.location)+'\">'+esc(short)+'</span>';}\n out+='</div>';\n }\n spacer.innerHTML=out;\n }\n\n var rafP=false;\n list.addEventListener('scroll',function(){if(!rafP){rafP=true;requestAnimationFrame(function(){rafP=false;paint();})}},{passive:true});\n\n /* Click-to-seek for virtual entries */\n list.addEventListener('click',function(e){\n var entry=e.target.closest('.console-entry.seekable');\n if(!entry)return;\n var off=parseFloat(entry.getAttribute('data-offset'));\n if(isNaN(off))return;\n var video=document.querySelector('.drawer-body video');\n if(video){video.currentTime=off;video.play();}\n });\n\n /* Store filter handler */\n panel.__conFilter=function(level){\n if(!level||level==='all'){filteredIdxs=null;}\n else{filteredIdxs=[];for(var i=0;i<allLogs.length;i++){if(allLogs[i].level===level)filteredIdxs.push(i);}}\n list.scrollTop=0;paint();\n };\n\n /* Video sync: scroll to matching entry by time */\n panel.__videoSync=function(seconds){\n if(!videoOffsets.length)return;\n var vis=getVis();\n var matchVi=-1;\n for(var i=vis.length-1;i>=0;i--){\n if(videoOffsets[vis[i]]>=0&&videoOffsets[vis[i]]<=seconds){matchVi=i;break;}\n }\n if(matchVi<0){highlightIdx=-1;paint();return;}\n var newHL=vis[matchVi];\n if(newHL===highlightIdx)return;\n highlightIdx=newHL;\n /* Scroll to row */\n var rowTop=matchVi*CON_ROW_H;\n var vh=list.clientHeight;\n if(rowTop<list.scrollTop||rowTop>list.scrollTop+vh-CON_ROW_H){\n list.scrollTop=rowTop-vh/2;\n }\n paint();\n };\n\n paint();\n },0);\n }else{\n h+='<div class=\"console-list\">';\n for(var i=0;i<logs.length;i++){h+=renderConsoleLogEntry(logs[i],hasVideo,baseTime);}\n h+='</div>';\n h+='</div>';\n\n /* Wire up click-to-seek for non-virtual entries */\n if(hasVideo&&baseTime){\n setTimeout(function(){\n var panel=document.getElementById(uid);if(!panel)return;\n panel.addEventListener('click',function(e){\n var entry=e.target.closest('.console-entry.seekable');\n if(!entry)return;\n var off=parseFloat(entry.getAttribute('data-offset'));\n if(isNaN(off))return;\n var video=document.querySelector('.drawer-body video');\n if(video){video.currentTime=off;video.play();}\n });\n /* Video sync for non-virtual */\n var entries=panel.querySelectorAll('.console-entry.seekable');\n var offsets=[];\n for(var i=0;i<entries.length;i++){\n var off=parseFloat(entries[i].getAttribute('data-offset'));\n if(!isNaN(off))offsets.push({el:entries[i],offset:off});\n }\n if(offsets.length){\n var lastHL=null;\n panel.__videoSync=function(seconds){\n var match=null;\n for(var i=offsets.length-1;i>=0;i--){\n if(offsets[i].offset<=seconds){match=offsets[i].el;break;}\n }\n if(match===lastHL)return;\n if(lastHL)lastHL.classList.remove('console-active');\n if(match){match.classList.add('console-active');match.scrollIntoView({block:'nearest',behavior:'smooth'});}\n lastHL=match;\n };\n }\n },0);\n }\n }\n return h;\n }\n`;\n","/**\n * Client-side JavaScript: Artifact utilities\n *\n * Provides formatBytes utility used by other report modules.\n */\n\nexport const JS_ARTIFACTS = `\n /* ── Artifact Utilities ── */\n function formatBytes(bytes){\n if(bytes===0)return '0 B';\n var k=1024;var sizes=['B','KB','MB','GB'];\n var i=Math.floor(Math.log(bytes)/Math.log(k));\n if(i>=sizes.length)i=sizes.length-1;\n return parseFloat((bytes/Math.pow(k,i)).toFixed(1))+' '+sizes[i];\n }\n`;\n","/**\n * Client-side JavaScript: visual regression section.\n *\n * Renders one card per snapshot comparison, with up to four ways of looking at\n * the same pair of images: side by side, a wipe slider, an onion-skin blend and\n * Playwright's own diff. Which modes appear depends on which images exist — a\n * passing comparison has only a baseline, and a failing one has all four.\n *\n * The markup reuses the report's existing `.seg-ctrl` / `.seg-btn` / `.seg-pane`\n * convention, so the delegated pane-switch handler in `html-js-interactions`\n * drives the tabs with no changes there. Only the two range inputs need a\n * listener of their own, registered at the bottom of this module.\n *\n * ES5-compatible vanilla JS — it is concatenated into the report's single\n * inline script, under a CSP that allows no external resources.\n */\n\nexport const JS_VISUAL = `\n /* ── Visual Regression ── */\n\n function visStatusLabel(status){\n if(status==='new')return 'new baseline';\n if(status==='updated')return 'baseline updated';\n return status;\n }\n\n function visMetricsText(v){\n var parts=[];\n if(typeof v.diffPixels==='number')parts.push(v.diffPixels.toLocaleString()+' px');\n if(typeof v.diffPixelRatio==='number')parts.push((v.diffPixelRatio*100).toFixed(2)+'%');\n if(v.width&&v.height){\n var dims=v.width+'x'+v.height;\n /* Call out a size change explicitly: a comparison can fail purely\n because the render changed shape, and the pixel count alone reads\n like a content change when that happens. */\n if(v.actualWidth&&v.actualHeight&&(v.actualWidth!==v.width||v.actualHeight!==v.height))\n dims+=' -> '+v.actualWidth+'x'+v.actualHeight;\n parts.push(dims);\n }\n return parts.join(' \\\\u00b7 ');\n }\n\n /* Deliberately no loading=\"lazy\" on any image below. Every pane but the\n first is built inside a display:none container, and Chrome does not\n reliably start a lazy load when such an image is later revealed — the\n diff pane came up blank every time. A test has a handful of comparisons\n at most and they are only built when the drawer opens, so eager loading\n is by far the cheaper mistake. */\n function visImg(src,alt){\n return '<img class=\"artifact-col-img\" src=\"'+esc(src)+'\" alt=\"'+esc(alt)+'\">';\n }\n\n function visSideBySide(v){\n var h='<div class=\"vis-pair\">';\n if(v.baseline)h+='<div><div class=\"vis-pane-label\">Baseline</div>'+visImg(v.baseline,'Baseline')+'</div>';\n if(v.actual)h+='<div><div class=\"vis-pane-label\">This run</div>'+visImg(v.actual,'This run')+'</div>';\n if(v.previous)h+='<div><div class=\"vis-pane-label\">Previous attempt</div>'+visImg(v.previous,'Previous attempt')+'</div>';\n return h+'</div>';\n }\n\n function visSlider(v,id){\n var h='<div class=\"vis-stack\" data-vis-stack=\"'+id+'\">';\n h+='<img src=\"'+esc(v.baseline)+'\" alt=\"Baseline\">';\n h+='<img class=\"vis-top\" data-vis-wipe=\"'+id+'\" src=\"'+esc(v.actual)+'\" alt=\"This run\" style=\"clip-path:inset(0 0 0 50%)\">';\n h+='<div class=\"vis-handle\" data-vis-handle=\"'+id+'\" style=\"left:50%\"></div>';\n h+='</div>';\n h+='<div class=\"vis-range-row\"><span>Baseline</span><input type=\"range\" class=\"vis-range\" min=\"0\" max=\"100\" value=\"50\" data-vis-slider=\"'+id+'\" aria-label=\"Wipe between baseline and this run\"><span>This run</span></div>';\n return h;\n }\n\n function visOnion(v,id){\n var h='<div class=\"vis-stack\">';\n h+='<img src=\"'+esc(v.baseline)+'\" alt=\"Baseline\">';\n h+='<img class=\"vis-top\" data-vis-fade=\"'+id+'\" src=\"'+esc(v.actual)+'\" alt=\"This run\" style=\"opacity:0.5\">';\n h+='</div>';\n h+='<div class=\"vis-range-row\"><span>Baseline</span><input type=\"range\" class=\"vis-range\" min=\"0\" max=\"100\" value=\"50\" data-vis-onion=\"'+id+'\" aria-label=\"Blend between baseline and this run\"><span>This run</span></div>';\n return h;\n }\n\n function visModes(v){\n var modes=[];\n if(v.baseline&&v.actual){\n modes.push({key:'side',label:'Side by side'});\n modes.push({key:'wipe',label:'Wipe'});\n modes.push({key:'onion',label:'Onion skin'});\n }else if(v.baseline||v.actual){\n modes.push({key:'side',label:'Image'});\n }\n if(v.diff)modes.push({key:'diff',label:'Pixel diff'});\n /* Only when a DOM baseline existed to compare against — an assertion\n written with Playwright's own expect compares pixels only. */\n if(v.domChangeCount!==undefined)modes.push({key:'dom',label:'Elements'});\n return modes;\n }\n\n function visPaneBody(mode,v,id){\n if(mode==='side')return visSideBySide(v);\n if(mode==='wipe')return visSlider(v,id);\n if(mode==='onion')return visOnion(v,id);\n if(mode==='dom')return renderDomPane(v);\n return '<div class=\"vis-diff-wrap\">'+visImg(v.diff,'Pixel diff')+'</div>';\n }\n\n function renderVisualCard(v,idx){\n var id='v'+idx;\n var h='<div class=\"vis-card\">';\n h+='<div class=\"vis-head\"><span class=\"vis-name\">'+esc(v.name)+'</span>';\n h+='<span class=\"vis-badge vb-'+esc(v.status)+'\">'+esc(visStatusLabel(v.status))+'</span>';\n if(v.domChangeCount)h+='<span class=\"vis-badge vb-dom\">'+v.domChangeCount+' element'+(v.domChangeCount===1?'':'s')+'</span>';\n var metrics=visMetricsText(v);\n if(metrics)h+='<span class=\"vis-metrics\">'+esc(metrics)+'</span>';\n h+='</div>';\n\n var modes=visModes(v);\n if(modes.length===0){\n h+='<div class=\"vis-note\">No images were captured for this comparison.</div></div>';\n return h;\n }\n\n if(v.status==='new')h+='<div class=\"vis-note\">No baseline existed, so this render was written as one. Nothing was compared.</div>';\n else if(v.status==='updated')h+='<div class=\"vis-note\">Run with --update-snapshots: the baseline was rewritten rather than compared.</div>';\n\n if(modes.length>1){\n h+='<div class=\"seg-ctrl\" data-seg-group=\"visual-'+id+'\">';\n for(var m=0;m<modes.length;m++){\n h+='<button class=\"seg-btn'+(m===0?' active':'')+'\" data-seg=\"'+id+'-'+modes[m].key+'\">'+esc(modes[m].label)+'</button>';\n }\n h+='</div>';\n }\n for(var p=0;p<modes.length;p++){\n h+='<div class=\"seg-pane'+(p===0?' active':'')+'\" data-seg-pane=\"'+id+'-'+modes[p].key+'\">'+visPaneBody(modes[p].key,v,id)+'</div>';\n }\n return h+'</div>';\n }\n\n function renderVisualSection(visual){\n if(!visual||visual.length===0)return '';\n var failed=0;\n for(var f=0;f<visual.length;f++)if(visual[f].status==='failed')failed++;\n var h='<div class=\"vis-header\"><svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/><path d=\"M12 3v18\"/></svg>Visual comparisons <span class=\"vis-count\">'+visual.length+'</span>';\n /* A separator, because two bare adjacent numbers read as one. */\n if(failed>0)h+='<span class=\"vis-count vis-count-fail\">· '+failed+' failed</span>';\n h+='</div><div class=\"vis-list\">';\n for(var i=0;i<visual.length;i++)h+=renderVisualCard(visual[i],i);\n return h+'</div>';\n }\n\n /* Slider and onion-skin are driven by range inputs, which fire 'input'\n rather than 'click' — the delegated click handler cannot see them. One\n document-level listener covers every card, including cards rendered into\n the drawer long after this runs. */\n document.addEventListener('input',function(e){\n var el=e.target;\n if(!el||!el.getAttribute)return;\n\n var wipeId=el.getAttribute('data-vis-slider');\n if(wipeId){\n var card=el.closest('.vis-card');\n if(!card)return;\n var top=card.querySelector('[data-vis-wipe=\"'+wipeId+'\"]');\n var handle=card.querySelector('[data-vis-handle=\"'+wipeId+'\"]');\n if(top)top.style.clipPath='inset(0 0 0 '+el.value+'%)';\n if(handle)handle.style.left=el.value+'%';\n return;\n }\n\n var fadeId=el.getAttribute('data-vis-onion');\n if(fadeId){\n var ocard=el.closest('.vis-card');\n if(!ocard)return;\n var otop=ocard.querySelector('[data-vis-fade=\"'+fadeId+'\"]');\n if(otop)otop.style.opacity=String(el.value/100);\n }\n });\n`;\n","/**\n * Client-side JavaScript: the Elements pane.\n *\n * Lists the elements that differ between the two renders, and draws a box over\n * the screenshot for whichever one the reader is looking at. This is what turns\n * \"80,858 pixels changed\" into \"this card gained a 24px margin\".\n *\n * Boxes are positioned as percentages of the comparison's capture box, which is\n * how the platform's existing region overlay does it — no measuring, no resize\n * handling, correct at any device pixel ratio or rendered size.\n *\n * ES5-compatible vanilla JS, concatenated into the report's single inline\n * script under a CSP that permits no external resources.\n */\n\nexport const JS_DOM = `\n /* ── DOM comparison ── */\n\n /* Rank-cycled so a row and its box always share a colour. */\n var DOM_TONES = ['#03b79c','#f59e0b','#60a5fa','#e879f9','#ef4444','#a3e635'];\n function domTone(rank){return DOM_TONES[(rank-1)%DOM_TONES.length];}\n\n function domKindLabel(kind){\n if(kind==='restyled')return 'restyled';\n if(kind==='text')return 'text';\n if(kind==='resized')return 'resized';\n return kind;\n }\n\n /* Percentages of the capture box: the image may be rendered at any size. */\n function domBoxStyle(rect,box){\n if(!rect||!box||!box[2]||!box[3])return null;\n var left=((rect[0]-box[0])/box[2])*100;\n var top=((rect[1]-box[1])/box[3])*100;\n var width=(rect[2]/box[2])*100;\n var height=(rect[3]/box[3])*100;\n /* An element scrolled out of the captured region has nothing to point at. */\n if(left>100||top>100||left+width<0||top+height<0)return null;\n return 'left:'+left.toFixed(3)+'%;top:'+top.toFixed(3)+'%;width:'+width.toFixed(3)+'%;height:'+height.toFixed(3)+'%';\n }\n\n function renderDomOverlay(v){\n var h='<div class=\"dom-ov\">';\n for(var i=0;i<v.domChanges.length;i++){\n var c=v.domChanges[i];\n /* A removed element has no box in this render; show where it used to be. */\n var style=domBoxStyle(c.rect||c.baselineRect,v.domCaptureBox);\n if(!style)continue;\n h+='<div class=\"dom-box\" data-dom-box=\"'+c.rank+'\" style=\"'+style+';border-color:'+domTone(c.rank)+'\"></div>';\n }\n return h+'</div>';\n }\n\n function renderDomRow(c){\n var h='<div class=\"dom-row\" data-dom-row=\"'+c.rank+'\">';\n h+='<span class=\"dom-swatch\" style=\"background:'+domTone(c.rank)+'\"></span>';\n h+='<span class=\"dom-label\">'+esc(c.label)+'</span>';\n for(var k=0;k<c.kinds.length;k++){\n h+='<span class=\"dom-kind dk-'+esc(c.kinds[k])+'\">'+esc(domKindLabel(c.kinds[k]))+'</span>';\n }\n h+='<span class=\"dom-area\">'+(c.areaPct*100).toFixed(1)+'%</span>';\n h+='</div>';\n\n var detail='';\n for(var a=0;a<(c.attrs||[]).length;a++){\n var at=c.attrs[a];\n detail+='<div class=\"dom-prop dom-prop-attr\"><span class=\"dom-prop-name\">'+esc(at.prop)+'</span>'+\n '<span class=\"dom-from\">'+esc(at.from)+'</span>'+\n '<span class=\"dom-arrow\">&rarr;</span>'+\n '<span class=\"dom-to\">'+esc(at.to)+'</span></div>';\n }\n for(var s=0;s<(c.styles||[]).length;s++){\n var st=c.styles[s];\n detail+='<div class=\"dom-prop\"><span class=\"dom-prop-name\">'+esc(st.prop)+'</span>'+\n '<span class=\"dom-from\">'+esc(st.from)+'</span>'+\n '<span class=\"dom-arrow\">&rarr;</span>'+\n '<span class=\"dom-to\">'+esc(st.to)+'</span></div>';\n }\n if(c.text){\n detail+='<div class=\"dom-prop\"><span class=\"dom-prop-name\">text</span>'+\n '<span class=\"dom-from\">'+esc(c.text.from)+'</span>'+\n '<span class=\"dom-arrow\">&rarr;</span>'+\n '<span class=\"dom-to\">'+esc(c.text.to)+'</span></div>';\n }\n if(c.baselineRect&&c.rect){\n var moved=(c.baselineRect[0]!==c.rect[0]||c.baselineRect[1]!==c.rect[1]);\n var sized=(c.baselineRect[2]!==c.rect[2]||c.baselineRect[3]!==c.rect[3]);\n if(moved||sized){\n detail+='<div class=\"dom-prop\"><span class=\"dom-prop-name\">rect</span>'+\n '<span class=\"dom-from\">'+c.baselineRect.join(', ')+'</span>'+\n '<span class=\"dom-arrow\">&rarr;</span>'+\n '<span class=\"dom-to\">'+c.rect.join(', ')+'</span></div>';\n }\n }\n if(detail)h+='<div class=\"dom-detail\">'+detail+'</div>';\n return h;\n }\n\n function renderDomPane(v){\n var changes=v.domChanges||[];\n if(changes.length===0){\n return '<div class=\"vis-note\">The DOM matched its baseline exactly \\\\u2014 every element in the same place, with the same styles and text.</div>';\n }\n\n var roots=[],shifted=[];\n for(var i=0;i<changes.length;i++){\n if(changes[i].consequence)shifted.push(changes[i]);else roots.push(changes[i]);\n }\n\n var h='';\n var image=v.actual||v.baseline;\n if(image){\n h+='<div class=\"dom-figure\">';\n h+='<img class=\"dom-img\" src=\"'+esc(image)+'\" alt=\"Changed elements\">';\n h+=renderDomOverlay(v);\n h+='</div>';\n }\n\n h+='<div class=\"dom-summary\">';\n h+='<strong>'+(v.domChangeCount||changes.length)+'</strong> element'+((v.domChangeCount||changes.length)===1?'':'s')+' differ';\n if(shifted.length>0){\n /* Say what we actually know: these only moved. Not that the one above\n moved them — the diff does not prove causation and must not imply it. */\n h+=' \\\\u00b7 <strong>'+roots.length+'</strong> changed, <strong>'+shifted.length+'</strong> only shifted position';\n }\n if(v.domChangeCount&&v.domChangeCount>changes.length){\n h+=' \\\\u00b7 showing the top '+changes.length;\n }\n if(v.domTruncated)h+=' <span class=\"dom-truncated\">capture truncated</span>';\n h+='</div>';\n\n h+='<div class=\"dom-list\">';\n for(var r=0;r<roots.length;r++)h+=renderDomRow(roots[r]);\n if(shifted.length>0){\n h+='<div class=\"dom-group-label\">Moved or resized only</div>';\n for(var m=0;m<shifted.length;m++)h+=renderDomRow(shifted[m]);\n }\n h+='</div>';\n return h;\n }\n\n /* Hover a row, highlight its box. Delegated so it covers cards that are\n rendered into the drawer long after this runs. */\n function domSetActive(card,rank,on){\n var box=card.querySelector('[data-dom-box=\"'+rank+'\"]');\n if(box)box.classList.toggle('hi',on);\n var row=card.querySelector('[data-dom-row=\"'+rank+'\"]');\n if(row)row.classList.toggle('hi',on);\n }\n\n document.addEventListener('mouseover',function(e){\n var row=e.target.closest?e.target.closest('.dom-row'):null;\n if(!row)return;\n var card=row.closest('.vis-card');\n if(card)domSetActive(card,row.getAttribute('data-dom-row'),true);\n });\n\n document.addEventListener('mouseout',function(e){\n var row=e.target.closest?e.target.closest('.dom-row'):null;\n if(!row)return;\n var card=row.closest('.vis-card');\n if(card&&!row.classList.contains('pinned'))domSetActive(card,row.getAttribute('data-dom-row'),false);\n });\n`;\n","/**\n * Client-side JavaScript for the redesigned report shell: the verdict band,\n * the needs-attention block, and the detail section rail.\n *\n * These replace `renderSummary` and add two new surfaces; the test grid and\n * the drawer keep their existing renderers and are only enriched in place.\n *\n * ES5-compatible vanilla JS, concatenated into the report's single inline\n * script under a CSP that permits no external resources.\n */\n\nexport const JS_LAYOUT = `\n /* ── Verdict band ── */\n\n /* Both report modes hand us a summary; streaming's carries fewer fields. */\n function vNum(v){return typeof v==='number'?v:0;}\n\n function renderVerdictBar(s){\n var order=[\n {k:'passed',n:vNum(s.passed)},{k:'failed',n:vNum(s.failed)},\n {k:'flaky',n:vNum(s.flaky)},{k:'timedout',n:vNum(s.timedout)},\n {k:'skipped',n:vNum(s.skipped)}\n ];\n var total=0;for(var i=0;i<order.length;i++)total+=order[i].n;\n if(total===0)return '<div class=\"verdict-bar\"><span class=\"verdict-bar-empty\"></span></div>';\n var h='<div class=\"verdict-bar\">';\n for(var j=0;j<order.length;j++){\n if(order[j].n===0)continue;\n h+='<span class=\"vb-'+order[j].k+'\" style=\"flex:'+order[j].n+'\"></span>';\n }\n return h+'</div>';\n }\n\n function renderVerdictHeadline(s){\n var failed=vNum(s.failed),total=vNum(s.total),flaky=vNum(s.flaky);\n if(failed>0)return failed+' of '+total+' test'+(total===1?'':'s')+' failed';\n if(flaky>0)return flaky+' of '+total+' test'+(total===1?'':'s')+' passed on retry';\n if(total===0)return 'No tests ran';\n return 'All '+total+' test'+(total===1?'':'s')+' passed';\n }\n\n /* Where the failures are, when they are all in one place — saves a scan. */\n function failureLocation(){\n var files={};var n=0;\n for(var i=0;i<tests.length;i++){\n var t=tests[i];\n if(t.status!=='failed'&&t.status!=='timedout')continue;\n if(!files[t.filePath]){files[t.filePath]=1;n++;}\n }\n if(n!==1)return null;\n for(var f in files){if(files.hasOwnProperty(f))return f;}\n return null;\n }\n\n function renderSummary(){\n var s=data.summary||{};\n var failed=vNum(s.failed),flaky=vNum(s.flaky);\n var cls=failed>0?'v-failed':(flaky>0?'':'v-passed');\n\n var h='<div class=\"verdict\"><div class=\"verdict-main '+cls+'\">';\n\n h+='<div class=\"verdict-head\"><div class=\"verdict-title\">'+esc(renderVerdictHeadline(s))+'</div>';\n var where=failed>0?failureLocation():null;\n if(where)h+='<div class=\"verdict-where\">all in <code>'+esc(where.split('/').pop())+'</code></div>';\n h+='<div class=\"verdict-dur\"><svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 7v5l3 2\"/></svg>'+fmtDur(data.totalDuration||0)+'</div>';\n h+='</div>';\n\n h+=renderVerdictBar(s);\n\n /* Non-zero statuses get a real count; the rest collapse to one grey line\n rather than each taking a sixth of the width. */\n var kinds=[\n {k:'passed',l:'passed',c:'#22c55e'},{k:'failed',l:'failed',c:'#ef4444'},\n {k:'flaky',l:'flaky',c:'#f59e0b'},{k:'timedout',l:'timed out',c:'#f97316'},\n {k:'skipped',l:'skipped',c:'#6b7280'}\n ];\n var counts='',zeros=[];\n for(var m=0;m<kinds.length;m++){\n var kk=kinds[m],n=vNum(s[kk.k]);\n if(n>0){\n counts+='<div class=\"vc\"><span class=\"vc-dot\" style=\"background:'+kk.c+'\"></span><span class=\"vc-n\">'+n+'</span><span class=\"vc-l\">'+kk.l+'</span></div>';\n }else{\n zeros.push('0 '+kk.l);\n }\n }\n h+='<div class=\"verdict-counts\">'+counts;\n if(zeros.length>0)h+='<div class=\"verdict-zeros\">'+esc(zeros.join(' \\\\u00b7 '))+'</div>';\n h+='</div>';\n\n h+='</div>';\n\n /* What this run actually captured — only tiles with something in them. */\n var tiles=[];\n if(vNum(s.visualChecks)>0)tiles.push({n:vNum(s.visualChecks),l:'visual checks',cls:''});\n if(vNum(s.visualDomChanges)>0)tiles.push({n:vNum(s.visualDomChanges),l:'DOM changes',cls:' vs-dom'});\n if(vNum(s.totalActionSteps)>0)tiles.push({n:vNum(s.totalActionSteps),l:'actions',cls:''});\n if(vNum(s.totalApiCalls)>0)tiles.push({n:vNum(s.totalApiCalls),l:'API calls',cls:''});\n if(vNum(s.totalNavigations)>0)tiles.push({n:vNum(s.totalNavigations),l:'navigations',cls:''});\n\n if(tiles.length>0){\n h+='<div class=\"verdict-side\"><div class=\"verdict-side-label\">Captured this run</div><div class=\"verdict-side-grid\">';\n for(var q=0;q<Math.min(tiles.length,4);q++){\n h+='<div><div class=\"vs-n'+tiles[q].cls+'\">'+tiles[q].n.toLocaleString()+'</div><div class=\"vs-l\">'+esc(tiles[q].l)+'</div></div>';\n }\n h+='</div></div>';\n }\n\n h+='</div>';\n summaryEl.innerHTML=h;\n }\n\n /* ── Needs attention ── */\n\n /* The first line of the error, which is the part that says what happened.\n Playwright's colour codes are stripped by the reporter before this. */\n function causeLine(t){\n if(t.firstFailedAssertion&&typeof assertionSentence==='function'){\n return '<em>'+assertionSentence(t.firstFailedAssertion)+'</em>';\n }\n if(t.assertionsFailed>0&&typeof apiFailureSentence==='function'&&t.apiAssertions){\n var sentence=apiFailureSentence(t.apiAssertions);\n if(sentence)return '<em>'+sentence+'</em>';\n }\n if(evVisualFailed(t)>0&&t.errorMessage){\n var px=t.errorMessage.match(/([\\\\d,]+) pixels \\\\(ratio ([\\\\d.]+)/);\n if(px)return '<em>'+esc(px[1])+' px differ</em>';\n }\n if(!t.errorMessage)return '';\n var first=String(t.errorMessage).split('\\\\n')[0].trim();\n if(first.length>150)first=first.slice(0,150)+'\\\\u2026';\n return '<em>'+esc(first)+'</em>';\n }\n\n function evidenceChips(t){\n var h='';\n /* These accessors read both report shapes — see html-js-render. */\n var _vis=evVisualCount(t),_visFail=evVisualFailed(t),_dom=evDomChanges(t),_net=evNetworkCount(t);\n if(_vis>0){\n h+='<span class=\"nchip'+(_visFail>0?' nchip-visual':'')+'\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/><path d=\"M12 3v18\"/></svg>'+(_visFail>0?'Visual diff':'Visual')+'</span>';\n }\n if(_dom>0){\n h+='<span class=\"nchip nchip-dom\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M8 3H5a2 2 0 00-2 2v3M16 3h3a2 2 0 012 2v3M8 21H5a2 2 0 01-2-2v-3M16 21h3a2 2 0 002-2v-3\"/></svg>'+_dom+' element'+(_dom===1?'':'s')+'</span>';\n }\n if(evHasArtifacts(t)){\n h+='<span class=\"nchip\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polygon points=\"5 3 19 12 5 21\"/></svg>Video</span>';\n }\n if(_net>0){\n h+='<span class=\"nchip\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M3 12h4l3 8 4-16 3 8h4\"/></svg>'+_net+' requests</span>';\n }\n if(t.apiCallCount>0){\n h+='<span class=\"nchip\">'+t.apiCallCount+' API call'+(t.apiCallCount===1?'':'s')+'</span>';\n }\n if(t.assertionsFailed>0){\n h+='<span class=\"nchip nchip-visual\">'+t.assertionsFailed+' failed assertion'+(t.assertionsFailed===1?'':'s')+'</span>';\n }else if(t.assertionsPassed>0){\n h+='<span class=\"nchip\">'+t.assertionsPassed+' assertion'+(t.assertionsPassed===1?'':'s')+'</span>';\n }\n if(typeof mobileEvidenceChips==='function')h+=mobileEvidenceChips(t);\n return h;\n }\n\n function renderNeedsAttention(){\n var el=document.getElementById('needs-attention');\n if(!el)return;\n\n var bad=[];\n for(var i=0;i<tests.length;i++){\n var st=tests[i].status;\n if(st==='failed'||st==='timedout'||st==='flaky')bad.push(tests[i]);\n }\n if(bad.length===0){el.innerHTML='';return;}\n\n /* Hard failures first, then flakes; biggest problems at the top. */\n bad.sort(function(a,b){\n var rank=function(x){return x.status==='flaky'?1:0;};\n return rank(a)-rank(b);\n });\n\n var h='<div class=\"needs\"><div class=\"needs-head\"><span class=\"needs-title\">Needs attention</span><span class=\"needs-count\">'+bad.length+'</span></div><div class=\"needs-list\">';\n\n for(var j=0;j<Math.min(bad.length,8);j++){\n var t=bad[j];\n h+='<div class=\"needs-card'+(t.status==='flaky'?' nc-flaky':'')+'\" data-testid=\"'+esc(t.testId)+'\">';\n\n h+='<div class=\"needs-body\">';\n h+='<div class=\"needs-row1\"><span class=\"needs-name\">'+esc(t.title)+'</span>';\n if(t.testType&&t.testType!=='unknown')h+='<span class=\"trb trb-type\">'+esc(t.testType)+'</span>';\n if(t.status==='flaky')h+='<span class=\"trb trb-flaky\">passed on retry</span>';\n h+='<span class=\"needs-file\">'+esc(t.filePath)+'</span></div>';\n\n var cause=causeLine(t);\n if(cause)h+='<div class=\"needs-cause\">'+cause+'</div>';\n\n var chips=evidenceChips(t);\n if(chips)h+='<div class=\"needs-chips\">'+chips+'</div>';\n h+='</div>';\n\n /* Show the failure, do not only describe it. */\n var _diff=evDiffPath(t);\n if(_diff){\n h+='<div class=\"needs-thumb\"><img src=\"'+esc(_diff)+'\" alt=\"Pixel diff\"><div class=\"needs-thumb-cap\">pixel diff</div></div>';\n }\n\n h+='<div class=\"needs-right\"><div class=\"needs-dur\">'+fmtDur(t.duration)+'</div>';\n h+='<div class=\"needs-open\">Open<svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><path d=\"M9 18l6-6-6-6\"/></svg></div></div>';\n\n h+='</div>';\n }\n\n if(bad.length>8)h+='<div class=\"needs-thumb-cap\" style=\"text-align:left;padding:4px 2px\">and '+(bad.length-8)+' more below</div>';\n\n el.innerHTML=h+'</div></div>';\n }\n\n /* ── Detail section rail ── */\n\n var RAIL_SECTIONS=[\n {id:'failure',label:'Why it failed',fail:true},\n {id:'visual',label:'Visual'},\n {id:'dom',label:'Elements',dom:true},\n {id:'assertions',label:'Assertions'},\n {id:'artifacts',label:'Video & trace'},\n {id:'actions',label:'Actions'},\n {id:'timeline',label:'Timeline'},\n {id:'api',label:'API calls'},\n {id:'console',label:'Console'},\n {id:'network',label:'Network'}\n ];\n\n function railIcon(id){\n if(id==='failure')return '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 8v4M12 16h.01\"/></svg>';\n if(id==='visual')return '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/><path d=\"M12 3v18\"/></svg>';\n if(id==='dom')return '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M8 3H5a2 2 0 00-2 2v3M16 3h3a2 2 0 012 2v3M8 21H5a2 2 0 01-2-2v-3M16 21h3a2 2 0 002-2v-3\"/></svg>';\n if(id==='artifacts')return '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polygon points=\"5 3 19 12 5 21\"/></svg>';\n if(id==='actions')return '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M12 20V10M18 20V4M6 20v-4\"/></svg>';\n if(id==='assertions')return '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M20 6L9 17l-5-5\"/></svg>';\n if(id==='console')return '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M4 17l6-6-6-6M12 19h8\"/></svg>';\n return '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M3 12h4l3 8 4-16 3 8h4\"/></svg>';\n }\n\n /* Built from what the drawer actually rendered, so the rail can never\n offer a jump to a section that is not there. */\n function renderDrawerRail(present,t){\n var h='<div class=\"rail-group\"><div class=\"rail-label\">Evidence</div>';\n var first=true;\n for(var i=0;i<RAIL_SECTIONS.length;i++){\n var sec=RAIL_SECTIONS[i];\n var info=present[sec.id];\n if(!info)continue;\n h+='<button class=\"rail-item'+(sec.fail?' rail-fail':'')+(first?' active':'')+'\" data-rail=\"'+sec.id+'\">';\n h+=railIcon(sec.id);\n h+='<span class=\"rail-name\">'+esc(sec.label)+'</span>';\n if(info.n)h+='<span class=\"rail-n'+(sec.dom?' rail-n-dom':'')+'\">'+info.n+'</span>';\n h+='</button>';\n first=false;\n }\n h+='</div>';\n\n h+='<div class=\"rail-meta\"><div class=\"rail-label\">Test</div><div class=\"rail-meta-rows\">';\n if(t.testId)h+='<div class=\"rail-meta-row\"><span class=\"rail-meta-k\">ID</span><span class=\"rail-meta-v mono\">'+esc(String(t.testId).substring(0,12))+'</span></div>';\n if(t.suiteName)h+='<div class=\"rail-meta-row\"><span class=\"rail-meta-k\">Suite</span><span class=\"rail-meta-v\">'+esc(t.suiteName)+'</span></div>';\n h+='<div class=\"rail-meta-row\"><span class=\"rail-meta-k\">Retry</span><span class=\"rail-meta-v\">'+(t.retryCount>0?t.retryCount+'':'none')+'</span></div>';\n h+='</div></div>';\n return h;\n }\n\n /* Scroll the page so a section's top is just under the viewport top.\n A smooth scrollIntoView was silently dropped here: the rail\n item is a <button>, and a smooth scroll requested from inside its click\n handler never started — the section stayed off-screen and the rail entry\n did nothing. Instant scrolling from a computed offset always lands, and\n smooth is attempted first with a check that it actually moved. */\n function jumpToSection(el){\n if(!el)return;\n var pageY=window.scrollY!==undefined?window.scrollY:(window.pageYOffset||0);\n var y=Math.max(0,el.getBoundingClientRect().top+pageY-12);\n try{\n window.scrollTo({top:y,behavior:'smooth'});\n }catch(e){\n window.scrollTo(0,y);\n }\n setTimeout(function(){\n var now=window.scrollY!==undefined?window.scrollY:(window.pageYOffset||0);\n if(Math.abs(now-y)>40)window.scrollTo(0,y);\n },350);\n }\n\n /* Jump, rather than scroll past everything above it. */\n document.addEventListener('click',function(e){\n var btn=e.target.closest?e.target.closest('.rail-item'):null;\n if(!btn)return;\n var id=btn.getAttribute('data-rail');\n\n /* Elements is not a section of its own — it is a pane inside Visual. Jump\n to Visual and switch the pane, so the rail entry lands where it says. */\n var sectionId=id==='dom'?'visual':id;\n var target=document.querySelector('[data-section=\"'+sectionId+'\"]');\n if(id==='dom'&&target){\n var btns=target.querySelectorAll('.seg-btn');\n for(var b2=0;b2<btns.length;b2++){\n if(btns[b2].textContent==='Elements'){btns[b2].click();break;}\n }\n }\n jumpToSection(target);\n var all=document.querySelectorAll('.rail-item');\n for(var i=0;i<all.length;i++)all[i].classList.remove('active');\n btn.classList.add('active');\n });\n\n /* A failing test opens on its failure, not on the top of a long page. */\n document.addEventListener('click',function(e){\n var card=e.target.closest?e.target.closest('.needs-card'):null;\n if(!card)return;\n openTest(card.getAttribute('data-testid'));\n });\n`;\n\n/**\n * Routing for the detail view.\n *\n * The detail is a page, not an overlay. It gets its own history entry, so the\n * browser Back button works and a link to a failing test opens that test.\n *\n * Hash routing rather than `pushState`: the embedded report is a single file\n * people open straight off disk, and `pushState` is not permitted on `file://`.\n * A hash works in both that case and behind the report server.\n */\nexport const JS_ROUTER = `\n var overviewViewEl=document.getElementById('view-overview');\n var detailViewEl=document.getElementById('view-detail');\n var detailNavEl=document.getElementById('detail-nav');\n var _baseTitle=document.title;\n var _overviewScroll=0;\n var _currentTestId=null;\n /* Set while we are the ones changing the URL, so our own write does not\n bounce back through the router as if the user had pressed Back. */\n var _suppressRoute=false;\n\n function testHash(id){return '#test/'+encodeURIComponent(id);}\n\n function parseTestHash(){\n var h=String(location.hash||'');\n if(h.indexOf('#test/')!==0)return null;\n try{return decodeURIComponent(h.slice(6));}catch(e){return h.slice(6);}\n }\n\n /* Switch the view FIRST, then record it in the URL.\n Driving the switch off the hashchange event made the whole navigation\n hostage to an event that is not always delivered — the view simply never\n changed. The URL still updates, so deep links and Back keep working, but\n nothing user-visible depends on the event arriving. */\n function setHashQuietly(value){\n try{\n _suppressRoute=true;\n if(history&&history.replaceState){\n history.pushState(null,'',value||location.pathname+location.search);\n }else{\n location.hash=value||'';\n }\n }catch(e){\n /* pushState is refused on file:// — the hash still works there. */\n try{location.hash=value||'';}catch(e2){/* no URL update; the view is right */}\n }\n setTimeout(function(){_suppressRoute=false;},0);\n }\n\n /* Whether the detail view we are showing came from a push of ours, and so\n can be closed by unwinding history instead of pushing again. */\n var _pushedDetailEntry=false;\n\n function openTest(testId){\n if(!testId||!testMap[testId])return;\n showTest(testId);\n setHashQuietly(testHash(testId));\n _pushedDetailEntry=true;\n }\n\n function closeTest(){\n if(!_currentTestId)return;\n /* Leave the detail entry behind rather than stacking another one on top of\n it. setHashQuietly pushes, so closing this way left #test/<id> in\n history *behind* the overview — the browser Back button then replayed it\n and re-opened the very test the reader had just closed.\n history.back() unwinds our own push; routeFromHash handles the\n resulting popstate and switches the view. */\n if(_pushedDetailEntry&&history&&history.back){\n _pushedDetailEntry=false;\n history.back();\n return;\n }\n showOverview();\n setHashQuietly('');\n }\n\n /* Prev/next walk the CURRENTLY FILTERED list, so stepping through a\n \"Failed\" filter visits the failures and nothing else. */\n function testNeighbours(testId){\n var list=getFilteredTests();\n for(var i=0;i<list.length;i++){\n if(list[i].testId===testId)return {list:list,idx:i};\n }\n return {list:list,idx:-1};\n }\n\n function stepTest(delta){\n if(!_currentTestId)return;\n var n=testNeighbours(_currentTestId);\n if(n.idx<0)return;\n var next=n.list[n.idx+delta];\n if(next)openTest(next.testId);\n }\n\n function renderDetailNav(testId){\n var t=testMap[testId]||{};\n var n=testNeighbours(testId);\n var pos=n.idx>=0?(n.idx+1)+' of '+n.list.length:'';\n\n var h='<button class=\"detail-back\" id=\"detail-back\"><svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M15 18l-6-6 6-6\"/></svg>Back to results</button>';\n if(t.filePath)h+='<span class=\"detail-crumb\">'+esc(t.filePath)+'</span>';\n\n h+='<div class=\"detail-steps\">';\n var noPrev=n.idx<=0;\n var noNext=n.idx<0||n.idx>=n.list.length-1;\n h+='<button class=\"detail-step'+(noPrev?' is-off':'')+'\" id=\"detail-prev\" title=\"Previous test\"'+(noPrev?' disabled':'')+'><svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M15 18l-6-6 6-6\"/></svg></button>';\n if(pos)h+='<span class=\"detail-pos\">'+esc(pos)+'</span>';\n h+='<button class=\"detail-step'+(noNext?' is-off':'')+'\" id=\"detail-next\" title=\"Next test\"'+(noNext?' disabled':'')+'><svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M9 18l6-6-6-6\"/></svg></button>';\n h+='</div>';\n\n detailNavEl.innerHTML=h;\n }\n\n function showOverview(){\n _currentTestId=null;\n var v=drawerBodyEl.querySelector('video');\n if(v)v.pause();\n detailViewEl.hidden=true;\n overviewViewEl.hidden=false;\n document.title=_baseTitle;\n /* Put the reader back where they were in the list, not at the top. */\n window.scrollTo(0,_overviewScroll);\n }\n\n function showTest(testId){\n if(_currentTestId===null)_overviewScroll=window.scrollY||window.pageYOffset||0;\n _currentTestId=testId;\n overviewViewEl.hidden=true;\n detailViewEl.hidden=false;\n window.scrollTo(0,0);\n var t=testMap[testId];\n document.title=(t&&t.title?t.title+' \\u2014 ':'')+_baseTitle;\n renderDetailNav(testId);\n loadAndRenderTest(testId);\n }\n\n /* Back/forward, and the initial load of a pasted link. Idempotent: a\n re-entrant call for the view already showing does nothing. */\n function routeFromHash(){\n if(_suppressRoute)return;\n var id=parseTestHash();\n if(id&&testMap[id]){\n if(_currentTestId!==id)showTest(id);\n }else if(_currentTestId!==null){\n showOverview();\n }\n }\n\n window.addEventListener('hashchange',routeFromHash);\n window.addEventListener('popstate',routeFromHash);\n`;\n","/**\n * Client-side JavaScript for the API surfaces.\n *\n * Three things the report captured and never showed: the run's own API\n * breakdowns (eight of the nine stats reached only the terminal), the\n * assertions (captured, uploaded, documented — and rendered nowhere), and the\n * per-test evidence a grid row needs to be worth scanning.\n *\n * ES5-compatible vanilla JS, concatenated into the report's single inline\n * script under a CSP that permits no external resources.\n */\n\nexport const JS_API = `\n var API_METHOD_COLOR={GET:'#60a5fa',POST:'#34d399',PUT:'#fbbf24',PATCH:'#a78bfa',DELETE:'#f87171',HEAD:'#6b7280',OPTIONS:'#6b7280'};\n var API_STATUS_ORDER=['2xx','3xx','4xx','5xx','error'];\n var API_STATUS_COLOR={'2xx':'#22c55e','3xx':'#60a5fa','4xx':'#fb923c','5xx':'#ef4444','error':'#ef4444'};\n\n function apiMethodColor(m){return API_METHOD_COLOR[String(m||'').toUpperCase()]||'#8b949e';}\n\n /* A value from an assertion. These are arbitrary JSON, so keep them short\n enough to sit on one line and let the reader open the call for the rest. */\n function fmtAssertValue(v){\n if(v===undefined)return 'undefined';\n if(v===null)return 'null';\n var s;\n if(typeof v==='string'){s=v;}\n else{try{s=JSON.stringify(v);}catch(e){s=String(v);}}\n if(s===undefined||s===null)s=String(v);\n if(s.length>120)s=s.slice(0,120)+'\\\\u2026';\n return s;\n }\n\n function statusMixTotal(mix){\n if(!mix)return 0;\n var n=0;\n for(var i=0;i<API_STATUS_ORDER.length;i++)n+=(mix[API_STATUS_ORDER[i]]||0);\n return n;\n }\n\n /* ── Run-level API band ── */\n\n function renderApiMethodCard(byMethod,total){\n var keys=[];\n for(var k in byMethod){if(byMethod.hasOwnProperty(k)&&byMethod[k]>0)keys.push(k);}\n if(keys.length===0)return '';\n keys.sort(function(a,b){return byMethod[b]-byMethod[a];});\n var max=byMethod[keys[0]]||1;\n var h='<div class=\"tcard\"><div class=\"tcard-h\">By method<span class=\"tcard-sub\">'+total.toLocaleString()+' calls</span></div><div style=\"display:flex;flex-direction:column;gap:8px\">';\n for(var i=0;i<keys.length;i++){\n var m=keys[i],c=apiMethodColor(m),pct=Math.max(2,Math.round((byMethod[m]/max)*100));\n h+='<div class=\"mrow\"><span class=\"mrow-k\" style=\"color:'+c+'\">'+esc(m)+'</span>';\n h+='<span class=\"mrow-track\"><span class=\"mrow-fill\" style=\"width:'+pct+'%;background:'+c+'\"></span></span>';\n h+='<span class=\"mrow-n\">'+byMethod[m].toLocaleString()+'</span></div>';\n }\n return h+'</div></div>';\n }\n\n function renderApiStatusCard(mix){\n var total=statusMixTotal(mix);\n if(total===0)return '';\n var h='<div class=\"tcard\"><div class=\"tcard-h\">By status</div><div class=\"sbar\">';\n for(var i=0;i<API_STATUS_ORDER.length;i++){\n var k=API_STATUS_ORDER[i],n=mix[k]||0;\n if(n===0)continue;\n h+='<span style=\"flex:'+n+';background:'+API_STATUS_COLOR[k]+'\"></span>';\n }\n h+='</div><div class=\"sgrid\">';\n for(var j=0;j<API_STATUS_ORDER.length;j++){\n var kk=API_STATUS_ORDER[j],nn=mix[kk]||0;\n h+='<div class=\"scell\"><span class=\"spill sp-'+kk+'\">'+esc(kk)+'</span>';\n h+='<span class=\"scell-n'+(nn===0?' is-zero':'')+'\">'+nn.toLocaleString()+'</span></div>';\n }\n return h+'</div></div>';\n }\n\n function renderAssertionsCard(s){\n var total=vNum(s.totalAssertions);\n if(total===0)return '';\n var pass=vNum(s.passedAssertions),fail=vNum(s.failedAssertions);\n var h='<div class=\"tcard\"><div class=\"tcard-h\">Assertions</div>';\n h+='<div class=\"tbig\"><span class=\"tbig-n\">'+total.toLocaleString()+'</span><span class=\"tbig-l\">total</span></div>';\n h+='<div class=\"tsplit\">';\n h+='<span class=\"tsplit-i tsplit-ok\"><svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><path d=\"M20 6L9 17l-5-5\"/></svg>'+pass.toLocaleString()+'</span>';\n h+='<span class=\"tsplit-i '+(fail>0?'tsplit-bad':'')+'\" style=\"'+(fail>0?'':'color:var(--fg-2)')+'\"><svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><path d=\"M18 6L6 18M6 6l12 12\"/></svg>'+fail.toLocaleString()+'</span>';\n h+='</div></div>';\n return h;\n }\n\n function renderApiTimingCard(rt,uniqueUrls){\n if(!rt)return '';\n var h='<div class=\"tcard\"><div class=\"tcard-h\">Response time</div><div class=\"tpair\">';\n h+='<div class=\"tpair-i\"><span class=\"tpair-n\">'+Math.round(rt.avg)+'<small>ms</small></span><span class=\"tpair-l\">average</span></div>';\n h+='<div class=\"tpair-i\"><span class=\"tpair-n\">'+Math.round(rt.p95)+'<small>ms</small></span><span class=\"tpair-l\">p95</span></div>';\n h+='<div class=\"tpair-i\"><span class=\"tpair-n\">'+Math.round(rt.max)+'<small>ms</small></span><span class=\"tpair-l\">slowest</span></div>';\n if(uniqueUrls>0)h+='<div class=\"tpair-i\"><span class=\"tpair-n\">'+uniqueUrls.toLocaleString()+'</span><span class=\"tpair-l\">endpoints</span></div>';\n return h+'</div></div>';\n }\n\n /* The band only exists when the run actually produced the evidence it\n describes — a pure-UI run renders nothing here rather than four zeroes. */\n function renderTypeBand(){\n var el=document.getElementById('type-band');\n if(!el)return;\n var s=data.summary||{};\n var cards=[];\n\n if(vNum(s.totalApiCalls)>0){\n var mc=renderApiMethodCard(s.apiCallsByMethod||{},vNum(s.totalApiCalls));\n if(mc)cards.push(mc);\n var sc=renderApiStatusCard(s.apiCallsByStatusRange||{});\n if(sc)cards.push(sc);\n var tc=renderApiTimingCard(s.apiResponseTime,vNum(s.uniqueApiUrls));\n if(tc)cards.push(tc);\n }\n var ac=renderAssertionsCard(s);\n if(ac)cards.push(ac);\n\n if(typeof renderMobileCards==='function'){\n var mob=renderMobileCards(s);\n for(var mi=0;mi<mob.length;mi++)cards.push(mob[mi]);\n }\n\n if(cards.length===0){el.innerHTML='';return;}\n var cls=cards.length>=3?'tband-3':'tband-2';\n /* Four cards wrap to two rows of two rather than shrinking to slivers. */\n if(cards.length===4)cls='tband-2';\n el.innerHTML='<div class=\"tband '+cls+'\">'+cards.join('')+'</div>';\n }\n\n /* ── Per-row evidence ── */\n\n /* Status chips plus a single pass/fail mark. A row has roughly 110px for\n this, so it shows the shape of the calls, not the calls themselves. */\n function apiRowEvidence(t){\n if(!t)return '';\n var mix=t.apiStatusMix,h='';\n if(mix&&statusMixTotal(mix)>0){\n for(var i=0;i<API_STATUS_ORDER.length;i++){\n var k=API_STATUS_ORDER[i],n=mix[k]||0;\n if(n===0)continue;\n h+='<span class=\"achip sp-'+k+'\" title=\"'+n+' '+esc(k)+' response'+(n===1?'':'s')+'\">'+n+'</span>';\n }\n }else if(t.apiCallCount>0){\n h+='<span class=\"achip\" style=\"background:rgba(255,255,255,.04);color:var(--fg-1)\">'+t.apiCallCount+'</span>';\n }\n var pass=t.assertionsPassed||0,fail=t.assertionsFailed||0;\n if(pass>0||fail>0){\n if(fail>0){\n h+='<span class=\"amark amark-bad\" title=\"'+fail+' assertion'+(fail===1?'':'s')+' failed\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><path d=\"M18 6L6 18M6 6l12 12\"/></svg></span>';\n }else{\n h+='<span class=\"amark amark-ok\" title=\"'+pass+' assertion'+(pass===1?'':'s')+' passed\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><path d=\"M20 6L9 17l-5-5\"/></svg></span>';\n }\n }\n return h?'<span class=\"arow\">'+h+'</span>':'';\n }\n\n /* ── Detail: the assertions section ── */\n\n function findApiCall(apiCalls,callId){\n if(!apiCalls||!callId)return null;\n for(var i=0;i<apiCalls.length;i++){\n var c=apiCalls[i];\n if(c.id===callId||c.callId===callId)return c;\n }\n return null;\n }\n\n function assertionExpression(a){\n if(a.expression)return a.expression;\n /* No expression was captured; say what the assertion checked instead of\n leaving the row blank. */\n return 'expect('+String(a.type||'value')+') \\\\u2192 '+fmtAssertValue(a.expected);\n }\n\n function renderAssertionsSection(assertions,apiCalls){\n if(!assertions||assertions.length===0)return '';\n var pass=0,fail=0,i;\n for(i=0;i<assertions.length;i++){\n if(assertions[i].status==='failed')fail++;else pass++;\n }\n\n var h='<div class=\"apisec\"><div class=\"apisec-head\">';\n h+='<span class=\"apisec-title\">Assertions</span>';\n if(pass>0)h+='<span class=\"apisec-badge ab-ok\">'+pass+' passed</span>';\n if(fail>0)h+='<span class=\"apisec-badge ab-bad\">'+fail+' failed</span>';\n h+='</div><div class=\"asec\">';\n\n /* Failures first — the reason anyone opened this page. */\n var ordered=assertions.slice().sort(function(a,b){\n return (a.status==='failed'?0:1)-(b.status==='failed'?0:1);\n });\n\n for(i=0;i<ordered.length;i++){\n var a=ordered[i],failed=a.status==='failed';\n h+='<div class=\"arow-item'+(failed?' is-failed':'')+'\">';\n h+=failed\n ? '<svg style=\"flex-shrink:0\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#ef4444\" stroke-width=\"2.5\"><path d=\"M18 6L6 18M6 6l12 12\"/></svg>'\n : '<svg style=\"flex-shrink:0\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#22c55e\" stroke-width=\"2.5\"><path d=\"M20 6L9 17l-5-5\"/></svg>';\n h+='<span class=\"atype\">'+esc(String(a.type||'custom'))+'</span>';\n h+='<span class=\"aexpr\">'+esc(assertionExpression(a))+'</span>';\n if(a.location&&a.location.line)h+='<span class=\"aloc\">:'+a.location.line+'</span>';\n h+='</div>';\n\n if(failed){\n h+='<div class=\"adiff\">';\n h+='<span class=\"adiff-i\"><span class=\"adiff-k\">expected</span><span class=\"adiff-v adiff-exp\">'+esc(fmtAssertValue(a.expected))+'</span></span>';\n h+='<span class=\"adiff-i\"><span class=\"adiff-k\">received</span><span class=\"adiff-v adiff-act\">'+esc(fmtAssertValue(a.actual))+'</span></span>';\n h+='</div>';\n var call=findApiCall(apiCalls,a.callId);\n if(call){\n var code=call.responseStatusCode!=null?call.responseStatusCode:(call.response&&call.response.status);\n h+='<div class=\"acall\">on '+esc(String(call.method||''))+' '+esc(urlPath(String(call.url||'')))+(code?' \\\\u2192 '+code:'')+'</div>';\n }\n }\n }\n return h+'</div></div>';\n }\n\n /* The same sentence, built from the flattened summary the index carries —\n so the run overview reads identically in embedded and streaming mode. */\n function assertionSentence(fa){\n if(!fa)return '';\n return 'A <code>'+esc(String(fa.type||'custom'))+'</code> assertion expected <code>'+\n esc(String(fa.expected))+'</code> but received <code>'+esc(String(fa.actual))+'</code>.';\n }\n\n /* The headline for an API failure: the assertion, not the matcher wrapper. */\n function apiFailureSentence(assertions){\n if(!assertions||assertions.length===0)return '';\n for(var i=0;i<assertions.length;i++){\n var a=assertions[i];\n if(a.status!=='failed')continue;\n return 'A <code>'+esc(String(a.type||'custom'))+'</code> assertion expected <code>'+\n esc(fmtAssertValue(a.expected))+'</code> but received <code>'+\n esc(fmtAssertValue(a.actual))+'</code>.';\n }\n return '';\n }\n`;\n","/**\n * Styles for the visual regression section of the HTML report.\n *\n * Kept out of `html-css.ts` deliberately — that file is already past the\n * 500-line ceiling in Constitution Principle II, so growing it further would\n * make a bad situation worse.\n *\n * The comparison modes lean on two layered images of identical size: the\n * baseline sits in normal flow and sets the box, the candidate is absolutely\n * positioned on top of it, and a single slider drives either a `clip-path`\n * wipe or an opacity blend. No canvas, no measurement, nothing to keep in sync\n * on resize.\n */\n\nexport const CSS_VISUAL = `\n.vis-header{font-size:11px;font-weight:700;color:var(--fg-1);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px;display:flex;align-items:center;gap:6px}\n.vis-header svg{opacity:.6}\n.vis-count{font-weight:400;color:var(--fg-2);font-size:10px}\n.vis-count-fail{color:#ef4444}\n.vis-list{display:flex;flex-direction:column;gap:20px}\n.vis-card{border:1px solid var(--bd);border-radius:10px;padding:14px;background:var(--bg-2);min-width:0}\n.vis-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:10px}\n.vis-name{font-size:12px;font-weight:600;color:var(--fg-1);word-break:break-all}\n.vis-badge{font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;text-transform:uppercase;letter-spacing:.04em;white-space:nowrap}\n.vis-badge.vb-passed{background:rgba(3,183,156,0.14);color:#2dd4a8}\n.vis-badge.vb-failed{background:rgba(239,68,68,0.14);color:#ef4444}\n.vis-badge.vb-new{background:rgba(59,130,246,0.14);color:#60a5fa}\n.vis-badge.vb-updated{background:rgba(234,179,8,0.14);color:#eab308}\n.vis-badge.vb-dom{background:rgba(232,121,249,0.14);color:#e879f9}\n.vis-metrics{font-size:11px;color:var(--fg-2);margin-left:auto;white-space:nowrap;font-variant-numeric:tabular-nums}\n.vis-note{font-size:11px;color:var(--fg-2);font-style:italic;padding:6px 0}\n\n/* Side-by-side: wraps to a single column when the drawer is narrow. */\n.vis-pair{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px}\n.vis-pane-label{font-size:10px;font-weight:600;color:var(--fg-2);text-transform:uppercase;letter-spacing:.04em;margin-bottom:6px}\n\n/* Layered comparison surface shared by the slider and onion-skin modes. */\n.vis-stack{position:relative;line-height:0;border:1px solid var(--bd);border-radius:8px;overflow:hidden;background:var(--bg)}\n.vis-stack img{display:block;width:100%;height:auto}\n.vis-stack .vis-top{position:absolute;top:0;left:0;width:100%;height:100%;object-fit:contain}\n.vis-handle{position:absolute;top:0;bottom:0;width:2px;background:#03b79c;pointer-events:none;box-shadow:0 0 0 1px rgba(0,0,0,0.35)}\n.vis-range{width:100%;margin-top:10px;accent-color:#03b79c;cursor:ew-resize}\n.vis-range-row{display:flex;align-items:center;gap:10px;font-size:10px;color:var(--fg-2);text-transform:uppercase;letter-spacing:.04em}\n.vis-range-row .vis-range{margin-top:0}\n\n/* A checkerboard behind the diff so fully transparent regions stay legible. */\n.vis-diff-wrap{background-image:linear-gradient(45deg,var(--bd-xs) 25%,transparent 25%),linear-gradient(-45deg,var(--bd-xs) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,var(--bd-xs) 75%),linear-gradient(-45deg,transparent 75%,var(--bd-xs) 75%);background-size:16px 16px;background-position:0 0,0 8px,8px -8px,-8px 0;border-radius:8px}\n`;\n\n/**\n * Styles for the Elements pane — the DOM comparison.\n *\n * The overlay mirrors the platform's existing region overlay: an absolutely\n * positioned layer over a `position:relative` image, with each box placed in\n * percentages so nothing needs measuring or re-measuring on resize.\n */\nexport const CSS_DOM = `\n.dom-figure{position:relative;line-height:0;border:1px solid var(--bd);border-radius:8px;overflow:hidden;margin-bottom:12px}\n.dom-img{display:block;width:100%;height:auto}\n.dom-ov{position:absolute;top:0;left:0;right:0;bottom:0;pointer-events:none}\n.dom-box{position:absolute;border:2px solid #03b79c;border-radius:2px;opacity:.55;transition:opacity .12s,box-shadow .12s}\n.dom-box.hi{opacity:1;box-shadow:0 0 0 2px rgba(0,0,0,.45),0 0 12px rgba(255,255,255,.25)}\n\n.dom-summary{font-size:11px;color:var(--fg-2);margin-bottom:10px}\n.dom-summary strong{color:var(--fg-1);font-variant-numeric:tabular-nums}\n.dom-truncated{display:inline-block;margin-left:6px;padding:1px 6px;border-radius:999px;background:rgba(234,179,8,.14);color:#eab308;font-size:10px;font-weight:600}\n\n.dom-list{display:flex;flex-direction:column;gap:2px}\n.dom-group-label{font-size:10px;font-weight:600;color:var(--fg-2);text-transform:uppercase;letter-spacing:.04em;margin:12px 0 4px}\n.dom-row{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:6px;cursor:default;flex-wrap:wrap}\n.dom-row:hover,.dom-row.hi{background:var(--bd-xs)}\n.dom-swatch{width:8px;height:8px;border-radius:2px;flex:none}\n.dom-label{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--fg-1);word-break:break-all}\n.dom-kind{font-size:9px;font-weight:600;padding:1px 6px;border-radius:999px;text-transform:uppercase;letter-spacing:.04em;background:var(--bd-xs);color:var(--fg-2)}\n.dom-kind.dk-restyled{background:rgba(232,121,249,.14);color:#e879f9}\n.dom-kind.dk-text{background:rgba(96,165,250,.14);color:#60a5fa}\n.dom-kind.dk-attrs{background:rgba(251,146,60,.14);color:#fb923c}\n.dom-kind.dk-added{background:rgba(3,183,156,.14);color:#2dd4a8}\n.dom-kind.dk-removed{background:rgba(239,68,68,.14);color:#ef4444}\n.dom-area{margin-left:auto;font-size:10px;color:var(--fg-2);font-variant-numeric:tabular-nums}\n\n.dom-detail{padding:0 8px 8px 24px;display:flex;flex-direction:column;gap:3px}\n.dom-prop{display:flex;align-items:baseline;gap:8px;font-size:10px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;flex-wrap:wrap}\n.dom-prop-name{color:var(--fg-2);min-width:130px}\n.dom-prop-attr .dom-prop-name{color:#fb923c;font-weight:600}\n.dom-from{color:#ef4444;word-break:break-all}\n.dom-arrow{color:var(--fg-2)}\n.dom-to{color:#2dd4a8;word-break:break-all}\n`;\n","/**\n * Styles for the redesigned report layout: the verdict band, the needs-\n * attention block, the enriched test row and the detail section rail.\n *\n * A separate file because `html-css.ts` is already 569 lines, past the\n * 500-line ceiling in Constitution Principle II. Everything here uses the\n * tokens defined there — no new colours, radii or type sizes are introduced.\n */\n\nexport const CSS_LAYOUT = `\n/* ── Wider shell ──\n 960px was sized for a list. The row now carries evidence and timing, and\n the detail panel shows two screenshots side by side; both were cramped. */\n.wrap{max-width:1400px;padding:28px 40px 64px}\n\n/* ── Verdict band (replaces the six equal stat chips) ── */\n.verdict{display:flex;gap:16px;align-items:stretch;margin-bottom:22px}\n.verdict-main{flex:1;min-width:0;background:var(--bg-1);border:1px solid var(--bd);border-radius:12px;padding:18px 22px;display:flex;flex-direction:column;gap:14px}\n.verdict-main.v-failed{border-color:rgba(239,68,68,0.22)}\n.verdict-main.v-passed{border-color:rgba(34,197,94,0.2)}\n.verdict-head{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap}\n.verdict-title{font-size:21px;font-weight:700;letter-spacing:-0.01em;color:var(--fg)}\n.verdict-where{font-size:12px;color:var(--fg-1)}\n.verdict-where code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:var(--fg)}\n.verdict-dur{margin-left:auto;display:flex;align-items:center;gap:6px;font-size:12px;color:var(--fg-1);white-space:nowrap}\n\n/* Proportion bar: one glance at the shape of the run. */\n.verdict-bar{display:flex;height:8px;gap:2px}\n.verdict-bar span{border-radius:999px;min-width:3px}\n.vb-passed{background:#22c55e}.vb-failed{background:#ef4444}\n.vb-flaky{background:#f59e0b}.vb-skipped{background:#6b7280}.vb-timedout{background:#f97316}\n.verdict-bar-empty{flex:1;background:var(--bd-m);border-radius:999px}\n\n.verdict-counts{display:flex;align-items:center;gap:20px;flex-wrap:wrap}\n.vc{display:flex;align-items:center;gap:7px}\n.vc-dot{width:9px;height:9px;border-radius:50%;flex-shrink:0}\n.vc-n{font-size:14px;font-weight:700;color:var(--fg);font-variant-numeric:tabular-nums}\n.vc-l{font-size:12px;color:var(--fg-1)}\n/* Statuses with nothing in them stop taking a sixth of the width. */\n.verdict-zeros{margin-left:auto;font-size:11px;color:var(--fg-2)}\n\n.verdict-side{width:268px;flex-shrink:0;background:var(--bg-1);border:1px solid var(--bd);border-radius:12px;padding:18px 20px;display:flex;flex-direction:column;gap:12px}\n.verdict-side-label{font-size:10px;font-weight:600;color:var(--fg-2);letter-spacing:.06em;text-transform:uppercase}\n.verdict-side-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px 10px}\n.vs-n{font-size:17px;font-weight:700;color:var(--fg);font-variant-numeric:tabular-nums}\n.vs-n.vs-dom{color:#e879f9}\n.vs-l{font-size:10px;color:var(--fg-1)}\n\n/* ── Needs attention ── */\n.needs{display:flex;flex-direction:column;gap:10px;margin-bottom:22px}\n.needs-head{display:flex;align-items:center;gap:8px;padding:0 2px}\n.needs-title{font-size:11px;font-weight:700;color:var(--fg);letter-spacing:.06em;text-transform:uppercase}\n.needs-count{font-size:10px;font-weight:700;color:#ef4444;background:rgba(239,68,68,0.12);padding:1px 7px;border-radius:4px}\n.needs-list{display:flex;flex-direction:column;gap:8px}\n.needs-card{background:var(--bg-1);border:1px solid rgba(239,68,68,0.2);border-left:3px solid #ef4444;border-radius:10px;padding:14px 18px;display:flex;gap:16px;align-items:flex-start;cursor:pointer;transition:border-color .15s,background .1s}\n.needs-card:hover{background:var(--hvr);border-color:rgba(239,68,68,0.4)}\n.needs-card.nc-flaky{border-color:rgba(245,158,11,0.2);border-left-color:#f59e0b}\n.needs-card.nc-flaky:hover{border-color:rgba(245,158,11,0.4)}\n.needs-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:8px}\n.needs-row1{display:flex;align-items:center;gap:10px;flex-wrap:wrap}\n.needs-name{font-size:14px;font-weight:600;color:var(--fg)}\n.needs-file{font-size:11px;color:var(--fg-2);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n.needs-cause{font-size:12px;color:var(--fg-1);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;line-height:1.6;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.needs-cause em{font-style:normal;color:var(--fg-err)}\n.needs-chips{display:flex;gap:6px;flex-wrap:wrap;align-items:center}\n.needs-thumb{width:148px;flex-shrink:0;display:flex;flex-direction:column;gap:5px}\n.needs-thumb img{width:100%;height:92px;object-fit:cover;object-position:top center;border-radius:7px;border:1px solid rgba(239,68,68,0.35);background:var(--bg)}\n.needs-thumb-cap{font-size:9px;color:var(--fg-2);text-align:center}\n.needs-right{display:flex;flex-direction:column;align-items:flex-end;gap:8px;flex-shrink:0}\n.needs-dur{font-size:13px;font-weight:700;color:var(--fg-1);font-variant-numeric:tabular-nums}\n.needs-open{display:flex;align-items:center;gap:4px;font-size:11px;font-weight:600;color:#2dd4a8}\n\n/* Pill chips used in the needs cards. */\n.nchip{display:flex;align-items:center;gap:4px;font-size:10px;font-weight:500;color:var(--fg-1);background:var(--bg-2);border:1px solid var(--bd);padding:2px 8px;border-radius:999px}\n.nchip-visual{color:#2dd4a8;background:rgba(3,183,156,0.1);border-color:rgba(3,183,156,0.22);font-weight:600}\n.nchip-dom{color:#e879f9;background:rgba(232,121,249,0.1);border-color:rgba(232,121,249,0.2);font-weight:600}\n\n/* ── Enriched test row ──\n Same height as before. Two regions added: what was captured, and how long\n it took relative to the slowest test in the run. */\n.trow-evidence{display:flex;gap:4px;align-items:center;flex-shrink:0}\n.tev{width:18px;height:18px;border-radius:4px;background:var(--bg-2);display:flex;align-items:center;justify-content:center;color:var(--fg-1);flex-shrink:0}\n.tev-visual{background:rgba(3,183,156,0.12);color:#2dd4a8}\n.tev-dom{background:rgba(232,121,249,0.12);color:#e879f9}\n.tev-err{background:rgba(239,68,68,0.12);color:#ef4444}\n.trow-time{width:120px;flex-shrink:0;display:flex;align-items:center;gap:8px}\n.trow-bar{flex:1;height:4px;border-radius:999px;background:var(--bd-m);overflow:hidden}\n.trow-bar span{display:block;height:100%;border-radius:999px}\n.tb-passed{background:#22c55e}.tb-failed{background:#ef4444}\n.tb-flaky{background:#f59e0b}.tb-skipped{background:#6b7280}.tb-timedout{background:#f97316}\n.test-row-dur{font-variant-numeric:tabular-nums}\n/* The title and its badges sit on one line now that the row has columns. */\n.test-row-info.tri-inline{flex-direction:row;align-items:center;gap:10px}\n.test-row-info.tri-inline .test-row-badges{flex-shrink:0}\n\n/* ── Detail panel ──\n Was 50vw, which left the comparison about 330px a side and a dead half-\n screen of grid behind it. */\n.drawer{width:min(1240px,94vw);max-width:94vw}\n.drawer-layout{display:flex;gap:28px;align-items:flex-start}\n.drawer-rail{width:206px;flex-shrink:0;position:sticky;top:0;display:flex;flex-direction:column;gap:18px}\n.drawer-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:24px}\n.rail-group{display:flex;flex-direction:column;gap:2px}\n.rail-label{font-size:10px;font-weight:600;color:var(--fg-2);letter-spacing:.06em;text-transform:uppercase;padding:0 10px 7px}\n.rail-item{display:flex;align-items:center;gap:9px;padding:8px 10px;border-radius:7px;color:var(--fg-1);cursor:pointer;transition:background .12s,color .12s;border:none;background:transparent;font-family:inherit;text-align:left;width:100%}\n.rail-item:hover{background:var(--bd-xs);color:var(--fg)}\n.rail-item.active{background:rgba(3,183,156,0.12);color:#2dd4a8}\n.rail-item.active .rail-name{font-weight:600}\n.rail-item.rail-fail{color:var(--fg-err)}\n.rail-item.rail-fail.active{background:rgba(239,68,68,0.1);color:var(--fg-err)}\n.rail-name{font-size:12.5px;font-weight:500;flex:1;min-width:0}\n.rail-n{font-size:10px;font-weight:700;background:var(--bd-xs);padding:1px 6px;border-radius:4px;font-variant-numeric:tabular-nums}\n.rail-item.active .rail-n{background:rgba(3,183,156,0.18)}\n.rail-n.rail-n-dom{color:#e879f9;background:rgba(232,121,249,0.12)}\n.rail-meta{border-top:1px solid var(--bd);padding-top:16px;display:flex;flex-direction:column;gap:9px}\n.rail-meta-rows{padding:0 10px;display:flex;flex-direction:column;gap:7px;font-size:11px}\n.rail-meta-row{display:flex;gap:8px}\n.rail-meta-k{color:var(--fg-2);width:44px;flex-shrink:0}\n.rail-meta-v{color:var(--fg-1);min-width:0;overflow:hidden;text-overflow:ellipsis}\n.rail-meta-v.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n\n/* Scroll-margin so a rail jump does not tuck the heading under the bar. */\n.drawer-section{scroll-margin-top:12px}\n\n@media (max-width:1100px){\n .drawer{width:96vw;max-width:96vw}\n .drawer-layout{flex-direction:column;gap:18px}\n .drawer-rail{width:100%;position:static;flex-direction:row;flex-wrap:wrap;gap:6px}\n .rail-group{flex-direction:row;flex-wrap:wrap;gap:4px}\n .rail-label,.rail-meta{display:none}\n .rail-item{width:auto}\n .verdict{flex-direction:column}\n .verdict-side{width:100%}\n}\n`;\n\n/**\n * Styles for the detail page.\n *\n * The detail used to be a fixed overlay; it is now a view in the flow, so the\n * page scrolls rather than a panel inside it. The `.drawer-*` class names are\n * kept because four modules query them (`.drawer-body video`, the section\n * wrappers) — the names are historical, the layout is not.\n */\nexport const CSS_DETAIL_PAGE = `\n/* The overlay is gone. Anything still carrying .drawer must not float. */\n.drawer,.drawer-backdrop{display:none}\n\n.detail-page{display:flex;flex-direction:column;gap:20px}\n#view-detail[hidden],#view-overview[hidden]{display:none}\n\n.detail-nav{display:flex;align-items:center;gap:14px;flex-wrap:wrap;padding-bottom:14px;border-bottom:1px solid var(--bd)}\n.detail-back{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:600;color:var(--fg-1);background:transparent;border:1px solid var(--bd-m);border-radius:8px;padding:6px 12px 6px 9px;cursor:pointer;font-family:inherit;transition:background .15s,color .15s,border-color .15s}\n.detail-back:hover{background:var(--hvr);color:var(--fg);border-color:var(--bd-l)}\n.detail-crumb{font-size:11px;color:var(--fg-2);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n\n.detail-steps{margin-left:auto;display:inline-flex;align-items:center;border:1px solid var(--bd-m);border-radius:8px;overflow:hidden}\n.detail-step{display:flex;align-items:center;justify-content:center;width:32px;height:30px;background:transparent;border:none;color:var(--fg-1);cursor:pointer;font-family:inherit;transition:background .15s,color .15s}\n.detail-step:hover:not(.is-off){background:var(--hvr);color:var(--fg)}\n.detail-step.is-off{color:var(--fg-2);cursor:default}\n.detail-pos{font-size:11px;font-weight:600;color:var(--fg-1);padding:0 10px;border-left:1px solid var(--bd-m);border-right:1px solid var(--bd-m);line-height:30px;font-variant-numeric:tabular-nums}\n\n/* The page scrolls now, so the old panel body must not scroll inside it. */\n#view-detail .drawer-body{flex:none;overflow:visible;padding:0}\n.detail-page .detail-rail,.detail-page .drawer-rail{top:16px}\n`;\n","/**\n * Styles for the API surfaces: the run-level API band on the overview, the\n * per-row API evidence in the grid, and the assertions section in the detail\n * page.\n *\n * Tokens are the report's own (`html-css.ts`); nothing here introduces a new\n * colour that is not already in the palette.\n */\n\nexport const CSS_API = `\n/* ── Run-level type band ── */\n.tband{display:grid;gap:16px;margin-bottom:20px}\n.tband-3{grid-template-columns:1.1fr 1.1fr .9fr}\n.tband-2{grid-template-columns:1fr 1fr}\n.tcard{background:var(--bg-1);border:1px solid rgba(255,255,255,.06);border-radius:12px;padding:16px 20px;display:flex;flex-direction:column;gap:12px;min-width:0}\n.tcard-h{font-size:10px;font-weight:600;color:var(--fg-2);letter-spacing:.06em;text-transform:uppercase;display:flex;align-items:center;gap:8px}\n.tcard-h .tcard-sub{margin-left:auto;font-weight:500;letter-spacing:0;text-transform:none;font-size:10px}\n\n/* Method distribution */\n.mrow{display:flex;align-items:center;gap:10px}\n.mrow-k{width:54px;font-size:11px;font-weight:700;font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace)}\n.mrow-track{display:block;flex:1;height:6px;border-radius:999px;background:rgba(255,255,255,.06);overflow:hidden}\n.mrow-fill{display:block;height:100%;border-radius:999px;min-width:3px}\n.mrow-n{font-size:11px;color:var(--fg-1);width:34px;text-align:right}\n\n/* Status distribution */\n.sbar{display:flex;height:8px;gap:2px}\n.sbar>span{border-radius:999px}\n.sgrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px 12px}\n.scell{display:flex;align-items:center;gap:8px}\n.spill{font-size:10px;font-weight:700;padding:1px 7px;border-radius:4px}\n.scell-n{font-size:12px;font-weight:700}\n.scell-n.is-zero{font-weight:400;color:var(--fg-2)}\n.sp-2xx{background:rgba(34,197,94,.12);color:#22c55e}\n.sp-3xx{background:rgba(96,165,250,.12);color:#60a5fa}\n.sp-4xx{background:rgba(251,146,60,.12);color:#fb923c}\n.sp-5xx{background:rgba(239,68,68,.12);color:#ef4444}\n.sp-error{background:rgba(239,68,68,.12);color:#ef4444}\n\n/* Big-number card (assertions, timings) */\n.tbig{display:flex;align-items:baseline;gap:8px}\n.tbig-n{font-size:28px;font-weight:800;line-height:1}\n.tbig-l{font-size:11px;color:var(--fg-1)}\n.tsplit{display:flex;gap:14px;flex-wrap:wrap}\n.tsplit-i{display:flex;align-items:center;gap:6px;font-size:13px;font-weight:700}\n.tsplit-ok{color:#22c55e}\n.tsplit-bad{color:#ef4444}\n.tpair{display:flex;gap:18px;flex-wrap:wrap}\n.tpair-i{display:flex;flex-direction:column;gap:2px}\n.tpair-n{font-size:17px;font-weight:700}\n.tpair-n small{font-size:11px;font-weight:500;color:var(--fg-1)}\n.tpair-l{font-size:10px;color:var(--fg-1)}\n\n/* ── Row evidence in the grid ── */\n.arow{display:flex;gap:5px;align-items:center;flex-shrink:0}\n.achip{font-size:10px;font-weight:700;padding:1px 6px;border-radius:4px;font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace)}\n.amark{width:18px;height:18px;border-radius:4px;display:flex;align-items:center;justify-content:center;flex-shrink:0}\n.amark-ok{background:rgba(34,197,94,.12);color:#22c55e}\n.amark-bad{background:rgba(239,68,68,.12);color:#ef4444}\n\n/* ── Assertions section (detail page) ── */\n.apisec{border:1px solid var(--bd);border-radius:11px;background:var(--bg-1);overflow:hidden}\n.apisec-head{display:flex;align-items:center;gap:10px;padding:12px 20px;border-bottom:1px solid rgba(255,255,255,.06)}\n.apisec-title{font-size:12px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}\n.apisec-badge{font-size:10px;font-weight:700;padding:1px 7px;border-radius:4px}\n.apisec-badge.ab-ok{background:rgba(34,197,94,.12);color:#22c55e}\n.apisec-badge.ab-bad{background:rgba(239,68,68,.12);color:#ef4444}\n.asec{display:flex;flex-direction:column}\n.arow-item{display:flex;align-items:center;gap:12px;padding:10px 20px;border-bottom:1px solid rgba(255,255,255,.04)}\n.arow-item:last-child{border-bottom:none}\n.arow-item.is-failed{background:rgba(239,68,68,.04)}\n.atype{font-size:9px;font-weight:700;padding:1px 6px;border-radius:999px;letter-spacing:.04em;text-transform:uppercase;flex-shrink:0;background:rgba(255,255,255,.04);color:var(--fg-1)}\n.arow-item.is-failed .atype{background:rgba(239,68,68,.14);color:#ef4444}\n.aexpr{font-size:11.5px;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace)}\n.aloc{font-size:10px;color:var(--fg-2);flex-shrink:0;font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace)}\n.adiff{display:flex;gap:22px;flex-wrap:wrap;padding:0 20px 12px 52px}\n.adiff-i{display:flex;gap:8px;align-items:baseline}\n.adiff-k{font-size:10px;color:var(--fg-2);width:58px}\n.adiff-v{font-size:11px;font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace);word-break:break-all}\n.adiff-exp{color:#2dd4a8}\n.adiff-act{color:#ef4444}\n.acall{font-size:10px;color:var(--fg-2);padding:0 20px 10px 52px;font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace)}\n\n/* ── API call row: the two columns that used to render empty ── */\n.ndt-assert{display:inline-flex;align-items:center;justify-content:center;width:100%;font-size:10px;font-weight:700}\n.ndt-assert-ok{color:#22c55e}\n.ndt-assert-bad{color:#ef4444}\n.ndt-assert-none{color:var(--fg-2);font-weight:400}\n.ndt-err-note{color:#ef4444;font-size:11px}\n\n/* Trace pane — a link out, plus how to open the thing */\n.trace-open{display:flex;flex-direction:column;gap:10px;padding:18px;background:var(--bg-2);border:1px solid var(--bd);border-radius:10px}\n.trace-link{display:inline-flex;align-items:center;gap:8px;align-self:flex-start;font-size:12.5px;font-weight:600;color:#2dd4a8;text-decoration:none;border:1px solid rgba(3,183,156,.3);background:rgba(3,183,156,.08);padding:8px 14px;border-radius:8px}\n.trace-link:hover{background:rgba(3,183,156,.14)}\n.trace-hint{font-size:11.5px;color:var(--fg-1);line-height:1.6}\n.trace-hint code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;background:var(--bg-code);padding:1px 5px;border-radius:4px;color:var(--fg)}\n\n/* Cookie table */\n.ndt-headers-label{font-size:9px;font-weight:700;color:var(--fg-2);letter-spacing:.06em;text-transform:uppercase;padding:10px 14px 0}\n.ck-tbl{display:grid;grid-template-columns:auto 1fr;gap:6px 16px;padding:12px 14px;font-size:11.5px}\n.ck-k{color:var(--fg-1);font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace)}\n.ck-v{color:var(--fg);word-break:break-all;font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace)}\n.ck-attr{color:var(--fg-2);font-size:10px}\n\n@media (max-width:1100px){\n .tband-3,.tband-2{grid-template-columns:1fr}\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';\nimport { JS_ACTIONS } from './html-js-actions.js';\nimport { JS_CONSOLE } from './html-js-console.js';\nimport { JS_ARTIFACTS } from './html-js-artifacts.js';\nimport { JS_VISUAL } from './html-js-visual.js';\nimport { JS_DOM } from './html-js-dom.js';\nimport { JS_LAYOUT, JS_ROUTER } from './html-js-layout.js';\nimport { JS_API } from './html-js-api.js';\nimport { CSS_VISUAL, CSS_DOM } from './html-css-visual.js';\nimport { CSS_LAYOUT, CSS_DETAIL_PAGE } from './html-css-layout.js';\nimport { CSS_API } from './html-css-api.js';\n\nconst JS = `\n(function(){\n var data=JSON.parse(document.getElementById('report-data').textContent);\n ${JS_RENDER}\n ${JS_ACTIONS}\n ${JS_CONSOLE}\n ${JS_NETWORK}\n ${JS_FILTERS}\n ${JS_ARTIFACTS}\n ${JS_DOM}\n ${JS_VISUAL}\n ${JS_INTERACTIONS}\n ${JS_LAYOUT}\n ${JS_API}\n ${JS_ROUTER}\n applyTheme();\n renderRunMeta();\n renderSummary();\n renderTypeBand();\n initFilters();\n renderFilterBar();\n renderTestGrid();\n renderNeedsAttention();\n routeFromHash();\n var _lo=document.getElementById('loading-overlay');if(_lo)_lo.remove();\n})();`;\n\nexport function renderHtmlDocument(reportJson: string, serverPort?: number | null, manifestJson?: string | null): 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 const safeManifestJson = manifestJson ? manifestJson.replace(/<\\//g, '<\\\\/') : null;\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: file: 'self'; media-src blob: data: file: 'self'; connect-src 'self' http://127.0.0.1:*;\">\n<title>TestRelic AI Analytics Report</title>\n${serverPort ? `<meta name=\"artifact-server-port\" content=\"${serverPort}\">` : ''}\n<link rel=\"icon\" type=\"image/svg+xml\" href=\"data:image/svg+xml;base64,${Buffer.from(LOGO_SVG_RAW).toString('base64')}\">\n<style>${CSS}${CSS_VISUAL}${CSS_DOM}${CSS_LAYOUT}${CSS_DETAIL_PAGE}${CSS_API}</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=\"view-overview\">\n <div id=\"summary-strip\"></div>\n <div id=\"type-band\"></div>\n <div id=\"needs-attention\"></div>\n <div id=\"filter-bar\" class=\"filter-bar\"></div>\n <div id=\"test-grid\"></div>\n </div>\n <div id=\"view-detail\" class=\"detail-page\" hidden>\n <div class=\"detail-nav\" id=\"detail-nav\"></div>\n <div class=\"drawer-body\" id=\"drawer-body\"></div>\n </div>\n</div>\n<div class=\"loading-overlay\" id=\"loading-overlay\"><div class=\"loading-spinner\"></div><div class=\"loading-text\">Loading report...</div></div>\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\">&times;</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${safeManifestJson ? `<script id=\"artifact-manifest-data\" type=\"application/json\">${safeManifestJson}</script>` : ''}\n<script>${JS}</script>\n</body>\n</html>`;\n}\n\n/**\n * Render a streaming-mode HTML document.\n * Embeds only the summary and compact test index — detail data is fetched from the server.\n */\nexport function renderStreamingHtmlDocument(\n summaryJson: string,\n indexJson: string,\n serverPort?: number | null,\n manifestJson?: string | null,\n): string {\n const safeSummary = summaryJson.replace(/<\\//g, '<\\\\/');\n // Use indexJson directly — caller provides pre-compacted index (from index-compact.json)\n // to avoid expensive parse-reserialize of the full index for large suites.\n const safeIndex = indexJson.replace(/<\\//g, '<\\\\/');\n const safeManifest = manifestJson ? manifestJson.replace(/<\\//g, '<\\\\/') : null;\n\n // Build a minimal report-data object that the existing JS modules can consume.\n // The timeline is empty — details are served on-demand from the server.\n // Parse summary to extract top-level fields for the report header\n let summaryObj: Record<string, unknown> = {};\n try { summaryObj = JSON.parse(summaryJson); } catch { /* use defaults */ }\n const streamingData = `{\n \"schemaVersion\":\"2.0\",\n \"reportMode\":\"streaming\",\n \"serverPort\":${serverPort ?? 'null'},\n \"summary\":${safeSummary},\n \"index\":${safeIndex},\n \"timeline\":[],\n \"testRunId\":${JSON.stringify(String(summaryObj.testRunId ?? ''))},\n \"startedAt\":${JSON.stringify(String(summaryObj.startedAt ?? ''))},\n \"completedAt\":${JSON.stringify(String(summaryObj.completedAt ?? ''))},\n \"totalDuration\":${Number(summaryObj.totalDuration) || 0},\n \"ci\":${summaryObj.ci ? JSON.stringify(summaryObj.ci).replace(/<\\//g, '<\\\\/') : 'null'},\n \"metadata\":${summaryObj.metadata ? JSON.stringify(summaryObj.metadata).replace(/<\\//g, '<\\\\/') : 'null'},\n \"shardRunIds\":${summaryObj.shardRunIds ? JSON.stringify(summaryObj.shardRunIds).replace(/<\\//g, '<\\\\/') : 'null'}\n }`;\n\n const JS_STREAMING = `\n(function(){\n var data=JSON.parse(document.getElementById('report-data').textContent);\n var _serverPort=data.serverPort||null;\n var _serverAvailable=false;\n var _detailCache={};\n var _detailCacheKeysS=[];\n var _detailCacheMaxS=50;\n var _testIndex=data.index||[];\n\n /* Resolve the API base URL: if served from http, use same origin; otherwise use explicit port */\n function getApiBase(){\n if(window.location.protocol==='http:'||window.location.protocol==='https:'){\n return window.location.origin;\n }\n if(_serverPort){return 'http://127.0.0.1:'+_serverPort;}\n return null;\n }\n var _apiBase=getApiBase();\n\n /* Check server availability */\n function checkServer(cb){\n if(!_apiBase){cb(false);return;}\n fetch(_apiBase+'/api/health',{method:'GET'}).then(function(r){\n _serverAvailable=r.ok;cb(r.ok);\n }).catch(function(){_serverAvailable=false;cb(false);});\n }\n\n /* Bounded detail cache (LRU, max 50 entries) */\n function sCacheDetail(testId,d){\n if(_detailCache[testId]){\n var ki=_detailCacheKeysS.indexOf(testId);\n if(ki>-1)_detailCacheKeysS.splice(ki,1);\n }else if(_detailCacheKeysS.length>=_detailCacheMaxS){\n var evict=_detailCacheKeysS.shift();\n delete _detailCache[evict];\n }\n _detailCache[testId]=d;\n _detailCacheKeysS.push(testId);\n }\n\n /* Fetch test detail metadata from server */\n function fetchTestDetail(testId,cb){\n if(_detailCache[testId]){cb(null,_detailCache[testId]);return;}\n if(!_serverAvailable||!_apiBase){cb('Server unavailable');return;}\n fetch(_apiBase+'/api/tests/'+testId).then(function(r){\n if(!r.ok)throw new Error('HTTP '+r.status);\n return r.json();\n }).then(function(d){\n sCacheDetail(testId,d);cb(null,d);\n }).catch(function(e){cb(e.message||'Failed to fetch');});\n }\n\n /* Fetch paginated heavy data (network, console, api-calls) */\n function fetchTestData(testId,dataType,page,pageSize,cb){\n if(!_serverAvailable||!_apiBase){cb('Server unavailable');return;}\n var url=_apiBase+'/api/tests/'+testId+'/'+dataType+'?page='+page+'&pageSize='+pageSize;\n fetch(url).then(function(r){\n if(!r.ok)throw new Error('HTTP '+r.status);\n return r.json();\n }).then(function(d){cb(null,d);}).catch(function(e){cb(e.message||'Failed to fetch');});\n }\n window.__trFetchTestData=fetchTestData;\n\n /* Make fetchTestDetail and index available globally for other modules */\n window.__trStreamingFetchDetail=fetchTestDetail;\n window.__trTestIndex=_testIndex;\n window.__trServerPort=_serverPort;\n window.__trIsStreaming=true;\n\n ${JS_RENDER}\n ${JS_ACTIONS}\n ${JS_CONSOLE}\n ${JS_NETWORK}\n ${JS_FILTERS}\n ${JS_ARTIFACTS}\n ${JS_DOM}\n ${JS_VISUAL}\n ${JS_INTERACTIONS}\n ${JS_LAYOUT}\n ${JS_API}\n ${JS_ROUTER}\n applyTheme();\n renderRunMeta();\n renderSummary();\n renderTypeBand();\n initFilters();\n renderFilterBar();\n renderTestGrid();\n renderNeedsAttention();\n /* Route only once the health check has answered. Routing first meant a\n pasted link straight to a test always rendered \"Server unavailable\" —\n the fetch ran while _serverAvailable was still false. */\n checkServer(function(ok){routeFromHash();});\n var _lo=document.getElementById('loading-overlay');if(_lo)_lo.remove();\n})();`;\n\n return `<!-- TestRelic AI Analytics Report — streaming mode -->\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: file: 'self'; media-src blob: data: file: 'self'; connect-src 'self' http://127.0.0.1:*;\">\n<title>TestRelic AI Analytics Report</title>\n${serverPort ? `<meta name=\"report-server-port\" content=\"${serverPort}\">` : ''}\n${serverPort ? `<meta name=\"artifact-server-port\" content=\"${serverPort}\">` : ''}\n<link rel=\"icon\" type=\"image/svg+xml\" href=\"data:image/svg+xml;base64,${Buffer.from(LOGO_SVG_RAW).toString('base64')}\">\n<style>${CSS}${CSS_VISUAL}${CSS_DOM}${CSS_LAYOUT}${CSS_DETAIL_PAGE}${CSS_API}</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=\"view-overview\">\n <div id=\"summary-strip\"></div>\n <div id=\"type-band\"></div>\n <div id=\"needs-attention\"></div>\n <div id=\"filter-bar\" class=\"filter-bar\"></div>\n <div id=\"test-grid\"></div>\n </div>\n <div id=\"view-detail\" class=\"detail-page\" hidden>\n <div class=\"detail-nav\" id=\"detail-nav\"></div>\n <div class=\"drawer-body\" id=\"drawer-body\"></div>\n </div>\n</div>\n<div class=\"loading-overlay\" id=\"loading-overlay\"><div class=\"loading-spinner\"></div><div class=\"loading-text\">Loading report...</div></div>\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\">&times;</button>\n </div>\n <div class=\"filter-drawer-body\" id=\"filter-drawer-body\"></div>\n</aside>\n<script id=\"report-data\" type=\"application/json\">${streamingData}</script>\n${safeManifest ? `<script id=\"artifact-manifest-data\" type=\"application/json\">${safeManifest}</script>` : ''}\n<script>${JS_STREAMING}</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 * Uses `execFile` with an argv array (no shell) so a report path containing\n * shell metacharacters can never be interpreted as a command.\n */\n\nimport { execFile } from 'node:child_process';\n\nexport function openInBrowser(filePath: string): void {\n try {\n const platform = process.platform;\n let file: string;\n let args: string[];\n\n if (platform === 'darwin') {\n file = 'open';\n args = [filePath];\n } else if (platform === 'win32') {\n // `start` is a cmd builtin, so it must be invoked via cmd.exe. The path\n // is passed as a discrete argv entry (not concatenated into a shell\n // string); the empty \"\" is the window-title placeholder `start` expects.\n file = 'cmd';\n args = ['/c', 'start', '', filePath];\n } else {\n file = 'xdg-open';\n args = [filePath];\n }\n\n execFile(file, args, (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 * JSONL (JSON Lines) streaming writer and reader.\n *\n * Writer: Appends one JSON object per line to a file using a persistent fd.\n * Used in worker processes to stream network requests, console logs,\n * and API calls to disk instead of accumulating in memory.\n *\n * Reader: Reads a specific page of lines from a JSONL file without loading\n * the entire file into memory.\n */\n\nimport { openSync, writeSync, closeSync, unlinkSync, mkdirSync, createReadStream } from 'node:fs';\nimport { join } from 'node:path';\nimport { tmpdir } from 'node:os';\nimport { randomUUID } from 'node:crypto';\nimport { createInterface } from 'node:readline';\n\nconst TESTRELIC_TMP_DIR = join(tmpdir(), 'testrelic-data');\n\n/**\n * Append-only JSONL file writer.\n *\n * Keeps a file descriptor open for the lifetime of the writer.\n * Each `append()` call writes one JSON-serialized line synchronously,\n * so data hits disk immediately and is eligible for GC.\n */\nexport class JsonlFileWriter {\n private readonly filePath: string;\n private readonly fd: number;\n private count = 0;\n private closed = false;\n\n constructor(prefix: string) {\n mkdirSync(TESTRELIC_TMP_DIR, { recursive: true });\n this.filePath = join(TESTRELIC_TMP_DIR, `${prefix}-${randomUUID().substring(0, 8)}.jsonl`);\n this.fd = openSync(this.filePath, 'w');\n }\n\n /** Serialize `obj` as a single JSON line and write to disk. */\n append(obj: unknown): void {\n if (this.closed) return;\n const line = JSON.stringify(obj) + '\\n';\n writeSync(this.fd, line);\n this.count++;\n }\n\n /** Absolute path to the JSONL file. */\n getPath(): string {\n return this.filePath;\n }\n\n /** Number of lines written so far. */\n getCount(): number {\n return this.count;\n }\n\n /** Close the file descriptor. Must be called before the file is moved. */\n close(): void {\n if (this.closed) return;\n this.closed = true;\n try {\n closeSync(this.fd);\n } catch {\n // Ignore close errors (fd may already be closed)\n }\n }\n\n /** Remove the temporary file from disk. */\n cleanup(): void {\n try {\n unlinkSync(this.filePath);\n } catch {\n // File may already be moved/deleted\n }\n }\n}\n\n/**\n * Read a page of items from a JSONL file.\n *\n * Uses `readline` to stream through the file line by line.\n * Only parses lines within the requested page range.\n * Skips lines before the page start without parsing them.\n *\n * @param filePath — Absolute path to the JSONL file\n * @param page — 1-based page number\n * @param pageSize — Number of items per page\n * @param knownTotal — If provided, avoids counting remaining lines after the page\n */\nexport async function readJsonlPage<T>(\n filePath: string,\n page: number,\n pageSize: number,\n knownTotal?: number,\n): Promise<{ items: T[]; total: number; page: number; pageSize: number; totalPages: number }> {\n const skip = (page - 1) * pageSize;\n const items: T[] = [];\n let lineCount = 0;\n\n const rl = createInterface({\n input: createReadStream(filePath, { encoding: 'utf-8' }),\n crlfDelay: Infinity,\n });\n\n for await (const line of rl) {\n if (line.length === 0) continue;\n if (lineCount >= skip && items.length < pageSize) {\n try {\n items.push(JSON.parse(line) as T);\n } catch {\n // Skip malformed lines\n }\n }\n lineCount++;\n // Early exit if we have all items and know the total\n if (items.length >= pageSize && knownTotal !== undefined) {\n break;\n }\n }\n\n const total = knownTotal ?? lineCount;\n const totalPages = Math.max(1, Math.ceil(total / pageSize));\n return { items, total, page, pageSize, totalPages };\n}\n","/**\n * Report Server Routes — HTTP route handlers for the report server.\n *\n * Each handler reads data from the streaming report directory on disk\n * and returns JSON responses. Pagination, filtering, search, and sort\n * are supported for the test index endpoint.\n */\n\nimport { readFileSync, existsSync, readdirSync, statSync, rmSync, createReadStream } from 'node:fs';\nimport { join, extname } from 'node:path';\nimport type { IncomingMessage, ServerResponse, Server } from 'node:http';\nimport type { TestIndexEntry, StreamingReportSummary, TestDetailData } from '@testrelic/core';\nimport { readJsonlPage } from './jsonl-stream.js';\n\nconst SAFE_ID_PATTERN = /^[a-f0-9]{12}$/;\nconst TIMESTAMP_PATTERN = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}-\\d{2}-\\d{2}(-\\d+)?$/;\nconst MAX_PAGE_SIZE = 500;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nexport function sendJson(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(body));\n}\n\nexport function setCorsHeaders(res: ServerResponse): void {\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'GET, DELETE, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type');\n}\n\nfunction readJsonFile<T>(filePath: string): T | null {\n try {\n if (!existsSync(filePath)) return null;\n return JSON.parse(readFileSync(filePath, 'utf-8')) as T;\n } catch {\n return null;\n }\n}\n\nfunction getDirSize(dirPath: string): number {\n let size = 0;\n try {\n const entries = readdirSync(dirPath, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = join(dirPath, entry.name);\n if (entry.isFile()) {\n size += statSync(fullPath).size;\n } else if (entry.isDirectory()) {\n size += getDirSize(fullPath);\n }\n }\n } catch { /* skip */ }\n return size;\n}\n\n// ---------------------------------------------------------------------------\n// Route Handlers\n// ---------------------------------------------------------------------------\n\n/** GET /api/health */\nexport function handleHealth(\n _req: IncomingMessage,\n res: ServerResponse,\n reportDir: string,\n startTime: number,\n): void {\n const index = readJsonFile<TestIndexEntry[]>(join(reportDir, 'index.json'));\n sendJson(res, 200, {\n status: 'ok',\n reportMode: 'streaming',\n testCount: index?.length ?? 0,\n uptime: Math.floor((Date.now() - startTime) / 1000),\n });\n}\n\n/** GET /api/summary */\nexport function handleSummary(\n _req: IncomingMessage,\n res: ServerResponse,\n reportDir: string,\n): void {\n const summary = readJsonFile<StreamingReportSummary>(join(reportDir, 'summary.json'));\n if (!summary) {\n sendJson(res, 404, { error: 'Summary not found' });\n return;\n }\n sendJson(res, 200, summary);\n}\n\n/** GET /api/tests — paginated, filterable, searchable, sortable test index. */\nexport function handleTests(\n req: IncomingMessage,\n res: ServerResponse,\n reportDir: string,\n): void {\n const index = readJsonFile<TestIndexEntry[]>(join(reportDir, 'index.json'));\n if (!index) {\n sendJson(res, 404, { error: 'Test index not found' });\n return;\n }\n\n const url = new URL(req.url ?? '/', `http://${req.headers.host}`);\n const params = url.searchParams;\n\n // Parse query parameters\n const page = Math.max(1, parseInt(params.get('page') ?? '1', 10) || 1);\n const pageSize = Math.min(MAX_PAGE_SIZE, Math.max(1, parseInt(params.get('pageSize') ?? '100', 10) || 100));\n const statusFilter = params.get('status')?.split(',').filter(Boolean) ?? null;\n const fileFilter = params.get('file') ?? null;\n const searchQuery = params.get('search')?.toLowerCase() ?? null;\n const tagFilter = params.get('tag')?.split(',').filter(Boolean) ?? null;\n const sortField = params.get('sort') ?? 'file';\n const sortOrder = params.get('order') === 'desc' ? -1 : 1;\n\n // Filter\n let filtered = index;\n if (statusFilter && statusFilter.length > 0) {\n filtered = filtered.filter((t) => statusFilter.includes(t.status));\n }\n if (fileFilter) {\n filtered = filtered.filter((t) => t.filePath === fileFilter || t.filePath.startsWith(fileFilter + '/'));\n }\n if (searchQuery) {\n filtered = filtered.filter((t) =>\n t.title.toLowerCase().includes(searchQuery) ||\n t.filePath.toLowerCase().includes(searchQuery),\n );\n }\n if (tagFilter && tagFilter.length > 0) {\n filtered = filtered.filter((t) => tagFilter.some((tag) => t.tags.includes(tag)));\n }\n\n // Sort\n filtered = [...filtered].sort((a, b) => {\n let cmp = 0;\n switch (sortField) {\n case 'duration': cmp = a.duration - b.duration; break;\n case 'status': cmp = a.status.localeCompare(b.status); break;\n case 'title': cmp = a.title.localeCompare(b.title); break;\n case 'file':\n default: cmp = a.filePath.localeCompare(b.filePath); break;\n }\n return cmp * sortOrder;\n });\n\n // Paginate\n const totalItems = filtered.length;\n const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));\n const startIdx = (page - 1) * pageSize;\n const tests = filtered.slice(startIdx, startIdx + pageSize);\n\n sendJson(res, 200, {\n tests,\n pagination: { page, pageSize, totalItems, totalPages },\n filters: {\n status: statusFilter,\n file: fileFilter,\n search: searchQuery,\n tag: tagFilter,\n },\n });\n}\n\n/** GET /api/tests/:id — metadata for a single test (lightweight). */\nexport function handleTestDetail(\n _req: IncomingMessage,\n res: ServerResponse,\n reportDir: string,\n testId: string,\n): void {\n // Path traversal protection\n if (!SAFE_ID_PATTERN.test(testId)) {\n sendJson(res, 400, { error: 'Invalid test ID format' });\n return;\n }\n\n // v3 directory format: tests/<testId>/meta.json\n const metaPath = join(reportDir, 'tests', testId, 'meta.json');\n // Fallback: v2 single-file format: tests/<testId>.json\n const legacyPath = join(reportDir, 'tests', `${testId}.json`);\n\n const filePath = existsSync(metaPath) ? metaPath : existsSync(legacyPath) ? legacyPath : null;\n if (!filePath) {\n sendJson(res, 404, { error: `Test not found: ${testId}` });\n return;\n }\n\n try {\n const data = readFileSync(filePath, 'utf-8');\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(data);\n } catch (err) {\n sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\n/** GET /api/tests/:id/network — paginated network requests from JSONL. */\nexport async function handleTestDataFile(\n req: IncomingMessage,\n res: ServerResponse,\n reportDir: string,\n testId: string,\n dataType: 'network' | 'console' | 'api-calls',\n): Promise<void> {\n if (!SAFE_ID_PATTERN.test(testId)) {\n sendJson(res, 400, { error: 'Invalid test ID format' });\n return;\n }\n\n const fileNames: Record<string, string> = {\n 'network': 'network.jsonl',\n 'console': 'console.jsonl',\n 'api-calls': 'api-calls.jsonl',\n };\n\n const jsonlPath = join(reportDir, 'tests', testId, fileNames[dataType]);\n if (!existsSync(jsonlPath)) {\n sendJson(res, 200, { items: [], total: 0, page: 1, pageSize: 50, totalPages: 0 });\n return;\n }\n\n try {\n const url = new URL(req.url ?? '/', `http://${req.headers.host}`);\n const params = url.searchParams;\n const page = Math.max(1, parseInt(params.get('page') ?? '1', 10) || 1);\n const pageSize = Math.min(MAX_PAGE_SIZE, Math.max(1, parseInt(params.get('pageSize') ?? '50', 10) || 50));\n\n // Read known total from meta.json if available\n let knownTotal: number | undefined;\n const metaPath = join(reportDir, 'tests', testId, 'meta.json');\n if (existsSync(metaPath)) {\n try {\n const meta = JSON.parse(readFileSync(metaPath, 'utf-8')) as TestDetailData;\n switch (dataType) {\n case 'network': knownTotal = meta.networkRequestsCount; break;\n case 'console': knownTotal = meta.consoleLogsCount; break;\n case 'api-calls': knownTotal = meta.apiCallsCount; break;\n }\n } catch { /* use line counting fallback */ }\n }\n\n const result = await readJsonlPage(jsonlPath, page, pageSize, knownTotal);\n sendJson(res, 200, result);\n } catch (err) {\n sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\n/** GET /api/files — per-file test aggregation. */\nexport function handleFiles(\n _req: IncomingMessage,\n res: ServerResponse,\n reportDir: string,\n): void {\n const index = readJsonFile<TestIndexEntry[]>(join(reportDir, 'index.json'));\n if (!index) {\n sendJson(res, 404, { error: 'Test index not found' });\n return;\n }\n\n const fileMap = new Map<string, { total: number; passed: number; failed: number; flaky: number; skipped: number; timedOut: number }>();\n for (const t of index) {\n if (t.isRetry) continue; // Only count final attempts\n let stats = fileMap.get(t.filePath);\n if (!stats) {\n stats = { total: 0, passed: 0, failed: 0, flaky: 0, skipped: 0, timedOut: 0 };\n fileMap.set(t.filePath, stats);\n }\n stats.total++;\n switch (t.status) {\n case 'passed': stats.passed++; break;\n case 'failed': stats.failed++; break;\n case 'flaky': stats.flaky++; break;\n case 'skipped': stats.skipped++; break;\n case 'timedout': stats.timedOut++; break;\n }\n }\n\n const files = Array.from(fileMap.entries())\n .map(([filePath, s]) => ({ filePath, ...s }))\n .sort((a, b) => a.filePath.localeCompare(b.filePath));\n\n sendJson(res, 200, { files });\n}\n\n/** GET /api/artifacts — list artifact runs. */\nexport function handleArtifactsList(\n _req: IncomingMessage,\n res: ServerResponse,\n artifactsDir: string,\n): void {\n if (!existsSync(artifactsDir)) {\n sendJson(res, 200, { runs: [], totalSizeBytes: 0 });\n return;\n }\n try {\n const runs: Array<{ folderName: string; totalSizeBytes: number; testCount: number }> = [];\n let totalSizeBytes = 0;\n const entries = readdirSync(artifactsDir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory() || !TIMESTAMP_PATTERN.test(entry.name)) continue;\n const runDir = join(artifactsDir, entry.name);\n const size = getDirSize(runDir);\n const testDirs = readdirSync(runDir, { withFileTypes: true }).filter((e) => e.isDirectory());\n runs.push({ folderName: entry.name, totalSizeBytes: size, testCount: testDirs.length });\n totalSizeBytes += size;\n }\n runs.sort((a, b) => b.folderName.localeCompare(a.folderName));\n sendJson(res, 200, { runs, totalSizeBytes });\n } catch (err) {\n sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\n/** DELETE /api/artifacts — delete all artifact runs. */\nexport function handleArtifactsDeleteAll(\n _req: IncomingMessage,\n res: ServerResponse,\n artifactsDir: string,\n): void {\n try {\n let deletedCount = 0;\n let freedBytes = 0;\n if (existsSync(artifactsDir)) {\n const entries = readdirSync(artifactsDir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory() || !TIMESTAMP_PATTERN.test(entry.name)) continue;\n const runDir = join(artifactsDir, entry.name);\n const size = getDirSize(runDir);\n rmSync(runDir, { recursive: true, force: true });\n freedBytes += size;\n deletedCount++;\n }\n }\n sendJson(res, 200, { deletedCount, freedBytes });\n } catch (err) {\n sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\n/** DELETE /api/artifacts/:folderName — delete a single artifact run. */\nexport function handleArtifactDelete(\n _req: IncomingMessage,\n res: ServerResponse,\n artifactsDir: string,\n folderName: string,\n): void {\n if (!TIMESTAMP_PATTERN.test(folderName)) {\n sendJson(res, 400, { error: 'Invalid folder name' });\n return;\n }\n const runDir = join(artifactsDir, folderName);\n try {\n const stat = statSync(runDir);\n if (!stat.isDirectory()) {\n sendJson(res, 404, { error: 'Not found' });\n return;\n }\n } catch {\n sendJson(res, 404, { error: 'Not found' });\n return;\n }\n try {\n const freedBytes = getDirSize(runDir);\n rmSync(runDir, { recursive: true, force: true });\n sendJson(res, 200, { deleted: folderName, freedBytes });\n } catch (err) {\n sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\n/** POST /api/shutdown — gracefully shut down the server. */\nexport function handleShutdown(\n _req: IncomingMessage,\n res: ServerResponse,\n server: Server,\n): void {\n sendJson(res, 200, { status: 'shutting_down' });\n server.close();\n}\n\n/**\n * Serve a static file from disk, honouring a byte `Range`.\n *\n * A `<video>` element asks for `Range: bytes=0-` before it will play. This\n * used to answer every request with a flat `200` and the whole body, which\n * Chrome treats as a protocol violation for media: the element sat in\n * NETWORK_LOADING with readyState 0, no error event, and a permanent spinner.\n * Recordings were captured, copied and linked correctly — and never played.\n *\n * Files are streamed rather than read whole: a screencast is routinely tens of\n * megabytes and there is no reason to hold one in memory per request.\n */\nexport function serveStaticFile(\n req: IncomingMessage,\n res: ServerResponse,\n filePath: string,\n): void {\n if (!existsSync(filePath)) {\n sendJson(res, 404, { error: 'File not found' });\n return;\n }\n try {\n const size = statSync(filePath).size;\n const ext = extname(filePath).toLowerCase();\n const contentType = MIME_TYPES[ext] ?? 'application/octet-stream';\n const range = parseByteRange(req.headers.range, size);\n\n if (range === 'unsatisfiable') {\n res.writeHead(416, { 'Content-Range': `bytes */${size}`, 'Accept-Ranges': 'bytes' });\n res.end();\n return;\n }\n\n if (range) {\n res.writeHead(206, {\n 'Content-Type': contentType,\n 'Content-Length': range.end - range.start + 1,\n 'Content-Range': `bytes ${range.start}-${range.end}/${size}`,\n 'Accept-Ranges': 'bytes',\n });\n pipeOrFail(createReadStream(filePath, { start: range.start, end: range.end }), res);\n return;\n }\n\n res.writeHead(200, {\n 'Content-Type': contentType,\n 'Content-Length': size,\n 'Accept-Ranges': 'bytes',\n });\n pipeOrFail(createReadStream(filePath), res);\n } catch (err) {\n sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\n/**\n * Pipe a read stream to the response, surviving a failure mid-stream.\n *\n * The previous implementation read the whole file synchronously inside a\n * `try`, so any read error became a 500. Streaming moves the failure to an\n * async `'error'` event, and an unhandled one on a stream is rethrown by Node\n * as an uncaught exception — which kills the `serve` process and every other\n * tab viewing the report. It is reachable: a video whose file is deleted by the\n * run-delete endpoint mid-request, or a `.webm` still locked by another\n * process on Windows.\n *\n * Headers are already sent by this point, so there is no status left to send;\n * the honest outcome is to abort the response and keep the server up.\n */\nfunction pipeOrFail(stream: ReturnType<typeof createReadStream>, res: ServerResponse): void {\n stream.on('error', () => {\n stream.destroy();\n res.destroy();\n });\n res.on('close', () => { stream.destroy(); });\n stream.pipe(res);\n}\n\n/**\n * Parse a single-range `Range` header against a known file size.\n *\n * Returns null when there is nothing to honour (absent, or a form we do not\n * serve such as a multi-range request), and 'unsatisfiable' when the range\n * falls outside the file so the caller can answer 416.\n */\nexport function parseByteRange(\n header: string | undefined,\n size: number,\n): { start: number; end: number } | 'unsatisfiable' | null {\n if (!header) return null;\n const match = /^bytes=(\\d*)-(\\d*)$/.exec(header.trim());\n if (!match) return null;\n\n const [, rawStart, rawEnd] = match;\n if (rawStart === '' && rawEnd === '') return null;\n\n let start: number;\n let end: number;\n if (rawStart === '') {\n // A suffix range: the last N bytes.\n const suffix = Number(rawEnd);\n if (suffix <= 0) return 'unsatisfiable';\n start = Math.max(0, size - suffix);\n end = size - 1;\n } else {\n start = Number(rawStart);\n end = rawEnd === '' ? size - 1 : Math.min(Number(rawEnd), size - 1);\n }\n\n if (!Number.isFinite(start) || !Number.isFinite(end)) return null;\n if (size === 0 || start >= size || start > end) return 'unsatisfiable';\n return { start, end };\n}\n\nconst MIME_TYPES: Record<string, string> = {\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n '.webm': 'video/webm',\n '.mp4': 'video/mp4',\n '.json': 'application/json',\n '.html': 'text/html',\n '.css': 'text/css',\n '.js': 'text/javascript',\n '.svg': 'image/svg+xml',\n};\n","/**\n * ReportServer — unified HTTP server for serving streaming reports and artifacts.\n *\n * Binds to 127.0.0.1 only. Auto-selects a port starting at 9323.\n * 30-minute inactivity timeout with auto-shutdown.\n */\n\nimport { createServer, type Server, type IncomingMessage, type ServerResponse } from 'node:http';\nimport { join, dirname } from 'node:path';\nimport { existsSync, readdirSync } from 'node:fs';\nimport {\n setCorsHeaders,\n sendJson,\n handleHealth,\n handleSummary,\n handleTests,\n handleTestDetail,\n handleTestDataFile,\n handleFiles,\n handleArtifactsList,\n handleArtifactsDeleteAll,\n handleArtifactDelete,\n handleShutdown,\n serveStaticFile,\n} from './report-server-routes.js';\n\nconst DEFAULT_PORT = 9323;\nconst MAX_PORT_ATTEMPTS = 10;\nconst INACTIVITY_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes\n\nexport interface ReportServerOptions {\n port?: number;\n htmlPath?: string;\n}\n\nexport interface ReportServerHandle {\n port: number;\n dispose: () => Promise<void>;\n}\n\nexport function startReportServer(\n reportDir: string,\n options?: ReportServerOptions,\n): Promise<ReportServerHandle> {\n return new Promise((resolve, reject) => {\n const startPort = options?.port ?? DEFAULT_PORT;\n const startTime = Date.now();\n let inactivityTimer: ReturnType<typeof setTimeout>;\n let currentAttempt = 0;\n\n // Determine artifacts directory — check both locations\n const artifactsDir = existsSync(join(reportDir, 'artifacts'))\n ? join(reportDir, 'artifacts')\n : existsSync(join(reportDir, '..', 'artifacts'))\n ? join(reportDir, '..', 'artifacts')\n : join(reportDir, 'artifacts');\n\n function resetTimer(): void {\n clearTimeout(inactivityTimer);\n inactivityTimer = setTimeout(() => {\n server.close();\n }, INACTIVITY_TIMEOUT_MS);\n }\n\n // Resolve the HTML report path — explicit option, or find it in the parent dir\n let htmlReportPath = options?.htmlPath ?? null;\n if (!htmlReportPath) {\n const parentDir = dirname(reportDir);\n try {\n const htmlFile = readdirSync(parentDir).find(f => f.endsWith('.html'));\n if (htmlFile) htmlReportPath = join(parentDir, htmlFile);\n } catch { /* ignore */ }\n }\n\n const server: Server = createServer((req: IncomingMessage, res: ServerResponse) => {\n resetTimer();\n setCorsHeaders(res);\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n let pathname: string;\n try {\n const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);\n pathname = url.pathname;\n } catch {\n sendJson(res, 400, { error: 'Invalid URL' });\n return;\n }\n\n // --- Serve HTML report at root ---\n if (req.method === 'GET' && (pathname === '/' || pathname === '/index.html')) {\n if (htmlReportPath && existsSync(htmlReportPath)) {\n serveStaticFile(req, res, htmlReportPath);\n return;\n }\n sendJson(res, 404, { error: 'HTML report not found' });\n return;\n }\n\n // --- Report API routes ---\n if (req.method === 'GET' && pathname === '/api/health') {\n handleHealth(req, res, reportDir, startTime);\n return;\n }\n if (req.method === 'GET' && pathname === '/api/summary') {\n handleSummary(req, res, reportDir);\n return;\n }\n if (req.method === 'GET' && pathname === '/api/tests') {\n handleTests(req, res, reportDir);\n return;\n }\n if (req.method === 'GET' && pathname === '/api/files') {\n handleFiles(req, res, reportDir);\n return;\n }\n\n // GET /api/tests/:id/network, /api/tests/:id/console, /api/tests/:id/api-calls\n const testDataMatch = pathname.match(/^\\/api\\/tests\\/([a-f0-9]+)\\/(network|console|api-calls)$/);\n if (req.method === 'GET' && testDataMatch) {\n handleTestDataFile(req, res, reportDir, testDataMatch[1], testDataMatch[2] as 'network' | 'console' | 'api-calls');\n return;\n }\n\n // GET /api/tests/:id\n const testMatch = pathname.match(/^\\/api\\/tests\\/([a-f0-9]+)$/);\n if (req.method === 'GET' && testMatch) {\n handleTestDetail(req, res, reportDir, testMatch[1]);\n return;\n }\n\n // --- Artifact routes ---\n if (req.method === 'GET' && pathname === '/api/artifacts') {\n handleArtifactsList(req, res, artifactsDir);\n return;\n }\n if (req.method === 'DELETE' && pathname === '/api/artifacts') {\n handleArtifactsDeleteAll(req, res, artifactsDir);\n return;\n }\n const deleteMatch = pathname.match(/^\\/api\\/artifacts\\/(.+)$/);\n if (req.method === 'DELETE' && deleteMatch) {\n handleArtifactDelete(req, res, artifactsDir, decodeURIComponent(deleteMatch[1]));\n return;\n }\n\n // --- Static artifact files ---\n if (req.method === 'GET' && pathname.startsWith('/artifacts/')) {\n const relPath = decodeURIComponent(pathname.slice('/artifacts/'.length));\n // Path traversal protection\n if (relPath.includes('..') || relPath.includes('\\0')) {\n sendJson(res, 400, { error: 'Invalid path' });\n return;\n }\n serveStaticFile(req, res, join(artifactsDir, relPath));\n return;\n }\n\n // --- Shutdown ---\n if (req.method === 'POST' && pathname === '/api/shutdown') {\n handleShutdown(req, res, server);\n return;\n }\n\n sendJson(res, 404, { error: 'Not found' });\n });\n\n function tryListen(port: number): void {\n const errorHandler = (err: NodeJS.ErrnoException): void => {\n if (err.code === 'EADDRINUSE' && currentAttempt < MAX_PORT_ATTEMPTS) {\n currentAttempt++;\n tryListen(port + 1);\n } else {\n reject(err);\n }\n };\n server.once('error', errorHandler);\n\n server.listen(port, '127.0.0.1', () => {\n // Remove the listen-phase error handler to avoid stale handlers\n server.removeListener('error', errorHandler);\n const addr = server.address();\n if (!addr || typeof addr === 'string') {\n reject(new Error('Failed to get server address'));\n return;\n }\n resetTimer();\n resolve({\n port: addr.port,\n dispose: () => new Promise<void>((res) => {\n clearTimeout(inactivityTimer);\n server.close(() => res());\n }),\n });\n });\n }\n\n tryListen(startPort);\n });\n}\n","/**\n * ArtifactServer — DEPRECATED.\n *\n * Artifact serving is now handled by the unified report server (report-server.ts).\n * This file is kept for backward compatibility only.\n *\n * @deprecated Use `startReportServer()` from `./report-server.js` instead.\n */\n\nimport { startReportServer } from './report-server.js';\nimport type { ReportServerHandle } from './report-server.js';\n\ninterface ArtifactServerHandle {\n port: number;\n dispose: () => Promise<void>;\n}\n\n/**\n * @deprecated Use `startReportServer()` from `./report-server.js` instead.\n */\nexport async function startArtifactServer(artifactsDir: string): Promise<ArtifactServerHandle> {\n const handle: ReportServerHandle = await startReportServer(artifactsDir);\n return { port: handle.port, dispose: handle.dispose };\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, existsSync, readFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { spawn } from 'node:child_process';\nimport type { TestRunReport, ArtifactRunManifest } from '@testrelic/core';\nimport type { ResolvedConfig } from './config.js';\nimport { renderHtmlDocument, renderStreamingHtmlDocument } from './html-template.js';\nimport { openInBrowser } from './browser-open.js';\nimport { startArtifactServer } from './artifact-server.js';\nimport { startReportServer } from './report-server.js';\nimport type { ReportServerHandle } from './report-server.js';\n\n// ---------------------------------------------------------------------------\n// Utility Functions\n// ---------------------------------------------------------------------------\n\nconst HTML_ESCAPE_MAP: Record<string, string> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#39;',\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, serverPort?: number | null, manifest?: ArtifactRunManifest | null): string {\n const reportJson = JSON.stringify(report);\n const manifestJson = manifest ? JSON.stringify(manifest) : null;\n return renderHtmlDocument(reportJson, serverPort, manifestJson);\n}\n\nexport async function writeHtmlReport(report: TestRunReport, config: ResolvedConfig, manifest?: ArtifactRunManifest | null): Promise<void> {\n try {\n let serverPort: number | null = null;\n let serverHandle: ReportServerHandle | null = null;\n const isStreamingMode = config.reportMode === 'streaming' || (\n config.reportMode === 'auto' && report.timeline.length === 0 && report.summary.total >= config.streamingThreshold\n );\n\n // Step 1: Write HTML first (server port not needed — JS auto-detects via window.location)\n let html: string;\n const outputPath = config.htmlReportPath;\n const htmlDir = dirname(outputPath);\n mkdirSync(htmlDir, { recursive: true });\n\n let summaryJson = '';\n let indexJson = '[]';\n let manifestJson: string | null = null;\n\n if (isStreamingMode) {\n // The streaming HTML template reads the run-header fields (run id, dates,\n // duration, ci, metadata, shard ids) off the summary object it receives.\n // report.summary only carries the pass/fail counts, so enrich it with the\n // report's top-level run metadata — otherwise the header renders\n // \"Run —\", \"Invalid Date\", and \"Duration 0ms\".\n summaryJson = JSON.stringify({\n ...report.summary,\n testRunId: report.testRunId,\n startedAt: report.startedAt,\n completedAt: report.completedAt,\n totalDuration: report.totalDuration,\n ci: report.ci,\n metadata: report.metadata,\n shardRunIds: report.shardRunIds,\n });\n const outputDir = dirname(config.outputPath);\n const reportDir = join(outputDir, '.testrelic-report');\n try {\n // Prefer pre-built compact index (avoids expensive parse-reserialize)\n const compactPath = join(reportDir, 'index-compact.json');\n const fullPath = join(reportDir, 'index.json');\n if (existsSync(compactPath)) {\n indexJson = readFileSync(compactPath, 'utf-8');\n } else if (existsSync(fullPath)) {\n indexJson = readFileSync(fullPath, 'utf-8');\n }\n } catch { /* use empty */ }\n manifestJson = manifest ? JSON.stringify(manifest) : null;\n html = renderStreamingHtmlDocument(summaryJson, indexJson, null, manifestJson);\n } else {\n html = generateHtmlReport(report, null, manifest);\n }\n\n const tmpPath = outputPath + '.tmp';\n writeFileSync(tmpPath, html, 'utf-8');\n renameSync(tmpPath, outputPath);\n\n // Step 2: Start server\n if (isStreamingMode) {\n // Streaming mode: start a report server (needed for test detail fetching)\n if (report.ci === null) {\n const outputDir = dirname(config.outputPath);\n const reportDir = join(outputDir, '.testrelic-report');\n const htmlPath = resolve(outputPath);\n try {\n // Shut down any stale servers first to avoid port conflicts\n await shutdownExistingServers();\n\n // Try spawning a detached server (survives after Playwright exits)\n serverPort = await spawnDetachedServer(reportDir);\n\n // Fallback: start in-process server if detached spawn failed\n if (!serverPort) {\n serverHandle = await startReportServer(reportDir, { htmlPath });\n serverPort = serverHandle.port;\n // Keep Node alive so the server doesn't die with Playwright\n const keepAlive = setInterval(() => {}, 60_000);\n const cleanup = (): void => {\n clearInterval(keepAlive);\n serverHandle?.dispose();\n };\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n // Auto-shutdown after 30 minutes\n setTimeout(cleanup, 30 * 60 * 1000).unref();\n }\n\n if (serverPort) {\n process.stderr.write(\n `\\n Report server: http://127.0.0.1:${serverPort}\\n`,\n );\n // Rewrite HTML with embedded server port so file:// opens can still connect\n try {\n const updatedHtml = renderStreamingHtmlDocument(summaryJson, indexJson, serverPort, manifestJson);\n const tmpRewrite = outputPath + '.tmp';\n writeFileSync(tmpRewrite, updatedHtml, 'utf-8');\n renameSync(tmpRewrite, outputPath);\n } catch { /* non-fatal */ }\n }\n } catch {\n // Server start failure is non-fatal\n }\n }\n } else {\n // Embedded mode: start an artifact server if applicable.\n //\n // NOTE: nothing in the page talks to this server. The HTML is rendered\n // above with serverPort null and never re-rendered (streaming rewrites\n // itself once the port is known; this branch does not), so the\n // `artifact-server-port` meta tag is never emitted — and no client\n // module reads that tag or rewrites an artifact URL in any case.\n // Artifacts load as relative file:// paths, which is why the CSP has to\n // permit the file: scheme. Left running because a user can still fetch\n // artifacts from it by hand; it is a candidate for removal.\n if (config.openReport && report.ci === null && config.includeArtifacts) {\n const outputDir = dirname(config.outputPath);\n const artifactsDir = join(outputDir, 'artifacts');\n if (existsSync(artifactsDir)) {\n try {\n const server = await startArtifactServer(artifactsDir);\n serverPort = server.port;\n process.on('exit', () => { server.dispose(); });\n } catch {\n // Server start failure is non-fatal\n }\n }\n }\n }\n\n // Step 3: Auto-open in browser if configured and not in CI\n if (config.openReport && report.ci === null) {\n if (isStreamingMode && serverPort) {\n openInBrowser(`http://127.0.0.1:${serverPort}`);\n } else {\n const absolutePath = resolve(outputPath);\n openInBrowser(absolutePath);\n }\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// ---------------------------------------------------------------------------\n// Detached Server Helpers\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_PORT = 9323;\nconst MAX_PORT_ATTEMPTS = 10;\n\n/** Check if a report server is already running on the default port range. */\nasync function detectExistingServer(): Promise<number | null> {\n for (let port = DEFAULT_PORT; port < DEFAULT_PORT + MAX_PORT_ATTEMPTS; port++) {\n try {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 500);\n const resp = await fetch(`http://127.0.0.1:${port}/api/health`, { signal: controller.signal });\n clearTimeout(timer);\n if (resp.ok) return port;\n } catch {\n // Not running on this port\n }\n }\n return null;\n}\n\n/** Shut down any existing report servers on the default port range. */\nasync function shutdownExistingServers(): Promise<void> {\n for (let port = DEFAULT_PORT; port < DEFAULT_PORT + MAX_PORT_ATTEMPTS; port++) {\n try {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 1000);\n await fetch(`http://127.0.0.1:${port}/api/shutdown`, { method: 'POST', signal: controller.signal });\n clearTimeout(timer);\n } catch {\n // Not running on this port — skip\n }\n }\n // Brief pause to let ports release\n await new Promise(r => setTimeout(r, 300));\n}\n\n/** Spawn a detached report server process that survives after Playwright exits. */\nasync function spawnDetachedServer(reportDir: string): Promise<number | null> {\n // Find the CLI entry point — try multiple locations\n const candidates = [\n join(__dirname, 'cli.cjs'),\n join(__dirname, 'cli.js'),\n join(__dirname, '..', 'dist', 'cli.cjs'),\n join(__dirname, '..', 'dist', 'cli.js'),\n ];\n const cliPath = candidates.find(p => existsSync(p));\n if (!cliPath) {\n // Fallback: run in-process server (will die with Playwright)\n const handle = await startReportServer(reportDir);\n process.on('exit', () => { handle?.dispose(); });\n return handle.port;\n }\n\n return new Promise<number | null>((resolvePort) => {\n const child = spawn(process.execPath, [cliPath, 'serve', reportDir], {\n detached: true,\n stdio: ['ignore', 'ignore', 'pipe'],\n env: { ...process.env },\n });\n\n let stderr = '';\n let resolved = false;\n const timeout = setTimeout(() => {\n if (!resolved) {\n resolved = true;\n child.stderr?.removeAllListeners();\n // Server didn't announce its port in time — try detecting it\n detectExistingServer().then(resolvePort);\n }\n }, 5000);\n\n child.stderr?.on('data', (chunk: Buffer) => {\n stderr += chunk.toString();\n // Look for \"Report server running at: http://127.0.0.1:XXXX\"\n const match = stderr.match(/127\\.0\\.0\\.1:(\\d+)/);\n if (match && !resolved) {\n resolved = true;\n clearTimeout(timeout);\n child.stderr?.removeAllListeners();\n child.unref();\n resolvePort(Number(match[1]));\n }\n });\n\n child.on('error', () => {\n if (!resolved) {\n resolved = true;\n clearTimeout(timeout);\n resolvePort(null);\n }\n });\n\n child.on('exit', () => {\n if (!resolved) {\n resolved = true;\n clearTimeout(timeout);\n resolvePort(null);\n }\n });\n\n // Detach the child so it survives after Playwright exits\n child.unref();\n });\n}\n","/**\n * Numbers for a visual comparison, recovered without decoding an image.\n *\n * Playwright compares images internally and keeps the result to itself: the\n * differing-pixel count exists only as prose inside the failure message, and\n * the image dimensions only inside the PNG. Rather than decode a PNG (which\n * would mean a dependency, and Principle V says no), both are read back — the\n * count from the message Playwright already formats, the dimensions from the\n * 8 bytes of header every PNG carries.\n */\n\n/** PNG magic number: \\x89 P N G \\r \\n \\x1a \\n */\nconst PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);\n\n/** Byte offsets of width and height inside the IHDR chunk of a PNG. */\nconst IHDR_WIDTH_OFFSET = 16;\nconst IHDR_HEIGHT_OFFSET = 20;\n\n/** Enough bytes to cover the signature, the chunk header and both dimensions. */\nexport const PNG_HEADER_BYTES = 24;\n\nexport interface ImageDimensions {\n readonly width: number;\n readonly height: number;\n}\n\n/**\n * Read a PNG's pixel dimensions from its IHDR chunk.\n *\n * Only the first 24 bytes are consulted, so the caller can hand over a partial\n * read of a large file. Returns `null` for anything that is not a PNG — JPEG\n * baselines are legal in Playwright and simply go un-measured.\n */\nexport function readPngDimensions(header: Buffer): ImageDimensions | null {\n if (header.length < PNG_HEADER_BYTES) return null;\n if (!header.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)) return null;\n\n const width = header.readUInt32BE(IHDR_WIDTH_OFFSET);\n const height = header.readUInt32BE(IHDR_HEIGHT_OFFSET);\n\n // A zero dimension is not a valid PNG, and would make a ratio divide by zero.\n if (width === 0 || height === 0) return null;\n\n return { width, height };\n}\n\nexport interface ParsedDiffMessage {\n /** Differing pixel count, from `\"N pixels (ratio …) are different.\"` */\n readonly diffPixels?: number;\n /** Baseline dimensions, present only when the two images differ in size. */\n readonly expectedWidth?: number;\n readonly expectedHeight?: number;\n /** Actual dimensions, present only when the two images differ in size. */\n readonly actualWidth?: number;\n readonly actualHeight?: number;\n /** The snapshot name Playwright echoed, when the assertion supplied one. */\n readonly snapshotName?: string;\n}\n\n/**\n * `123 pixels (ratio 0.02 of all image pixels) are different.`\n *\n * The ratio in that sentence is rounded up to two decimals, so it is read past\n * deliberately — the caller recomputes it from the count and the dimensions.\n */\nconst PIXELS_DIFFERENT_RE = /(\\d+) pixels \\(ratio [\\d.]+ of all image pixels\\) are different/;\n\n/** `Expected an image 800px by 600px, received 800px by 640px.` */\nconst SIZE_MISMATCH_RE = /Expected an image (\\d+)px by (\\d+)px, received (\\d+)px by (\\d+)px/;\n\n/**\n * Whether a failure message is a size mismatch.\n *\n * The comparator cannot draw a diff across differing dimensions, so it\n * attaches expected + actual and throws instead. Callers that would otherwise\n * infer \"no baseline existed\" from a missing diff need this to tell the two\n * apart — without it a real failure was reported as a freshly written\n * baseline.\n */\nexport function isSizeMismatchFailure(message: string | null | undefined): boolean {\n if (!message) return false;\n return SIZE_MISMATCH_RE.test(message);\n}\n\n/** ` Snapshot: home.png` — only emitted when the assertion named the snapshot. */\nconst SNAPSHOT_NAME_RE = /^\\s*Snapshot:\\s*(.+?)\\s*$/m;\n\n/**\n * Pull the comparison numbers out of a Playwright snapshot failure message.\n *\n * Every field is optional and a malformed message yields an empty object: a\n * report that is missing a pixel count is a far better outcome than a reporter\n * that throws while assembling one.\n */\nexport function parseDiffMessage(message: string | null | undefined): ParsedDiffMessage {\n if (!message) return {};\n\n const result: {\n diffPixels?: number;\n expectedWidth?: number;\n expectedHeight?: number;\n actualWidth?: number;\n actualHeight?: number;\n snapshotName?: string;\n } = {};\n\n const pixels = PIXELS_DIFFERENT_RE.exec(message);\n if (pixels) {\n const count = Number.parseInt(pixels[1] as string, 10);\n if (Number.isFinite(count)) result.diffPixels = count;\n }\n\n const sizes = SIZE_MISMATCH_RE.exec(message);\n if (sizes) {\n const [, ew, eh, aw, ah] = sizes;\n const parsed = [ew, eh, aw, ah].map((v) => Number.parseInt(v as string, 10));\n if (parsed.every((v) => Number.isFinite(v))) {\n result.expectedWidth = parsed[0];\n result.expectedHeight = parsed[1];\n result.actualWidth = parsed[2];\n result.actualHeight = parsed[3];\n }\n }\n\n const name = SNAPSHOT_NAME_RE.exec(message);\n if (name) result.snapshotName = name[1];\n\n return result;\n}\n\n/**\n * Differing pixels as a fraction of the image, at full precision.\n *\n * Returns `undefined` rather than a wrong number when either input is missing,\n * so a consumer can tell \"no measurement\" apart from \"measured zero\".\n */\nexport function computeDiffRatio(\n diffPixels: number | undefined,\n dimensions: ImageDimensions | null | undefined,\n): number | undefined {\n if (diffPixels === undefined || !dimensions) return undefined;\n const total = dimensions.width * dimensions.height;\n if (total <= 0) return undefined;\n return diffPixels / total;\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 * Uses async copies with a concurrency limiter to avoid blocking the\n * event loop and exhausting file descriptors on large suites.\n */\n\nimport { mkdirSync, existsSync, openSync, readSync, closeSync } from 'node:fs';\nimport { copyFile } from 'node:fs/promises';\nimport { join, extname, relative } from 'node:path';\nimport { createHash } from 'node:crypto';\nimport type { TestArtifacts, VisualComparison } from '@testrelic/core';\nimport type { VisualCandidate } from './visual-capture.js';\nimport { readPngDimensions, computeDiffRatio, PNG_HEADER_BYTES } from './visual-metrics.js';\n\ninterface Attachment {\n name: string;\n contentType: string;\n path?: string;\n body?: Buffer;\n}\n\n// ---------------------------------------------------------------------------\n// Async copy queue with concurrency limiter\n// ---------------------------------------------------------------------------\n\nconst COPY_CONCURRENCY = 20;\nlet activeCopies = 0;\nconst pendingCopies: Array<() => void> = [];\n\n/** Queue a file copy with bounded concurrency to prevent fd exhaustion. */\nasync function enqueueCopy(src: string, dest: string): Promise<boolean> {\n if (activeCopies >= COPY_CONCURRENCY) {\n await new Promise<void>(resolve => pendingCopies.push(resolve));\n }\n activeCopies++;\n try {\n await copyFile(src, dest);\n return true;\n } catch {\n return false;\n } finally {\n activeCopies--;\n if (pendingCopies.length > 0) pendingCopies.shift()!();\n }\n}\n\n// Tracks all in-flight copy promises so onEnd can await them\nconst inflightCopies: Promise<void>[] = [];\n\n/** Wait for all queued artifact copies to complete. Call from reporter onEnd. */\nexport async function flushPendingArtifactCopies(): Promise<void> {\n if (inflightCopies.length === 0) return;\n await Promise.allSettled(inflightCopies);\n inflightCopies.length = 0;\n}\n\n// ---------------------------------------------------------------------------\n// Timestamp folder naming\n// ---------------------------------------------------------------------------\n\n/**\n * Generate a timestamp-based folder name for the current test run.\n * Format: YYYY-MM-DDTHH-mm-ss (filesystem-safe, sorts chronologically).\n *\n * If the target directory already exists (sub-second collision),\n * appends a numeric suffix: `-1`, `-2`, etc.\n */\nexport function generateTimestampFolderName(artifactsDir: string): string {\n const now = new Date();\n const pad = (n: number): string => String(n).padStart(2, '0');\n const base = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}T${pad(now.getHours())}-${pad(now.getMinutes())}-${pad(now.getSeconds())}`;\n\n if (!existsSync(join(artifactsDir, base))) {\n return base;\n }\n\n // Collision: append numeric suffix\n let suffix = 1;\n while (existsSync(join(artifactsDir, `${base}-${suffix}`))) {\n suffix++;\n }\n return `${base}-${suffix}`;\n}\n\n// ---------------------------------------------------------------------------\n// Folder name sanitization\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/** Characters of the testId kept as the folder-name disambiguator. */\nconst ID_SUFFIX_LENGTH = 8;\n\n/**\n * Build the per-test artifact folder name.\n *\n * The sanitized leaf title alone is NOT unique: the same test title can appear\n * in several spec files, in several `describe` blocks, and once per Playwright\n * project (chromium/firefox/webkit). All of those would sanitize to the same\n * folder and race each other writing `video.webm` / `screenshot.png`, leaving\n * every colliding test pointing at one file — so distinct testIds ended up\n * uploading byte-identical artifacts.\n *\n * `testId` (the reporter's stable sha256 of filePath::suiteName::fullTitle)\n * disambiguates them. It is appended as a short suffix so the folder stays\n * human-readable while being unique per test.\n *\n * @param testTitle Test title used for the readable part of the folder name.\n * @param retryCount Playwright retry index (0 for the first attempt).\n * @param testId Stable per-test identifier. When omitted the legacy\n * title-only naming is used (kept for callers that have no id).\n */\nexport function buildArtifactFolderName(\n testTitle: string,\n retryCount: number,\n testId?: string,\n): string {\n let folderName = sanitizeFolderName(testTitle);\n\n const suffix = testId ? sanitizeFolderName(testId).substring(0, ID_SUFFIX_LENGTH) : '';\n if (suffix) {\n folderName += `--${suffix}`;\n }\n\n if (retryCount > 0) {\n folderName += `--retry-${retryCount}`;\n }\n\n return folderName;\n}\n\n// ---------------------------------------------------------------------------\n// Artifact copy (non-blocking)\n// ---------------------------------------------------------------------------\n\n/**\n * Queue artifact copies from Playwright's temp locations to structured output folders.\n * Returns paths immediately (optimistic) — copies happen asynchronously.\n * Call `flushPendingArtifactCopies()` in onEnd to ensure all copies complete.\n *\n * @param testId Stable per-test id used to disambiguate the destination folder\n * when two tests share a leaf title (different spec file,\n * different describe block, or different Playwright project).\n * Without it those tests collide on one folder and overwrite each\n * other's `video.webm` / `screenshot.png`.\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 runTimestamp?: string,\n testId?: 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 const trace = attachments.find(\n (a) => a.name === 'trace' && a.path,\n );\n\n if (!screenshot && !video && !trace) {\n return null;\n }\n\n const folderName = buildArtifactFolderName(testTitle, retryCount, testId);\n\n // When runTimestamp is provided, nest under timestamp folder:\n // artifacts/<runTimestamp>/<folderName>/\n // Otherwise fall back to legacy flat structure:\n // artifacts/<folderName>/\n const artifactSegments = runTimestamp\n ? ['artifacts', runTimestamp, folderName]\n : ['artifacts', folderName];\n const artifactDir = join(outputDir, ...artifactSegments);\n const relativeParts = artifactSegments;\n const result: { screenshot?: string; video?: string; trace?: 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 if (existsSync(screenshot.path)) {\n const ext = extname(screenshot.path) || '.png';\n const destName = `screenshot${ext}`;\n const dest = join(artifactDir, destName);\n // Fire-and-forget async copy; tracked for flush in onEnd\n inflightCopies.push(enqueueCopy(screenshot.path, dest).then(() => {}));\n result.screenshot = `${relativeParts.join('/')}/${destName}`;\n }\n }\n\n if (video?.path) {\n if (existsSync(video.path)) {\n const ext = extname(video.path) || '.webm';\n const destName = `video${ext}`;\n const dest = join(artifactDir, destName);\n inflightCopies.push(enqueueCopy(video.path, dest).then(() => {}));\n result.video = `${relativeParts.join('/')}/${destName}`;\n }\n }\n\n if (trace?.path) {\n if (existsSync(trace.path)) {\n // Playwright always writes `trace.zip`; keep the extension it gave us\n // rather than assuming, the way the two above do.\n const ext = extname(trace.path) || '.zip';\n const destName = `trace${ext}`;\n const dest = join(artifactDir, destName);\n inflightCopies.push(enqueueCopy(trace.path, dest).then(() => {}));\n result.trace = `${relativeParts.join('/')}/${destName}`;\n }\n }\n\n if (!result.screenshot && !result.video && !result.trace) {\n return null;\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Visual baseline artifacts\n// ---------------------------------------------------------------------------\n\n/** Subdirectory holding every visual comparison for one test attempt. */\nconst VISUAL_DIR = 'visual';\n\n/** Characters of the stem hash appended to keep sanitized names unique. */\nconst STEM_HASH_LENGTH = 6;\n\n/**\n * Directory name for one snapshot inside a test's `visual/` folder.\n *\n * The snapshot name comes from test code and can contain path separators\n * (`toHaveScreenshot(['checkout', 'cart.png'])` is legal), so it is flattened\n * to a single segment rather than nested — that makes directory traversal\n * structurally impossible instead of merely guarded against. Flattening can\n * collide two different snapshots onto one name, so a short digest of the\n * original stem is appended to keep them apart.\n */\nexport function buildVisualFolderName(stem: string): string {\n const hash = createHash('sha256').update(stem).digest('hex').substring(0, STEM_HASH_LENGTH);\n return `${sanitizeFolderName(stem)}--${hash}`;\n}\n\n/** Read a PNG's dimensions without loading the whole file. */\nfunction readDimensions(filePath: string): { width: number; height: number } | null {\n let fd: number | null = null;\n try {\n fd = openSync(filePath, 'r');\n const header = Buffer.alloc(PNG_HEADER_BYTES);\n const bytesRead = readSync(fd, header, 0, PNG_HEADER_BYTES, 0);\n return readPngDimensions(header.subarray(0, bytesRead));\n } catch {\n return null;\n } finally {\n if (fd !== null) {\n try {\n closeSync(fd);\n } catch {\n // Nothing useful to do if the descriptor will not close.\n }\n }\n }\n}\n\n/** The four images a comparison can carry, and what each is called on disk. */\nconst VISUAL_ROLES = [\n { key: 'expectedPath', dest: 'baseline', field: 'baseline' },\n { key: 'actualPath', dest: 'actual', field: 'actual' },\n { key: 'diffPath', dest: 'diff', field: 'diff' },\n { key: 'previousPath', dest: 'previous', field: 'previous' },\n] as const;\n\n/** The DOM snapshots, which live in the same folder as the images they explain. */\nconst DOM_ROLES = [\n { key: 'baselineDomPath', dest: 'baseline.dom.json', field: 'baselineDom' },\n { key: 'actualDomPath', dest: 'actual.dom.json', field: 'actualDom' },\n] as const;\n\n/**\n * Queue copies for every image of one comparison and return its report paths.\n *\n * Mirrors `copyArtifacts`: paths come back immediately and the copies drain\n * through the same bounded queue, flushed by `flushPendingArtifactCopies()`.\n */\nfunction copyOneComparison(\n candidate: VisualCandidate,\n visualDir: string,\n relativePrefix: string,\n): VisualComparison | null {\n const folder = buildVisualFolderName(candidate.name);\n const destDir = join(visualDir, folder);\n\n try {\n mkdirSync(destDir, { recursive: true });\n } catch {\n return null;\n }\n\n const paths: Record<string, string> = {};\n let copiedAny = false;\n\n for (const role of VISUAL_ROLES) {\n const src = candidate[role.key];\n if (!src || !existsSync(src)) continue;\n\n const ext = extname(src) || '.png';\n const destName = `${role.dest}${ext}`;\n inflightCopies.push(enqueueCopy(src, join(destDir, destName)).then(() => {}));\n paths[role.field] = `${relativePrefix}/${VISUAL_DIR}/${folder}/${destName}`;\n copiedAny = true;\n }\n\n // DOM snapshots are optional: present only where the assertion was written\n // with the SDK's own `expect` and a baseline already existed to compare to.\n const dom = candidate.dom;\n if (dom) {\n for (const role of DOM_ROLES) {\n const src = dom[role.key];\n if (!src || !existsSync(src)) continue;\n inflightCopies.push(enqueueCopy(src, join(destDir, role.dest)).then(() => {}));\n paths[role.field] = `${relativePrefix}/${VISUAL_DIR}/${folder}/${role.dest}`;\n }\n }\n\n if (!copiedAny) return null;\n\n // Measured from the baseline where there is one: the ratio has to be a\n // fraction of the image the comparison was judged against, and on a size\n // mismatch the actual is a different size entirely.\n const measured = candidate.expectedPath\n ? readDimensions(candidate.expectedPath)\n : candidate.actualPath\n ? readDimensions(candidate.actualPath)\n : null;\n const ratio = computeDiffRatio(candidate.diffPixels, measured);\n\n // Where the committed baseline lives, so a reviewer (or `tr visual`) can\n // accept this run by overwriting the file Playwright reads — not the copy in\n // `paths.baseline`, which is a per-run artifact. Project-relative and POSIX:\n // the report is read on other machines, and a Windows absolute path means\n // nothing there.\n const baselinePath = candidate.expectedPath\n ? relative(process.cwd(), candidate.expectedPath).replace(/\\\\/g, '/')\n : undefined;\n\n return {\n name: candidate.name,\n status: candidate.status,\n source: candidate.source,\n ...paths,\n ...(baselinePath ? { baselinePath } : {}),\n ...(measured ? { width: measured.width, height: measured.height } : {}),\n ...(candidate.actualWidth !== undefined ? { actualWidth: candidate.actualWidth } : {}),\n ...(candidate.actualHeight !== undefined ? { actualHeight: candidate.actualHeight } : {}),\n ...(candidate.diffPixels !== undefined ? { diffPixels: candidate.diffPixels } : {}),\n ...(ratio !== undefined ? { diffPixelRatio: ratio } : {}),\n ...(dom\n ? {\n domChanges: dom.changes,\n domChangeCount: dom.total,\n domRootCauses: dom.rootCauses,\n domTruncated: dom.truncated,\n domCaptureBox: dom.captureBox,\n }\n : {}),\n };\n}\n\n/**\n * Copy every visual comparison's images into the test's artifact folder.\n *\n * Lands alongside `screenshot.png` / `video.webm` rather than in a tree of its\n * own so that one test attempt stays one directory — the artifact manifest,\n * the report server's `/artifacts/*` route and the run-delete endpoint all\n * keep working unchanged.\n *\n * @returns One entry per comparison whose images were found, or null if none.\n */\nexport function copyVisualArtifacts(\n candidates: readonly VisualCandidate[],\n testTitle: string,\n retryCount: number,\n outputDir: string,\n runTimestamp?: string,\n testId?: string,\n): VisualComparison[] | null {\n if (candidates.length === 0) return null;\n\n const folderName = buildArtifactFolderName(testTitle, retryCount, testId);\n const artifactSegments = runTimestamp\n ? ['artifacts', runTimestamp, folderName]\n : ['artifacts', folderName];\n const visualDir = join(outputDir, ...artifactSegments, VISUAL_DIR);\n const relativePrefix = artifactSegments.join('/');\n\n try {\n mkdirSync(visualDir, { recursive: true });\n } catch {\n return null;\n }\n\n const results: VisualComparison[] = [];\n for (const candidate of candidates) {\n const copied = copyOneComparison(candidate, visualDir, relativePrefix);\n if (copied) results.push(copied);\n }\n\n return results.length > 0 ? results : null;\n}\n","/**\n * The `expect` layer that attaches DOM comparison to a visual assertion.\n *\n * Wraps `toHaveScreenshot` and `toMatchVisualBaseline` so that both capture a\n * DOM snapshot, compare it against a committed baseline, and record which\n * elements differ. The pixel comparison is untouched — this runs either side of\n * it and never changes its verdict.\n *\n * Composed as a layer rather than folded into `assertion-tracker.ts`: that\n * module records API assertions, and giving it visual concerns too would give\n * it two jobs. It also returns early for anything without an API call id, which\n * is exactly why `expect(page).toHaveScreenshot()` reaches raw Playwright today.\n */\n\nimport { basename, extname } from 'node:path';\nimport { writeFileSync } from 'node:fs';\nimport { existsSync } from 'node:fs';\nimport type { DomChange } from '@testrelic/core';\nimport {\n captureDom,\n domBaselinePath,\n readDomBaseline,\n writeDomBaseline,\n serializeSnapshot,\n type BoundedTarget,\n type EvaluateTarget,\n} from './dom-snapshot.js';\nimport { diffDomSnapshots } from './dom-diff.js';\nimport { supportsSnapshotPathOptions, warnSnapshotPathUnsupported } from './snapshot-path-support.js';\nimport type { DomSnapshot } from './dom-capture.js';\n\n/** Attachment carrying the DOM comparison from the worker to the reporter. */\nexport const VISUAL_DOM_ATTACHMENT = 'testrelic-visual-dom';\n\n/** One comparison's DOM result, as it crosses the process boundary. */\nexport interface VisualDomRecord {\n readonly stem: string;\n readonly changes: readonly DomChange[];\n readonly total: number;\n /** Of the total, how many changed rather than merely shifted. */\n readonly rootCauses: number;\n readonly truncated: boolean;\n readonly captureBox: readonly [number, number, number, number];\n /** Absolute paths for the reporter to copy into the run's artifacts. */\n readonly baselineDomPath?: string;\n readonly actualDomPath?: string;\n}\n\nconst VISUAL_MATCHERS = new Set(['toHaveScreenshot', 'toMatchVisualBaseline']);\n\ninterface MinimalTestInfo {\n outputPath(name: string): string;\n attachments: ReadonlyArray<{ name: string }>;\n snapshotPath(name: string, options: { kind: 'screenshot' }): string;\n attach(name: string, options: { body?: Buffer; contentType?: string }): Promise<void>;\n config: { updateSnapshots?: unknown };\n}\n\ninterface LocatorLike extends BoundedTarget {\n page(): EvaluateTarget;\n}\n\n// SAFETY: the received value of an `expect(...)` call is genuinely unknown.\n// These two guards check for the shape each path needs and nothing more.\nfunction isLocatorLike(value: unknown): value is LocatorLike {\n if (typeof value !== 'object' || value === null) return false;\n const v = value as Record<string, unknown>;\n return typeof v.boundingBox === 'function' && typeof v.page === 'function';\n}\n\nfunction isPageLike(value: unknown): value is EvaluateTarget {\n if (typeof value !== 'object' || value === null) return false;\n const v = value as Record<string, unknown>;\n return typeof v.evaluate === 'function' && typeof v.screenshot === 'function';\n}\n\n/** The snapshot name, whichever of the two call shapes was used. */\nfunction snapshotNameOf(args: readonly unknown[]): string | null {\n const first = args[0];\n if (typeof first === 'string') return first;\n if (Array.isArray(first)) return first.join('/');\n if (typeof first === 'object' && first !== null) {\n const name = (first as Record<string, unknown>).name;\n if (typeof name === 'string') return name;\n }\n return null;\n}\n\n/** The options object, which may be the first or the second argument. */\nfunction optionsOf(args: readonly unknown[]): Record<string, unknown> {\n for (const arg of args) {\n if (typeof arg === 'object' && arg !== null && !Array.isArray(arg)) {\n return arg as Record<string, unknown>;\n }\n }\n return {};\n}\n\nfunction maskLocatorsOf(options: Record<string, unknown>): BoundedTarget[] {\n const mask = options.mask;\n if (!Array.isArray(mask)) return [];\n return mask.filter((entry): entry is BoundedTarget => {\n return (\n typeof entry === 'object' &&\n entry !== null &&\n typeof (entry as Record<string, unknown>).boundingBox === 'function'\n );\n });\n}\n\n/** `toHaveScreenshot` rejects a bare name; the resolved path needs the same form. */\nfunction withPngExtension(name: string): string {\n return /\\.(png|jpg|jpeg)$/i.test(name) ? name : `${name}.png`;\n}\n\ninterface DomComparisonInput {\n readonly received: unknown;\n readonly args: readonly unknown[];\n readonly testInfo: MinimalTestInfo;\n /** Attachment count before the assertion ran, to spot the ones it added. */\n readonly attachedBefore: number;\n}\n\nconst ROLE_SUFFIX_RE = /^(.+)-(expected|actual|diff|previous)\\.[A-Za-z0-9]+$/;\n\n/**\n * The stem the reporter will group this comparison under.\n *\n * It must be read back from the attachments the assertion just produced, not\n * derived from the snapshot path: Playwright names attachments after the\n * resolved OUTPUT path, and the two are genuinely different — a snapshot at\n * `drift-target-win32.png` attaches as `drift-target-expected.png`. Deriving it\n * the obvious way silently produced a stem that matched nothing, so every\n * natively-asserted comparison lost its element list.\n *\n * Falls back to the snapshot basename for a passing assertion, which attaches\n * no images — that is the path `toMatchVisualBaseline` takes, and it names its\n * own attachment with exactly that stem.\n */\nfunction resolveStem(testInfo: MinimalTestInfo, attachedBefore: number, fallback: string): string {\n const attachments = testInfo.attachments;\n for (let i = attachedBefore; i < attachments.length; i++) {\n const match = ROLE_SUFFIX_RE.exec(attachments[i]?.name ?? '');\n if (match) return match[1] as string;\n }\n return fallback;\n}\n\n/** Capture the DOM of whatever is about to be screenshotted. */\nasync function capture(input: DomComparisonInput): Promise<DomSnapshot | null> {\n const options = optionsOf(input.args);\n const locator = isLocatorLike(input.received) ? input.received : null;\n const target = locator ? locator.page() : (input.received as EvaluateTarget);\n\n return captureDom({\n target,\n bounded: locator,\n maskLocators: maskLocatorsOf(options),\n fullPage: options.fullPage === true,\n });\n}\n\n/** True when the run was told to rewrite baselines rather than compare against them. */\nfunction isUpdatingSnapshots(testInfo: MinimalTestInfo): boolean {\n const mode = testInfo.config?.updateSnapshots;\n return mode === 'all' || mode === 'changed';\n}\n\n/**\n * Compare the captured DOM against its baseline and record the result.\n *\n * Writes the baseline when there is not one yet, or when the run was invoked\n * with `--update-snapshots` — mirroring exactly what Playwright does with the\n * image beside it, so the two never drift apart.\n */\nasync function recordDomComparison(input: DomComparisonInput, actual: DomSnapshot): Promise<void> {\n const name = snapshotNameOf(input.args);\n if (!name) return;\n\n // Resolving the baseline needs the options form of snapshotPath (>= 1.51).\n // Without it the path comes back wrong and this whole feature silently did\n // nothing; say so once rather than leave the user guessing.\n if (!supportsSnapshotPathOptions(input.testInfo)) {\n warnSnapshotPathUnsupported('DOM comparison');\n return;\n }\n\n const screenshotPath = input.testInfo.snapshotPath(withPngExtension(name), {\n kind: 'screenshot',\n });\n const baselinePath = domBaselinePath(screenshotPath);\n const stem = resolveStem(\n input.testInfo,\n input.attachedBefore,\n basename(screenshotPath, extname(screenshotPath)),\n );\n\n const serialized = serializeSnapshot(actual);\n const updating = isUpdatingSnapshots(input.testInfo);\n const baseline = updating ? null : readDomBaseline(baselinePath);\n\n if (!baseline) {\n // No reference to compare against — write one, the way Playwright writes\n // the image baseline in the same situation. Nothing is reported as changed,\n // because nothing was compared.\n writeDomBaseline(baselinePath, serialized.json);\n return;\n }\n\n const { changes, total, rootCauses } = diffDomSnapshots(baseline, actual);\n\n let actualDomPath: string | undefined;\n try {\n actualDomPath = input.testInfo.outputPath(`${stem}.actual.dom.json`);\n writeFileSync(actualDomPath, serialized.json, 'utf-8');\n } catch {\n actualDomPath = undefined;\n }\n\n const record: VisualDomRecord = {\n stem,\n changes,\n total,\n rootCauses,\n truncated: serialized.truncatedBytes || actual.truncatedNodes > 0,\n captureBox: actual.captureBox,\n ...(existsSync(baselinePath) ? { baselineDomPath: baselinePath } : {}),\n ...(actualDomPath ? { actualDomPath } : {}),\n };\n\n await input.testInfo.attach(VISUAL_DOM_ATTACHMENT, {\n body: Buffer.from(JSON.stringify([record])),\n contentType: 'application/json',\n });\n}\n\ntype MatcherFn = (...args: unknown[]) => unknown;\n\n/**\n * Wrap an `expect` so visual assertions also compare the DOM.\n *\n * Everything that is not a visual assertion against a Page or Locator passes\n * through untouched, including `.not`, `.soft` and every other matcher.\n */\nexport function withVisualCapture(\n baseExpect: (...args: unknown[]) => unknown,\n getTestInfo: () => MinimalTestInfo | null,\n): (...args: unknown[]) => unknown {\n const wrapped = function visualExpect(received: unknown, ...rest: unknown[]): unknown {\n const matchers = baseExpect(received, ...rest);\n\n const isTarget = isLocatorLike(received) || isPageLike(received);\n if (!isTarget || typeof matchers !== 'object' || matchers === null) return matchers;\n\n return new Proxy(matchers as object, {\n get(target, prop, receiver): unknown {\n const value = Reflect.get(target, prop, receiver);\n if (typeof prop !== 'string' || !VISUAL_MATCHERS.has(prop)) return value;\n if (typeof value !== 'function') return value;\n\n const original = value as MatcherFn;\n return async function domAwareMatcher(...args: unknown[]): Promise<unknown> {\n const testInfo = getTestInfo();\n\n // Capture BEFORE the assertion: a failing comparison may leave the\n // page mid-retry, and the DOM we want is the one that was rendered.\n let snapshot: DomSnapshot | null = null;\n const attachedBefore = testInfo?.attachments.length ?? 0;\n if (testInfo) {\n try {\n snapshot = await capture({ received, args, testInfo, attachedBefore });\n } catch {\n // DOM capture never changes whether the assertion runs.\n }\n }\n\n try {\n return await original.apply(target, args);\n } finally {\n if (testInfo && snapshot) {\n try {\n await recordDomComparison({ received, args, testInfo, attachedBefore }, snapshot);\n } catch {\n // A DOM comparison problem must never alter the test outcome.\n }\n }\n }\n };\n },\n });\n };\n\n // Carry `expect.soft`, `expect.configure`, `expect.extend` and the rest\n // forward; a wrapper that drops them silently breaks unrelated assertions.\n const statics = baseExpect as unknown as Record<string, unknown>;\n const target = wrapped as unknown as Record<string, unknown>;\n for (const key of Object.keys(statics)) {\n target[key] = statics[key];\n }\n\n return wrapped;\n}\n","/**\n * Recover visual baseline comparisons from a Playwright test result.\n *\n * Playwright's snapshot matchers do not report what they compared; they only\n * leave attachments behind. `toHaveScreenshot()` / `toMatchSnapshot()` attach\n * `<stem>-expected`, `<stem>-actual`, `<stem>-diff` and, on a retried failure,\n * `<stem>-previous`. Those four share a stem, and the stem is the snapshot's\n * identity within the test.\n *\n * This module is pure — it groups names and reads a message, and touches no\n * filesystem. Copying the images out is `artifact-manager.copyVisualArtifacts`.\n */\n\nimport type { VisualSource, VisualStatus } from '@testrelic/core';\nimport { parseDiffMessage, isSizeMismatchFailure } from './visual-metrics.js';\nimport { VISUAL_DOM_ATTACHMENT, type VisualDomRecord } from './visual-expect.js';\n\ninterface Attachment {\n name: string;\n contentType: string;\n path?: string;\n body?: Buffer;\n}\n\n/**\n * A comparison found in the attachments, still pointing at Playwright's temp\n * files. `artifact-manager` turns this into the report-facing\n * `VisualComparison` once the images have been copied somewhere durable.\n */\nexport interface VisualCandidate {\n readonly name: string;\n readonly status: VisualStatus;\n readonly source: VisualSource;\n readonly expectedPath?: string;\n readonly actualPath?: string;\n readonly diffPath?: string;\n readonly previousPath?: string;\n readonly diffPixels?: number;\n readonly actualWidth?: number;\n readonly actualHeight?: number;\n /** The DOM comparison for this snapshot, when one was made. */\n readonly dom?: VisualDomRecord;\n}\n\n/** Attachment name carrying the explicit matcher's JSON side-channel. */\nexport const VISUAL_META_ATTACHMENT = 'testrelic-visual';\n\n/** The four roles a snapshot image can play, as Playwright suffixes them. */\ntype VisualRole = 'expected' | 'actual' | 'diff' | 'previous';\n\nconst VISUAL_SUFFIX_RE = /^(.+)-(expected|actual|diff|previous)(\\.[A-Za-z0-9]+)$/;\n\n/** One stem's worth of images, before a status is decided. */\ninterface StemGroup {\n expected?: string;\n actual?: string;\n diff?: string;\n previous?: string;\n}\n\n/**\n * Metadata the `toMatchVisualBaseline` matcher attaches alongside its images.\n *\n * It exists because a passing comparison is otherwise invisible: Playwright\n * attaches nothing at all when the images match, so without this the report\n * could only ever show the failures.\n */\nexport interface VisualMetaRecord {\n readonly stem: string;\n readonly name: string;\n readonly status: VisualStatus;\n}\n\n/** Read and validate the explicit matcher's metadata attachment. */\nexport function parseVisualMeta(attachments: readonly Attachment[]): Map<string, VisualMetaRecord> {\n const byStem = new Map<string, VisualMetaRecord>();\n\n for (const attachment of attachments) {\n if (attachment.name !== VISUAL_META_ATTACHMENT || !attachment.body) continue;\n try {\n const parsed: unknown = JSON.parse(attachment.body.toString('utf-8'));\n if (!Array.isArray(parsed)) continue;\n for (const entry of parsed) {\n const record = toMetaRecord(entry);\n if (record) byStem.set(record.stem, record);\n }\n } catch {\n // A malformed side-channel costs us the pass-case detail, nothing more.\n }\n }\n\n return byStem;\n}\n\nconst VALID_STATUSES: readonly string[] = ['passed', 'failed', 'new', 'updated'];\n\n// SAFETY: parsed from an attachment body written by the worker process; every\n// field is checked before use.\nfunction toMetaRecord(entry: unknown): VisualMetaRecord | null {\n if (typeof entry !== 'object' || entry === null) return null;\n const e = entry as Record<string, unknown>;\n if (typeof e.stem !== 'string' || !e.stem) return null;\n if (typeof e.name !== 'string' || !e.name) return null;\n if (typeof e.status !== 'string' || !VALID_STATUSES.includes(e.status)) return null;\n return { stem: e.stem, name: e.name, status: e.status as VisualStatus };\n}\n\n/**\n * Read the DOM comparison side-channel, keyed by the same stem the images use.\n *\n * Written by `visual-expect`; absent whenever the assertion used Playwright's\n * own `expect`, or the page had no DOM baseline yet.\n */\nexport function parseVisualDom(\n attachments: readonly Attachment[],\n): Map<string, VisualDomRecord> {\n const byStem = new Map<string, VisualDomRecord>();\n\n for (const attachment of attachments) {\n if (attachment.name !== VISUAL_DOM_ATTACHMENT || !attachment.body) continue;\n try {\n const parsed: unknown = JSON.parse(attachment.body.toString('utf-8'));\n if (!Array.isArray(parsed)) continue;\n for (const entry of parsed) {\n // SAFETY: written by the worker process; the two fields read before\n // anything else uses it are checked here.\n if (typeof entry !== 'object' || entry === null) continue;\n const record = entry as Record<string, unknown>;\n if (typeof record.stem !== 'string' || !Array.isArray(record.changes)) continue;\n byStem.set(record.stem, entry as VisualDomRecord);\n }\n } catch {\n // A malformed side-channel costs the element list, nothing more.\n }\n }\n\n return byStem;\n}\n\n/** Group every `<stem>-<role>.<ext>` attachment that has a file behind it. */\nfunction groupBySnapshotStem(attachments: readonly Attachment[]): Map<string, StemGroup> {\n const groups = new Map<string, StemGroup>();\n\n for (const attachment of attachments) {\n if (!attachment.path) continue;\n const match = VISUAL_SUFFIX_RE.exec(attachment.name);\n if (!match) continue;\n\n const [, stem, role] = match;\n const group = groups.get(stem as string) ?? {};\n group[role as VisualRole] = attachment.path;\n groups.set(stem as string, group);\n }\n\n return groups;\n}\n\n/**\n * Decide whether a stem is really a snapshot comparison.\n *\n * An ordinary user attachment named `report-actual.json` would otherwise be\n * mistaken for one. A genuine comparison always produces an `actual` next to\n * either the baseline it was compared against or the diff it produced, so\n * requiring that pair costs nothing real and rejects the lookalikes. Stems the\n * explicit matcher has vouched for skip this check — it attaches a lone\n * baseline on a pass, which is the one legitimate single-image case.\n */\nfunction isCredibleComparison(group: StemGroup): boolean {\n return Boolean(group.actual && (group.expected || group.diff));\n}\n\n/** Derive a status from the images alone, for natively-produced comparisons. */\nfunction deriveStatus(\n group: StemGroup,\n updatingSnapshots: boolean,\n failureMessage: string | null | undefined,\n): VisualStatus {\n if (group.diff) return 'failed';\n // A size mismatch also produces no diff — the comparator cannot draw one\n // when the two images have different dimensions, so it attaches expected +\n // actual and throws. Absence of a diff therefore does NOT imply \"no baseline\n // existed\": read the message before concluding that. Without this a genuine\n // failure was reported as a freshly written baseline, it was missing from\n // visualFailures / visualFailedCount / visualDiffPath, and because\n // applyMetrics only targets failed stems the parsed dimensions were dropped\n // too (making SIZE_MISMATCH_RE dead in practice).\n if (isSizeMismatchFailure(failureMessage)) return 'failed';\n // Playwright attached a baseline and an actual but no diff: it had no\n // baseline and wrote one. That is not a pass — nothing was compared.\n return updatingSnapshots ? 'updated' : 'new';\n}\n\n/**\n * Attach the pixel count to the comparison it actually describes.\n *\n * The failure message belongs to whichever assertion threw. When it echoes a\n * snapshot name, that name is matched against the stems; otherwise the numbers\n * are only trustworthy if exactly one comparison failed, and are dropped when\n * several did rather than being pinned on an arbitrary one.\n */\nfunction selectMetricsTarget(\n failed: readonly string[],\n snapshotName: string | undefined,\n): string | null {\n if (failed.length === 0) return null;\n if (failed.length === 1) return failed[0] as string;\n if (!snapshotName) return null;\n\n const base = snapshotName.replace(/\\.[A-Za-z0-9]+$/, '');\n const matches = failed.filter((stem) => stem === base || stem.endsWith(`/${base}`));\n return matches.length === 1 ? (matches[0] as string) : null;\n}\n\nexport interface ParseVisualOptions {\n /** True when the run was invoked with `--update-snapshots`. */\n readonly updatingSnapshots?: boolean;\n /** Concatenated failure messages for the test attempt. */\n readonly failureMessage?: string | null;\n}\n\n/**\n * Extract every visual comparison recorded against one test attempt.\n *\n * Returns an empty array — never throws — for a test that made no visual\n * assertions, which is the overwhelming majority of them.\n */\nexport function parseVisualAttachments(\n attachments: readonly Attachment[],\n options: ParseVisualOptions = {},\n): VisualCandidate[] {\n if (attachments.length === 0) return [];\n\n const meta = parseVisualMeta(attachments);\n const dom = parseVisualDom(attachments);\n const groups = groupBySnapshotStem(attachments);\n if (groups.size === 0) return [];\n\n const updating = options.updatingSnapshots === true;\n const candidates: VisualCandidate[] = [];\n\n for (const [stem, group] of groups) {\n const vouched = meta.get(stem);\n if (!vouched && !isCredibleComparison(group)) continue;\n\n candidates.push({\n name: vouched?.name ?? stem,\n status: vouched?.status ?? deriveStatus(group, updating, options.failureMessage),\n source: vouched ? 'toMatchVisualBaseline' : 'toHaveScreenshot',\n ...(group.expected ? { expectedPath: group.expected } : {}),\n ...(group.actual ? { actualPath: group.actual } : {}),\n ...(group.diff ? { diffPath: group.diff } : {}),\n ...(group.previous ? { previousPath: group.previous } : {}),\n ...(dom.has(stem) ? { dom: dom.get(stem) as VisualDomRecord } : {}),\n });\n }\n\n return applyMetrics(foldByName(candidates), groups, options.failureMessage);\n}\n\n/**\n * One assertion, one record.\n *\n * The explicit matcher vouches for its comparison under its own stem, and\n * Playwright attaches the same comparison's images under ITS stem. When the\n * two differ — every first run, where the matcher never attached a baseline\n * copy — the loop above produced two candidates for one name: the vouched\n * verdict, and an unvouched `toHaveScreenshot` twin carrying the images. Fold\n * the twin into the vouched record: the verdict is the matcher's, the images\n * are whichever record has them.\n */\nfunction foldByName(candidates: readonly VisualCandidate[]): VisualCandidate[] {\n const vouched = new Map<string, VisualCandidate>();\n for (const c of candidates) {\n if (c.source === 'toMatchVisualBaseline') vouched.set(c.name, c);\n }\n if (vouched.size === 0) return [...candidates];\n\n const out: VisualCandidate[] = [];\n for (const c of candidates) {\n if (c.source === 'toMatchVisualBaseline') {\n out.push(c);\n continue;\n }\n const owner = vouched.get(c.name);\n if (!owner) {\n out.push(c);\n continue;\n }\n // Merge in place so the vouched record's position in `out` is kept.\n const merged: VisualCandidate = {\n ...owner,\n ...(owner.expectedPath === undefined && c.expectedPath ? { expectedPath: c.expectedPath } : {}),\n ...(owner.actualPath === undefined && c.actualPath ? { actualPath: c.actualPath } : {}),\n ...(owner.diffPath === undefined && c.diffPath ? { diffPath: c.diffPath } : {}),\n ...(owner.previousPath === undefined && c.previousPath ? { previousPath: c.previousPath } : {}),\n ...(owner.dom === undefined && c.dom ? { dom: c.dom } : {}),\n };\n const at = out.indexOf(owner);\n if (at >= 0) out[at] = merged;\n vouched.set(c.name, merged);\n }\n return out;\n}\n\n/** Fold the parsed failure numbers into the one comparison they belong to. */\nfunction applyMetrics(\n candidates: readonly VisualCandidate[],\n groups: ReadonlyMap<string, StemGroup>,\n failureMessage: string | null | undefined,\n): VisualCandidate[] {\n const parsed = parseDiffMessage(failureMessage);\n if (parsed.diffPixels === undefined && parsed.actualWidth === undefined) {\n return [...candidates];\n }\n\n const failedStems = candidates.filter((c) => c.status === 'failed').map((c) => stemOf(c, groups));\n const target = selectMetricsTarget(failedStems, parsed.snapshotName);\n if (target === null) return [...candidates];\n\n return candidates.map((candidate) =>\n stemOf(candidate, groups) === target\n ? {\n ...candidate,\n ...(parsed.diffPixels !== undefined ? { diffPixels: parsed.diffPixels } : {}),\n ...(parsed.actualWidth !== undefined ? { actualWidth: parsed.actualWidth } : {}),\n ...(parsed.actualHeight !== undefined ? { actualHeight: parsed.actualHeight } : {}),\n }\n : candidate,\n );\n}\n\n/**\n * Recover the stem a candidate came from.\n *\n * The explicit matcher renames a candidate to the author's chosen name, so the\n * name cannot be used to look it back up among the groups.\n */\nfunction stemOf(candidate: VisualCandidate, groups: ReadonlyMap<string, StemGroup>): string {\n if (groups.has(candidate.name)) return candidate.name;\n for (const [stem, group] of groups) {\n if (\n group.actual === candidate.actualPath &&\n group.expected === candidate.expectedPath &&\n group.diff === candidate.diffPath\n ) {\n return stem;\n }\n }\n return candidate.name;\n}\n","/**\n * Strip terminal colour codes from text bound for the HTML report.\n *\n * Playwright formats assertion failures for a terminal, so the message a\n * reporter receives carries SGR escape sequences. The report rendered them\n * verbatim, which is why a failure panel read:\n *\n * Error: ESC[2mexpect(ESC[22mESC[31mpageESC[39m...\n *\n * and the Playwright call log came out sprayed with `[2m` / `[22m`. The escape\n * character survives JSON and `textContent` untouched — only a terminal ever\n * consumed it.\n */\n\n/** ESC (0x1B) and the single-byte CSI introducer (0x9B). */\nconst ESC = '\\u001B';\nconst CSI = '\\u009B';\n\n/**\n * ANSI escape sequences, both the modern CSI form and the older two-byte one.\n *\n * Deliberately broader than colour codes alone: cursor moves and erase\n * sequences turn up in wrapped tool output too, and none of them mean anything\n * in HTML.\n */\nconst ANSI_PATTERN = new RegExp(\n `[${ESC}${CSI}][[\\\\]()#;?]*(?:\\\\d{1,4}(?:;\\\\d{0,4})*)?[0-9A-PR-TZcf-nqry=><]`,\n 'g',\n);\n\n/**\n * Remove ANSI escape sequences from a string.\n *\n * Returns the input unchanged when there is nothing to strip. That fast path\n * matters: this runs on every captured step title in a large suite, and the\n * overwhelming majority contain no escapes at all.\n */\nexport function stripAnsi(text: string): string {\n if (!text) return text;\n if (text.indexOf(ESC) === -1 && text.indexOf(CSI) === -1) return text;\n return text.replace(ANSI_PATTERN, '');\n}\n\n/** Strip, tolerating null and undefined so call sites stay uncluttered. */\nexport function stripAnsiSafe(text: string | null | undefined): string | null {\n if (text === null || text === undefined) return null;\n return stripAnsi(text);\n}\n","/**\n * ArtifactIndex — builds the artifact manifest by scanning run folders.\n *\n * Scans the artifacts directory for timestamped run folders,\n * computes file sizes, and produces an ArtifactRunManifest.\n */\n\nimport { readdirSync, statSync } from 'node:fs';\nimport { join, extname, basename } from 'node:path';\nimport type {\n ArtifactRunManifest,\n ArtifactRunEntry,\n ArtifactTestEntry,\n ArtifactFile,\n} from '@testrelic/core';\n\nconst TIMESTAMP_PATTERN = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}-\\d{2}-\\d{2}(-\\d+)?$/;\nconst MANIFEST_SCHEMA_VERSION = '1.0';\n\n/** Subdirectory holding visual baseline comparisons within a test folder. */\nconst VISUAL_DIR = 'visual';\n\nfunction classifyFileType(name: string): ArtifactFile['type'] {\n const ext = extname(name).toLowerCase();\n if (['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp'].includes(ext)) return 'screenshot';\n if (['.webm', '.mp4', '.avi', '.mov'].includes(ext)) return 'video';\n // Match on the NAME, not just the extension: `.zip` alone says nothing about\n // what is inside, and the collector writes this file as `trace.zip`.\n if (ext === '.zip' && basename(name, ext) === 'trace') return 'trace';\n return 'other';\n}\n\n/**\n * Classify a file inside a `visual/<snapshot>/` folder.\n *\n * These are all images, so extension alone would call every one of them a\n * screenshot and the manifest could not tell a baseline from the render that\n * disagreed with it. The writer controls these names, so the stem is reliable.\n */\nfunction classifyVisualFileType(name: string): ArtifactFile['type'] {\n if (name.endsWith('.dom.json')) return 'dom';\n const stem = basename(name, extname(name));\n if (stem === 'baseline') return 'baseline';\n if (stem === 'diff') return 'diff';\n return classifyFileType(name);\n}\n\n/** Collect one snapshot folder's images, e.g. `visual/home--a1b2c3/`. */\nfunction scanVisualFolder(\n snapshotDir: string,\n relativeBase: string,\n snapshotName: string,\n): ArtifactFile[] {\n const files: ArtifactFile[] = [];\n try {\n for (const entry of readdirSync(snapshotDir, { withFileTypes: true })) {\n if (!entry.isFile()) continue;\n const stat = statSync(join(snapshotDir, entry.name));\n files.push({\n name: `${VISUAL_DIR}/${snapshotName}/${entry.name}`,\n type: classifyVisualFileType(entry.name),\n relativePath: `${relativeBase}/${VISUAL_DIR}/${snapshotName}/${entry.name}`,\n sizeBytes: stat.size,\n });\n }\n } catch {\n // Skip unreadable snapshot folders\n }\n return files;\n}\n\nfunction scanTestFolder(testDir: string, runFolder: string, testName: string): ArtifactTestEntry {\n const files: ArtifactFile[] = [];\n const relativeBase = `artifacts/${runFolder}/${testName}`;\n try {\n const entries = readdirSync(testDir, { withFileTypes: true });\n for (const entry of entries) {\n // Visual comparisons are the one nested case: a test can hold many named\n // snapshots, so they live a level down rather than flat beside the video.\n if (entry.isDirectory() && entry.name === VISUAL_DIR) {\n const visualDir = join(testDir, entry.name);\n for (const snapshot of readdirSync(visualDir, { withFileTypes: true })) {\n if (!snapshot.isDirectory()) continue;\n files.push(\n ...scanVisualFolder(join(visualDir, snapshot.name), relativeBase, snapshot.name),\n );\n }\n continue;\n }\n if (!entry.isFile()) continue;\n const filePath = join(testDir, entry.name);\n const stat = statSync(filePath);\n files.push({\n name: entry.name,\n type: classifyFileType(entry.name),\n relativePath: `${relativeBase}/${entry.name}`,\n sizeBytes: stat.size,\n });\n }\n } catch {\n // Skip unreadable directories\n }\n return { testName, files };\n}\n\nfunction scanRunFolder(artifactsDir: string, folderName: string): ArtifactRunEntry {\n const runDir = join(artifactsDir, folderName);\n const tests: ArtifactTestEntry[] = [];\n let totalSizeBytes = 0;\n\n try {\n const entries = readdirSync(runDir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const testEntry = scanTestFolder(join(runDir, entry.name), folderName, entry.name);\n tests.push(testEntry);\n for (const file of testEntry.files) {\n totalSizeBytes += file.sizeBytes;\n }\n }\n } catch {\n // Skip unreadable directories\n }\n\n // Parse timestamp from folder name: 2026-03-02T14-30-00 → 2026-03-02T14:30:00\n const timestamp = folderName\n .replace(/^(\\d{4}-\\d{2}-\\d{2})T(\\d{2})-(\\d{2})-(\\d{2})/, '$1T$2:$3:$4')\n .replace(/-\\d+$/, ''); // Remove collision suffix for ISO parsing\n\n tests.sort((a, b) => a.testName.localeCompare(b.testName));\n\n return {\n folderName,\n timestamp,\n totalSizeBytes,\n testCount: tests.length,\n tests,\n isCurrentRun: false,\n };\n}\n\n/**\n * Build an artifact manifest by scanning the artifacts directory.\n *\n * Discovers timestamped run folders, scans their contents,\n * and returns a sorted manifest (newest first).\n * Legacy flat folders (non-timestamped) are grouped under a \"Legacy\" entry.\n */\nexport function buildArtifactManifest(\n outputDir: string,\n currentRunTimestamp: string,\n): ArtifactRunManifest {\n const artifactsDir = join(outputDir, 'artifacts');\n const runs: ArtifactRunEntry[] = [];\n const legacyTests: ArtifactTestEntry[] = [];\n let legacySize = 0;\n\n try {\n const entries = readdirSync(artifactsDir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n\n if (TIMESTAMP_PATTERN.test(entry.name)) {\n const run = scanRunFolder(artifactsDir, entry.name);\n runs.push({\n ...run,\n isCurrentRun: entry.name === currentRunTimestamp,\n });\n } else {\n // Legacy flat folder — treat as a test entry\n const testEntry = scanTestFolder(join(artifactsDir, entry.name), entry.name, entry.name);\n legacyTests.push(testEntry);\n for (const file of testEntry.files) {\n legacySize += file.sizeBytes;\n }\n }\n }\n } catch {\n // Artifacts directory doesn't exist or is unreadable — return empty manifest\n }\n\n // Sort runs by timestamp descending (newest first)\n runs.sort((a, b) => b.timestamp.localeCompare(a.timestamp));\n\n // Add legacy group if any non-timestamped folders exist\n if (legacyTests.length > 0) {\n legacyTests.sort((a, b) => a.testName.localeCompare(b.testName));\n runs.push({\n folderName: '__legacy__',\n timestamp: '1970-01-01T00:00:00',\n totalSizeBytes: legacySize,\n testCount: legacyTests.length,\n tests: legacyTests,\n isCurrentRun: false,\n });\n }\n\n const totalSizeBytes = runs.reduce((sum, r) => sum + r.totalSizeBytes, 0);\n\n return {\n schemaVersion: MANIFEST_SCHEMA_VERSION,\n generatedAt: new Date().toISOString(),\n artifactBaseDir: 'artifacts',\n totalSizeBytes,\n runs,\n serverPort: null,\n };\n}\n","/**\n * GitignoreManager — auto-add artifact directory to .gitignore.\n *\n * Ensures the configured artifact output directory is listed\n * in the project's .gitignore. Additive-only (never removes entries).\n */\n\nimport { existsSync, readFileSync, appendFileSync } from 'node:fs';\nimport { dirname, join, relative, resolve } from 'node:path';\n\n/** Hard ceiling on git-root walk depth — see `findGitRoot`. */\nconst MAX_WALK_DEPTH = 256;\n\n/**\n * Find the repository root by walking up from startDir looking for `.git`.\n * Returns null if no git repository is found.\n *\n * `startDir` is resolved to an absolute path first: a relative artifacts dir\n * (the default is `./test-results/artifacts`) is anchored to `process.cwd()`\n * so the walk climbs the project's real ancestors. Termination uses\n * `dirname(dir) === dir`, which is idempotent at the filesystem root on every\n * platform — unlike comparing against `parse(dir).root`, which is the empty\n * string for a relative path and is never reached, spinning forever.\n */\nfunction findGitRoot(startDir: string): string | null {\n let dir = resolve(startDir);\n // Belt-and-braces: `dirname(dir) === dir` already terminates at the filesystem\n // root, but this reporter runs inside the customer's `playwright test` process,\n // where an unbounded walk is fatal rather than cosmetic. A depth cap makes it\n // structurally impossible for this loop to spin the event loop again, whatever\n // a future path edge case (UNC share, exotic mount) does to the termination\n // condition. Real trees are nowhere near this deep.\n for (let depth = 0; depth < MAX_WALK_DEPTH; depth++) {\n if (existsSync(join(dir, '.git'))) {\n return dir;\n }\n const parent = dirname(dir);\n if (parent === dir) {\n return null; // reached the filesystem root without finding `.git`\n }\n dir = parent;\n }\n return null;\n}\n\n/**\n * Ensure the artifact output directory is listed in .gitignore.\n *\n * - If not in a git repo, silently returns (no error).\n * - If the pattern is already present, does nothing.\n * - If missing, appends the pattern with a descriptive comment.\n *\n * @param artifactsDir - Absolute path to the artifacts directory\n */\nexport function ensureGitignore(artifactsDir: string): void {\n try {\n const gitRoot = findGitRoot(artifactsDir);\n if (!gitRoot) return;\n\n const gitignorePath = join(gitRoot, '.gitignore');\n const relativePath = relative(gitRoot, artifactsDir).replace(/\\\\/g, '/');\n const pattern = relativePath.endsWith('/') ? relativePath : `${relativePath}/`;\n\n if (existsSync(gitignorePath)) {\n const content = readFileSync(gitignorePath, 'utf-8');\n // Check if the pattern (or a parent pattern) already covers the directory\n const lines = content.split('\\n').map((l) => l.trim());\n if (lines.some((line) => line === pattern || line === relativePath)) {\n return; // Already covered\n }\n }\n\n // Append the pattern\n const entry = `\\n# TestRelic test artifacts\\n${pattern}\\n`;\n appendFileSync(gitignorePath, entry, 'utf-8');\n } catch {\n // Never crash — gitignore management is best-effort\n }\n}\n","/**\n * Watchdog — fail-open timeout guards for reporter phases.\n *\n * A Playwright reporter runs *inside* the customer's `playwright test` process.\n * Playwright awaits `onBegin`/`onEnd`, so anything the reporter awaits that never\n * settles wedges the entire test run — before a single test executes, in the\n * `onBegin` case. Reporting is telemetry: it must never be load-bearing for the\n * customer's tests.\n *\n * Every await in the reporter that touches the network, auth, the filesystem, or\n * a child process goes through `withTimeout`, which guarantees three things:\n *\n * 1. **It always settles.** A pending operation is abandoned after `timeoutMs`.\n * 2. **It never rejects.** A rejection becomes `undefined` — callers cannot be\n * made to crash the run by a telemetry failure.\n * 3. **It never holds the process open.** The timer is `unref()`d, so a pending\n * watchdog cannot by itself keep Node's event loop alive at exit.\n *\n * On expiry a single line goes to stderr, so a stalled run is diagnosable\n * instead of silent — the failure mode that made the 2.11.1 hang so expensive.\n */\n\n/** Sentinel distinguishing \"timer won the race\" from a legitimate `undefined`. */\nconst TIMED_OUT: unique symbol = Symbol('testrelic.timeout');\n\n/** Cloud auth + run-init during `onBegin`. Blocks the first test, so keep it tight. */\nexport const CLOUD_INIT_TIMEOUT_MS = 30_000;\n\n/** Cloud finalize + result upload during `onEnd`. Larger payloads, more headroom. */\nexport const CLOUD_FINALIZE_TIMEOUT_MS = 120_000;\n\n/** Report finalization / artifact flush — local disk I/O, should be fast. */\nexport const REPORT_FINALIZE_TIMEOUT_MS = 60_000;\n\n/** Cloud client teardown (timer cleanup + queue flush). */\nexport const CLOUD_DISPOSE_TIMEOUT_MS = 15_000;\n\n/**\n * Run `operation` with a hard upper bound, failing open on timeout or rejection.\n *\n * Resolves with the operation's value, or `undefined` if it timed out or threw.\n * Never rejects. The abandoned promise keeps running (we cannot cancel an\n * arbitrary promise) but no longer blocks the caller — the point is that the\n * customer's test run proceeds regardless.\n *\n * @param operation - Factory invoked immediately; a throwing factory is caught too.\n * @param timeoutMs - Upper bound in milliseconds.\n * @param label - Human-readable phase name used in the stderr warning.\n */\nexport async function withTimeout<T>(\n operation: () => Promise<T>,\n timeoutMs: number,\n label: string,\n): Promise<T | undefined> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const timeout = new Promise<typeof TIMED_OUT>((resolve) => {\n timer = setTimeout(() => resolve(TIMED_OUT), timeoutMs);\n // Never let a pending watchdog be the reason the process cannot exit.\n if (typeof timer?.unref === 'function') timer.unref();\n });\n\n try {\n // Invoking inside the try means a factory that throws synchronously is\n // handled identically to one that returns a rejected promise.\n const result = await Promise.race([operation(), timeout]);\n\n if (result === TIMED_OUT) {\n process.stderr.write(\n `[testrelic] ${label} timed out after ${Math.round(timeoutMs / 1000)}s — ` +\n 'continuing without it. Your tests are unaffected.\\n',\n );\n return undefined;\n }\n return result as T;\n } catch (err) {\n process.stderr.write(\n `[testrelic] ${label} failed — continuing without it. Your tests are unaffected. ` +\n `(${err instanceof Error ? err.message : String(err)})\\n`,\n );\n return undefined;\n } finally {\n // Clear eagerly so a completed phase leaves no timer behind at all.\n if (timer) clearTimeout(timer);\n }\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 ActionStep,\n ApiCallRecord,\n ApiAssertion,\n NavigationAnnotation,\n NavigationType,\n FailureDiagnostic,\n NetworkStats,\n TestStatus,\n TestType,\n TestResult as TRTestResult,\n TestArtifacts,\n ConsoleLogEntry,\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 readonly actions: ActionStep[] | null;\n readonly consoleLogs: ConsoleLogEntry[] | 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 actions: test.actions,\n consoleLogs: test.consoleLogs,\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 ActionStep,\n VisualComparison,\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 readonly actions: readonly ActionStep[] | null;\n readonly visual?: readonly VisualComparison[] | 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 */\nexport function 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. */\nexport function 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 // The published shape is readonly; the accumulator must not be. Tallying\n // into a `Readonly` alias was five assignment errors, one per key.\n const statusRanges: Record<keyof ApiCallsByStatusRange, number> =\n { '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 // Action step statistics\n let totalActionSteps = 0;\n const actionStepsByCategory: Record<string, number> = {};\n function countActions(steps: readonly ActionStep[]): void {\n for (const step of steps) {\n totalActionSteps++;\n actionStepsByCategory[step.category] = (actionStepsByCategory[step.category] ?? 0) + 1;\n if (step.children.length > 0) {\n countActions(step.children);\n }\n }\n }\n for (const test of tests) {\n if (test.actions) {\n countActions(test.actions);\n }\n }\n\n // Visual comparison counts. A `new` or `updated` snapshot is neither a\n // pass nor a failure — nothing was compared — so it is counted apart\n // rather than folded into either bucket.\n let visualChecks = 0;\n let visualFailures = 0;\n let visualNew = 0;\n let visualDomChanges = 0;\n for (const test of tests) {\n if (!test.visual) continue;\n for (const comparison of test.visual) {\n visualChecks++;\n if (comparison.status === 'failed') visualFailures++;\n else if (comparison.status === 'new' || comparison.status === 'updated') visualNew++;\n visualDomChanges += comparison.domChangeCount ?? 0;\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 totalActionSteps,\n actionStepsByCategory,\n visualChecks,\n visualFailures,\n visualNew,\n visualDomChanges,\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 htmlReportPath - Path to the HTML report file\n * @param quiet - When true, suppresses all output\n */\nexport function printConsoleSummary(\n summary: Summary,\n outputPath: string,\n htmlReportPath: 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 AI - Playwright Test Report');\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 // Action steps section (only if action steps present)\n if (summary.totalActionSteps > 0) {\n const catParts: string[] = [];\n const catMap = summary.actionStepsByCategory;\n if (catMap['ui_action']) catParts.push(`${catMap['ui_action']} UI`);\n if (catMap['assertion']) catParts.push(`${catMap['assertion']} assertions`);\n if (catMap['custom_step']) catParts.push(`${catMap['custom_step']} custom`);\n const catStr = catParts.length > 0 ? ` (${catParts.join(' ')})` : '';\n output += line(`Actions: ${summary.totalActionSteps} steps${catStr}`);\n }\n\n // Output paths\n output += line(`Report: ${htmlReportPath}`);\n output += line(`Data: ${outputPath}`);\n\n output += bottom;\n output += 'For more information visit us at https://docs.testrelic.ai\\n';\n\n process.stderr.write(output);\n}\n","/**\n * Action Step Collector\n *\n * Converts Playwright TestStep trees into ActionStep arrays.\n * Filters to user-meaningful categories, calculates video offsets,\n * and preserves parent-child hierarchy.\n */\n\nimport type { ActionStep, ActionCategory } from '@testrelic/core';\nimport { stripAnsiSafe } from './ansi.js';\n\n/** Playwright step categories we include in the report. */\nconst INCLUDED_CATEGORIES = new Set(['pw:api', 'expect', 'test.step']);\n\n/** Maps Playwright step category to our ActionCategory. */\nfunction mapCategory(pwCategory: string): ActionCategory {\n if (pwCategory === 'expect') return 'assertion';\n if (pwCategory === 'test.step') return 'custom_step';\n return 'ui_action';\n}\n\n/**\n * Minimal shape of Playwright's TestStep that we consume.\n * Avoids importing Playwright types directly in this module.\n */\nexport interface PwStepLike {\n readonly title: string;\n readonly category: string;\n readonly startTime: Date;\n readonly duration: number;\n readonly error?: { message?: string } | undefined;\n readonly steps: readonly PwStepLike[];\n}\n\n/**\n * Recursively converts a Playwright step tree into ActionStep objects.\n *\n * When a step's category is included (pw:api, expect, test.step), it is\n * converted and its children are nested underneath it.\n *\n * When a step's category is excluded (hook, fixture, attach, etc.), the\n * step itself is skipped but its children are still traversed — any\n * included descendants are \"promoted\" into the output list. This ensures\n * actions inside beforeEach/afterEach hooks are captured.\n *\n * @param step - A single Playwright TestStep\n * @param testStartTime - Test result start time for video offset calculation\n * @param output - Array to push results into (for promoted children)\n */\nfunction convertStep(\n step: PwStepLike,\n testStartTime: Date,\n output: ActionStep[],\n): void {\n if (INCLUDED_CATEGORIES.has(step.category)) {\n const children: ActionStep[] = [];\n for (const child of step.steps) {\n convertStep(child, testStartTime, children);\n }\n\n const videoOffsetMs = step.startTime.getTime() - testStartTime.getTime();\n\n output.push({\n title: step.title,\n category: mapCategory(step.category),\n timestamp: step.startTime.toISOString(),\n duration: step.duration,\n videoOffset: videoOffsetMs >= 0 ? videoOffsetMs / 1000 : null,\n status: step.error ? 'failed' : 'passed',\n // Assertion step errors carry the same terminal colour codes.\n error: stripAnsiSafe(step.error?.message),\n children,\n });\n } else {\n // Skip this step but look through to its children —\n // promotes pw:api/expect/test.step steps inside hooks/fixtures.\n for (const child of step.steps) {\n convertStep(child, testStartTime, output);\n }\n }\n}\n\n/**\n * Collects action steps from a Playwright test result's step tree.\n *\n * Filters to only user-meaningful categories (pw:api, expect, test.step),\n * looks through excluded parents (hook, fixture, attach) to capture their\n * included children, calculates video offsets, and preserves nesting hierarchy.\n *\n * @param steps - Top-level Playwright steps from result.steps\n * @param testStartTime - Test result start time (for video offset)\n * @returns Chronologically ordered array of ActionStep objects\n */\nexport function collectActionSteps(\n steps: readonly PwStepLike[],\n testStartTime: Date,\n): ActionStep[] {\n const result: ActionStep[] = [];\n for (const step of steps) {\n convertStep(step, testStartTime, result);\n }\n return result;\n}\n","/**\n * Cloud authentication — token exchange and refresh.\n *\n * Handles the JWT exchange model: API key → short-lived access token.\n * All HTTP via built-in `fetch` (Node.js 18+). Timeouts via AbortController.\n *\n * SECURITY (Constitution Principle VIII):\n * - API key is never logged or serialized beyond the single POST body.\n * - HTTPS enforced for all non-localhost communication.\n * - Error messages never expose credentials or internal paths.\n */\n\nimport { ErrorCode, createError } from '@testrelic/core';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface TokenExchangeResult {\n readonly accessToken: string;\n readonly refreshToken: string;\n readonly expiresIn: number;\n readonly orgId: string;\n readonly orgName: string;\n readonly userId: string;\n readonly userName: string;\n}\n\nexport interface TokenRefreshResult {\n readonly accessToken: string;\n readonly refreshToken: string;\n readonly expiresIn: number;\n}\n\nexport interface AuthError {\n readonly code: 'invalid_key' | 'expired_key' | 'validation_error' | 'rate_limited' | 'server_error' | 'network_error' | 'timeout';\n readonly message: string;\n readonly statusCode: number | null;\n}\n\n// ---------------------------------------------------------------------------\n// HTTPS Enforcement\n// ---------------------------------------------------------------------------\n\nconst LOCALHOST_HOSTS = new Set(['localhost', '127.0.0.1', '0.0.0.0']);\n\n/**\n * Validate that the endpoint URL uses HTTPS.\n * HTTP is allowed only for localhost addresses (dev environments).\n * Throws on violation (Constitution Principle VIII — transport security).\n */\nexport function enforceHttps(endpoint: string): void {\n const url = new URL(endpoint);\n if (url.protocol === 'https:') return;\n if (url.protocol === 'http:' && LOCALHOST_HOSTS.has(url.hostname)) return;\n throw createError(\n ErrorCode.CLOUD_CONFIG_INVALID,\n `HTTPS is required for cloud communication. Got: ${url.protocol}//${url.hostname}`,\n );\n}\n\n// ---------------------------------------------------------------------------\n// Health Check\n// ---------------------------------------------------------------------------\n\nconst HEALTH_CHECK_TIMEOUT_MS = 3000;\n\n/**\n * Lightweight connectivity probe — GET request to `{endpoint}/health`.\n * Returns true if the cloud is reachable and healthy, false otherwise.\n */\nexport async function healthCheck(endpoint: string): Promise<boolean> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), HEALTH_CHECK_TIMEOUT_MS);\n try {\n const response = await fetch(`${endpoint}/health`, {\n method: 'GET',\n signal: controller.signal,\n });\n return response.ok;\n } catch {\n return false;\n } finally {\n clearTimeout(timer);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Structured Error Parsing\n// ---------------------------------------------------------------------------\n\nasync function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Parse a structured cloud error response: `{ error: { code, message, details? } }`.\n * Returns the human-readable message, or a fallback string if parsing fails.\n */\nasync function parseCloudError(response: Response): Promise<string> {\n try {\n const body = await response.json() as Record<string, unknown>;\n const error = body.error as Record<string, unknown> | undefined;\n if (error && typeof error.message === 'string') {\n const details = error.details as Array<{ field: string; message: string }> | undefined;\n if (Array.isArray(details) && details.length > 0) {\n const detailMessages = details.map((d) => `${d.field}: ${d.message}`).join(', ');\n return `${error.message} (${detailMessages})`;\n }\n return error.message;\n }\n } catch {\n // Response body not JSON — use fallback\n }\n return `HTTP ${response.status}`;\n}\n\n// ---------------------------------------------------------------------------\n// Token Exchange\n// ---------------------------------------------------------------------------\n\n/**\n * Exchange an API key for a short-lived JWT access token.\n *\n * POST /sdk/auth/token with { apiKey }\n *\n * Returns a TokenExchangeResult on success.\n * Returns an AuthError on failure (never throws).\n */\nexport async function exchangeToken(\n endpoint: string,\n apiKey: string,\n timeout: number,\n): Promise<TokenExchangeResult | AuthError> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeout);\n try {\n const response = await fetch(`${endpoint}/sdk/auth/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ apiKey }),\n signal: controller.signal,\n });\n\n if (response.ok) {\n const body = await response.json() as Record<string, unknown>;\n return {\n accessToken: body.accessToken as string,\n refreshToken: body.refreshToken as string,\n expiresIn: body.expiresIn as number,\n orgId: body.orgId as string,\n orgName: body.orgName as string,\n userId: body.userId as string,\n userName: body.userName as string,\n };\n }\n\n // 429 — rate limited, respect Retry-After and retry once\n if (response.status === 429) {\n const retryAfter = response.headers.get('Retry-After');\n const waitMs = retryAfter ? parseInt(retryAfter, 10) * 1000 : 5000;\n if (!isNaN(waitMs) && waitMs > 0) {\n await sleep(waitMs);\n // Retry once after waiting\n const retryResponse = await fetch(`${endpoint}/sdk/auth/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ apiKey }),\n signal: controller.signal,\n });\n if (retryResponse.ok) {\n const body = await retryResponse.json() as Record<string, unknown>;\n return {\n accessToken: body.accessToken as string,\n refreshToken: body.refreshToken as string,\n expiresIn: body.expiresIn as number,\n orgId: body.orgId as string,\n orgName: body.orgName as string,\n userId: body.userId as string,\n userName: body.userName as string,\n };\n }\n }\n return { code: 'rate_limited', message: 'Rate limited during token exchange.', statusCode: 429 };\n }\n\n // 400 — validation error (e.g., malformed API key format)\n if (response.status === 400) {\n const errorMessage = await parseCloudError(response);\n return { code: 'validation_error', message: errorMessage, statusCode: 400 };\n }\n\n if (response.status === 401) {\n return { code: 'invalid_key', message: 'API key is invalid or has been revoked.', statusCode: 401 };\n }\n if (response.status === 403) {\n return { code: 'expired_key', message: 'API key has expired.', statusCode: 403 };\n }\n\n const errorMessage = await parseCloudError(response);\n return {\n code: 'server_error',\n message: errorMessage,\n statusCode: response.status,\n };\n } catch (err) {\n if (err instanceof DOMException && err.name === 'AbortError') {\n return { code: 'timeout', message: 'Token exchange timed out.', statusCode: null };\n }\n return {\n code: 'network_error',\n message: 'Failed to reach cloud for token exchange.',\n statusCode: null,\n };\n } finally {\n clearTimeout(timer);\n }\n}\n\n/** Type guard: check if result is an AuthError. */\nexport function isAuthError(result: TokenExchangeResult | AuthError): result is AuthError {\n return 'code' in result && typeof (result as AuthError).code === 'string'\n && ['invalid_key', 'expired_key', 'validation_error', 'rate_limited', 'server_error', 'network_error', 'timeout'].includes((result as AuthError).code);\n}\n\n// ---------------------------------------------------------------------------\n// Token Refresh\n// ---------------------------------------------------------------------------\n\n/**\n * Refresh an expiring access token.\n *\n * POST /sdk/auth/refresh with { refreshToken }\n *\n * Returns a TokenRefreshResult on success, or null on failure.\n */\nexport async function refreshAccessToken(\n endpoint: string,\n currentRefreshToken: string,\n timeout: number,\n): Promise<TokenRefreshResult | null> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeout);\n try {\n let response = await fetch(`${endpoint}/sdk/auth/refresh`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ refreshToken: currentRefreshToken }),\n signal: controller.signal,\n });\n\n // 429 — rate limited, respect Retry-After and retry once\n if (response.status === 429) {\n const retryAfter = response.headers.get('Retry-After');\n const waitMs = retryAfter ? parseInt(retryAfter, 10) * 1000 : 5000;\n if (!isNaN(waitMs) && waitMs > 0) {\n await sleep(waitMs);\n response = await fetch(`${endpoint}/sdk/auth/refresh`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ refreshToken: currentRefreshToken }),\n signal: controller.signal,\n });\n }\n }\n\n if (!response.ok) return null;\n\n const body = await response.json() as Record<string, unknown>;\n return {\n accessToken: body.accessToken as string,\n refreshToken: body.refreshToken as string,\n expiresIn: body.expiresIn as number,\n };\n } catch {\n return null;\n } finally {\n clearTimeout(timer);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Repo Resolution\n// ---------------------------------------------------------------------------\n\nexport interface RepoResolveResult {\n readonly repoId: string;\n readonly displayName: string;\n}\n\n/**\n * Resolve or auto-create a repo by git identity.\n *\n * POST /repos/resolve with { gitId, displayName, branch }\n *\n * Returns the resolved repo, or null on failure.\n */\nexport async function resolveRepo(\n endpoint: string,\n accessToken: string,\n gitId: string,\n displayName: string,\n timeout: number,\n branch?: string | null,\n): Promise<RepoResolveResult | null> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeout);\n try {\n const requestBody: Record<string, string> = { gitId, displayName };\n if (branch) requestBody.branch = branch;\n const response = await fetch(`${endpoint}/repos/resolve`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${accessToken}`,\n },\n body: JSON.stringify(requestBody),\n signal: controller.signal,\n });\n\n if (!response.ok) return null;\n\n const responseBody = await response.json() as Record<string, unknown>;\n return {\n repoId: responseBody.repoId as string,\n displayName: responseBody.displayName as string,\n };\n } catch {\n return null;\n } finally {\n clearTimeout(timer);\n }\n}\n","/**\n * Git metadata collection and project ID normalization.\n *\n * Collects branch, commit, message, and remote URL using git CLI.\n * Each field fails independently — a missing remote doesn't prevent\n * branch detection.\n *\n * Uses `execSync` for simplicity (< 100ms total, runs at startup only).\n */\n\nimport { execSync } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\nimport { basename, join } from 'node:path';\nimport type { GitMetadata } from '@testrelic/core';\n\n// ---------------------------------------------------------------------------\n// Git Command Execution\n// ---------------------------------------------------------------------------\n\nconst GIT_TIMEOUT_MS = 5000;\n\n/** Run a git command and return stdout, or null on any failure. */\nfunction execGit(command: string, cwd?: string): string | null {\n try {\n const result = execSync(command, {\n cwd,\n timeout: GIT_TIMEOUT_MS,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n return result.trim() || null;\n } catch {\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Git Metadata Collection\n// ---------------------------------------------------------------------------\n\n/**\n * Collect git metadata from the current working directory.\n * Each field is collected independently — failures don't cascade.\n * Non-git projects return all nulls without error.\n */\nexport function collectGitMetadata(cwd?: string): GitMetadata {\n const branch = execGit('git rev-parse --abbrev-ref HEAD', cwd);\n const commitSha = execGit('git rev-parse --short HEAD', cwd);\n const commitMessage = execGit('git log -1 --pretty=%s', cwd);\n const commitAuthor =\n execGit('git log -1 --format=%an', cwd) ??\n execGit('git config user.name', cwd);\n const rawRemoteUrl = getRemoteUrl(cwd);\n const remoteUrl = rawRemoteUrl ? normalizeGitRemoteUrl(rawRemoteUrl) : null;\n\n return { branch, commitSha, commitMessage, commitAuthor, remoteUrl };\n}\n\n/**\n * Get the remote URL, preferring `origin` but falling back to the first\n * available remote. Handles monorepos where the remote may not be named `origin`.\n */\nfunction getRemoteUrl(cwd?: string): string | null {\n // Try origin first (most common)\n const originUrl = execGit('git remote get-url origin', cwd);\n if (originUrl) return originUrl;\n\n // List all remotes and use the first one\n const remotes = execGit('git remote', cwd);\n if (!remotes) return null;\n\n const firstRemote = remotes.split('\\n')[0]?.trim();\n if (!firstRemote) return null;\n\n return execGit(`git remote get-url ${firstRemote}`, cwd);\n}\n\n// ---------------------------------------------------------------------------\n// Remote URL Normalization\n// ---------------------------------------------------------------------------\n\n/**\n * Normalize a git remote URL into a stable project identifier.\n *\n * Strips protocol, replaces `:` with `/`, strips `.git` suffix, lowercases.\n *\n * Examples:\n * https://github.com/acme/checkout-service.git → github.com/acme/checkout-service\n * git@github.com:acme/checkout-service.git → github.com/acme/checkout-service\n */\nexport function normalizeGitRemoteUrl(url: string): string {\n let normalized = url.trim();\n\n // Strip protocol (https://, http://, git://, ssh://)\n normalized = normalized.replace(/^[a-z+]+:\\/\\//, '');\n\n // Handle SSH format: git@host:path → host/path\n normalized = normalized.replace(/^[^@]+@/, '');\n normalized = normalized.replace(/:(?!\\d)/, '/');\n\n // Strip .git suffix\n normalized = normalized.replace(/\\.git$/, '');\n\n // Strip trailing slashes\n normalized = normalized.replace(/\\/+$/, '');\n\n // Strip authentication (user:pass@)\n normalized = normalized.replace(/^[^@/]+@/, '');\n\n return normalized.toLowerCase();\n}\n\n// ---------------------------------------------------------------------------\n// Repo Display Name\n// ---------------------------------------------------------------------------\n\n/**\n * Derive a human-readable repo display name from a normalized git remote URL\n * or from a directory path.\n *\n * Returns the last path segment (e.g., \"checkout-service\" from \"github.com/acme/checkout-service\").\n */\nexport function deriveRepoDisplayName(normalizedUrl: string): string {\n const segments = normalizedUrl.split('/').filter(Boolean);\n return segments[segments.length - 1] ?? normalizedUrl;\n}\n\n/**\n * Read the `name` field from a package.json in the given directory.\n * Returns null if the file doesn't exist or can't be parsed.\n */\nexport function readPackageJsonName(dirPath: string): string | null {\n try {\n const raw = readFileSync(join(dirPath, 'package.json'), 'utf-8');\n const pkg = JSON.parse(raw) as Record<string, unknown>;\n return typeof pkg.name === 'string' && pkg.name.length > 0 ? pkg.name : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Derive a project git ID for non-git projects.\n * Tries package.json `name` first, falls back to `local/{dirname}`.\n */\nexport function deriveNonGitProjectId(dirPath: string): string {\n const pkgName = readPackageJsonName(dirPath);\n if (pkgName) return pkgName;\n return `local/${basename(dirPath)}`;\n}\n","/**\n * Maps TESTRELIC_RUN_TYPE to the cloud ingest enum so monitoring dashboards\n * (which filter by run_type) receive runs in the correct bucket.\n */\nconst ALLOWED = new Set(['smoke', 'regression', 'nightly', 'ci']);\n\nexport type SdkRunType = 'smoke' | 'regression' | 'nightly' | 'ci';\n\nexport function resolveSdkRunTypeFromEnv(): SdkRunType | undefined {\n const raw = process.env.TESTRELIC_RUN_TYPE?.trim().toLowerCase();\n if (!raw) return undefined;\n if (!ALLOWED.has(raw)) return undefined;\n return raw as SdkRunType;\n}\n","/**\n * Deadlines for the upload path — the thing that was missing.\n *\n * ⛔ THE BUG THIS EXISTS TO KILL. Every request in `cloud-upload.ts` and\n * `cloud-artifact-upload.ts` called bare `fetch(url, init)` with no `signal`,\n * so each one inherited undici's defaults — 300s to receive headers, 300s\n * between body chunks — and each sat inside a 3-attempt retry loop. One stalled\n * connection therefore held the process for roughly 3 × 5 minutes plus backoff.\n * The upload runs inside Playwright's `onEnd`, which the runner must await\n * before it can exit, so the visible symptom was \"the tests passed and then\n * Studio hung\", with Ctrl+C doing nothing: an in-flight request with no abort\n * signal is not cancelled by a signal handler. Observed at 11m49s on a real run.\n *\n * ⛔ AND THE CONFIGURED TIMEOUT NEVER REACHED IT. `timeout` (default 30s) was\n * threaded into `cloud-auth.ts` — the token exchange and the health check — and\n * nowhere else. A tester who set it believed they had bounded uploading; they\n * had bounded the handshake.\n *\n * Three tools, deliberately separate:\n *\n * · `withTimeout` — one request, one AbortController, always cleared.\n * · `putDeadlineMs` — a transfer's budget scales with its size, because a\n * 200 MB trace legitimately takes longer than a 2 KB JSON POST and a flat\n * cap would abandon healthy uploads on slow links.\n * · `Budget` — the phase-level circuit breaker: once the whole upload has\n * spent its allowance, later requests fail immediately rather than each\n * paying its own timeout. Ten artifacts × one stall each is the difference\n * between one late run and an unusable app.\n */\n\n/** What the upload phase gets in total, when nothing else is configured. */\nexport const DEFAULT_UPLOAD_BUDGET_MS = 120_000;\n\n/** Floor and ceiling for a single presigned PUT, whatever the file size says. */\nexport const PUT_MIN_MS = 30_000;\nexport const PUT_MAX_MS = 600_000;\n\n/** Assumed throughput for sizing a transfer's deadline: 1 MB/s is slow enough\n * to cover a hotel wifi and fast enough that a wedged socket still gives up. */\nconst ASSUMED_BYTES_PER_MS = 1_000;\n\n/**\n * Run one fetch under a deadline.\n *\n * The timer is cleared in `finally` — a pending `setTimeout` keeps Node's event\n * loop alive, which in a reporter that has just finished its work is its own\n * flavour of \"the process won't exit\".\n */\nexport async function withTimeout(\n url: string,\n init: RequestInit,\n timeoutMs: number,\n): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), Math.max(1, timeoutMs));\n try {\n return await fetch(url, { ...init, signal: controller.signal });\n } finally {\n clearTimeout(timer);\n }\n}\n\n/** How long one presigned PUT may take, from the size it declares. */\nexport function putDeadlineMs(sizeBytes: number, base: number): number {\n const scaled = base + Math.ceil(Math.max(0, sizeBytes) / ASSUMED_BYTES_PER_MS);\n return Math.min(PUT_MAX_MS, Math.max(PUT_MIN_MS, scaled));\n}\n\n/**\n * The upload phase's total allowance.\n *\n * ⛔ `remaining()` returning 0 is a REFUSAL, not a hint: callers must stop\n * rather than start a request with no time left, because a request started at\n * the boundary still pays its own full timeout — which is exactly how \"one\n * stalled artifact\" became \"ten stalled artifacts\".\n */\nexport class Budget {\n private readonly deadline: number;\n\n constructor(totalMs: number = DEFAULT_UPLOAD_BUDGET_MS, now: number = Date.now()) {\n this.deadline = now + Math.max(0, totalMs);\n }\n\n remaining(now: number = Date.now()): number {\n return Math.max(0, this.deadline - now);\n }\n\n expired(now: number = Date.now()): boolean {\n return this.remaining(now) === 0;\n }\n\n /** The budget for one request: never more than what the phase has left. */\n slice(requestMs: number, now: number = Date.now()): number {\n return Math.min(requestMs, this.remaining(now));\n }\n}\n","/**\n * Cloud upload — batch and realtime upload functions.\n *\n * Handles payload construction, gzip compression for large payloads,\n * retry with exponential backoff, rate limit handling, and structured\n * failure reporting.\n *\n * Constitution Principle VIII: HTTPS enforced via cloud-auth.ts.\n * Never throws to caller — returns structured results.\n */\n\nimport { gzip } from 'node:zlib';\nimport { promisify } from 'node:util';\nimport type {\n TestRunReport,\n GitMetadata,\n CIMetadata,\n Summary,\n TimelineEntry,\n TimelineStep,\n} from '@testrelic/core';\nimport type { SdkRunType } from './run-type-env.js';\nimport { resolveSdkRunTypeFromEnv } from './run-type-env.js';\nimport { Budget, withTimeout } from './cloud-deadline.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface TestResultForUpload {\n readonly testId: string;\n readonly title: string;\n readonly status: string;\n readonly duration: number;\n readonly suiteName: string;\n readonly filePath: string;\n readonly testType: string;\n readonly isFlaky: boolean;\n readonly startedAt: string;\n readonly completedAt: string;\n readonly tags: readonly string[];\n readonly failure: unknown | null;\n /** Current attempt index (0 = first run, 1 = first retry, etc.) */\n readonly retry: number;\n /** Total number of retries that occurred (test.results.length - 1) */\n readonly retryCount: number;\n /** Human-readable retry outcome, e.g. \"passed on retry 1\", or null if no retries */\n readonly retryStatus: string | null;\n /** Testing platform/origin, e.g. \"Local\", \"GitHub Actions\", \"BrowserStack\" */\n readonly source?: string;\n /** UI tests — browser name, e.g. \"Chrome\", \"Firefox\", \"WebKit\" */\n readonly browser?: string;\n /** UI tests — operating system, e.g. \"Windows\", \"macOS\", \"Linux\" */\n readonly os?: string;\n /** API tests — test runner, e.g. \"Newman Runner\", \"Playwright API Client\" */\n readonly runner?: string;\n /** API tests — protocol, e.g. \"rest\", \"graphql\", \"grpc\". Drives API-workspace routing. */\n readonly apiProtocol?: string;\n /** UI tests — viewport resolution, e.g. \"1920x1080\" */\n readonly resolution?: string;\n /**\n * Playwright action steps (click, fill, expect, etc.) included in the batch\n * payload as a reliable fallback for the fire-and-forget per-test realtime\n * upload. If the realtime call succeeds first, the server deduplicates by\n * skipping reingest. If it fails silently, the batch guarantees delivery.\n */\n readonly actions?: readonly Record<string, unknown>[];\n /** Console log entries captured during this test — batch fallback. */\n readonly consoleLogs?: readonly Record<string, unknown>[];\n /** Network/HAR request entries captured during this test — batch fallback. */\n readonly networkRequests?: readonly Record<string, unknown>[];\n /**\n * Navigation entries captured during this test — batch fallback. This is the\n * fix for Test Navigation never reaching the server under the default 'batch'\n * upload strategy: the realtime per-test path (which carries navigations) never\n * runs, and the streaming report's timeline is empty, so the batch per-test\n * payload is the only delivery route. Small (a few URL entries), so the payload\n * size guard above never strips it.\n */\n readonly navigations?: readonly Record<string, unknown>[];\n /**\n * Visual regression comparisons for this test — baseline/actual/diff paths,\n * the pixel counts, and the DOM elements that differed.\n *\n * The IMAGES have always been uploaded as artifacts; this is the structured\n * result that says what they mean. Without it the platform receives three\n * PNGs with no record of which snapshot they belong to, whether the\n * comparison passed, how much changed, or which element caused it — so a\n * reviewer has the pictures and nothing to decide with.\n *\n * Bounded, not small: the capped element list can reach tens of KB per\n * comparison, which is why `visual` is one of the HEAVY_TEST_KEYS the\n * payload-size tiers shed.\n */\n readonly visual?: readonly Record<string, unknown>[];\n /**\n * API request/response calls in the canonical `apiReport.apiCalls` shape —\n * batch fallback so the API-workspace inspector renders req/res even when the\n * fire-and-forget realtime per-test upload is lost. Without this the batch\n * re-ingest would overwrite the realtime apiReport with one that has no calls.\n */\n readonly apiCalls?: readonly Record<string, unknown>[];\n /** Captured API assertions in the canonical `protocolAssertions` shape — batch fallback. */\n readonly protocolAssertions?: readonly Record<string, unknown>[];\n}\n\nexport interface RunUploadPayload {\n readonly runId: string;\n readonly repoGitId: string;\n readonly startedAt: string;\n readonly summary: Summary;\n readonly timeline: readonly (TimelineEntry | TimelineStep)[];\n readonly tests?: readonly TestResultForUpload[];\n readonly branch?: string;\n readonly commit?: string;\n readonly commitMessage?: string;\n readonly commitAuthor?: string;\n readonly finishedAt?: string;\n readonly duration?: number;\n readonly ciProvider?: string;\n readonly ciRunUrl?: string;\n /** Where tests ran: \"local\", \"github-actions\", \"gitlab-ci\", etc. */\n readonly environment?: string;\n /** Cloud-farm platform this run executed on (BrowserStack / LambdaTest). */\n readonly cloudPlatform?: 'browserstack' | 'lambdatest';\n readonly cloudBuildId?: string;\n readonly cloudSessionId?: string;\n /** When set, monitoring dashboards (/monitoring/*) include this run for that bucket. */\n readonly runType?: SdkRunType;\n /** Run-level API signal — \"api\" when every test in the run is an API test. Lets the cloud open the API workspace without a ?testType=api URL param. */\n readonly testType?: string;\n /** Dominant API protocol for the run (e.g. \"rest\"). Pairs with testType==='api'. */\n readonly apiProtocol?: string;\n /** Free-form run metadata; mirrors the cloud contract (`testType`/`primaryProtocol`) so already-deployed servers classify the run too. */\n readonly metadata?: Record<string, unknown>;\n}\n\nexport interface UploadResult {\n readonly success: boolean;\n readonly statusCode: number | null;\n readonly error: string | null;\n}\n\nexport interface UploadFailure {\n readonly success: false;\n readonly reason: string;\n readonly statusCode: number | null;\n readonly payload: Record<string, unknown>;\n readonly targetEndpoint: string;\n readonly method: string;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst GZIP_THRESHOLD_BYTES = 1_048_576; // 1MB\nconst RETRY_DELAYS_MS = [1000, 3000, 9000];\nconst MAX_RETRIES = 3;\n/** Per-request ceiling for the JSON calls (run upload, init, finalize). The\n * config's own `timeout` overrides it — see cloud-deadline for why bare\n * `fetch` could not be left to undici's 5-minute defaults. */\nconst DEFAULT_REQUEST_TIMEOUT_MS = 30_000;\n/** Strip per-test consoleLogs/networkRequests from the batch if the total\n * serialized payload exceeds this ceiling. The batch request is gzipped before\n * upload (~10x smaller) and the server accepts 100MB *decompressed*, so this\n * sits just under that limit — detail is kept for all realistic runs and the\n * strip is a last resort (with a warning), never a silent drop. The default\n * 'batch' strategy never runs the realtime per-test path, so the batch arrays\n * are the ONLY copies the cloud receives. */\nconst MAX_BATCH_PAYLOAD_BYTES = 90 * 1_048_576; // 90MB raw (server inflates to <=100MB)\n\n// ---------------------------------------------------------------------------\n// Payload Construction\n// ---------------------------------------------------------------------------\n\nexport interface RunApiSignal {\n readonly testType: 'api';\n readonly apiProtocol: string;\n readonly metadata: { readonly testType: 'api'; readonly primaryProtocol: string };\n}\n\nexport interface RunTestTypeSignal {\n /** Run-level test type the cloud labels: Web / API / Mobile / Unit. */\n readonly testType: 'e2e' | 'api' | 'mobile' | 'unit';\n /** Dominant API protocol — present only when testType === 'api'. */\n readonly apiProtocol?: string;\n /** Mirrors the cloud contract — present only when testType === 'api'. */\n readonly metadata?: { readonly testType: 'api'; readonly primaryProtocol: string };\n}\n\n/** Non-api per-test slugs eligible for the run-level most-common fallback. */\nconst RUN_NONAPI_SLUGS = new Set(['e2e', 'mobile', 'unit']);\n\n/**\n * Run-level test-type classification for a Playwright run.\n *\n * 1. EVERY test is an API test ⇒ 'api' (+ dominant protocol). Requiring *all*\n * tests avoids misrouting a mixed UI+API run to the API report, and keeps\n * the API-workspace routing (which keys on testType === 'api') intact.\n * 2. else the most-common recognized non-api slug ({e2e, mobile, unit}).\n * 3. else 'e2e' — Playwright is browser-driven, so Web is the capture-truth\n * default for an otherwise-unclassified run.\n */\nexport function deriveRunTestType(\n tests: readonly TestResultForUpload[] | undefined,\n): RunTestTypeSignal {\n if (tests && tests.length > 0 && tests.every((t) => t.testType === 'api')) {\n const counts = new Map<string, number>();\n for (const t of tests) {\n const proto = t.apiProtocol ?? 'rest';\n counts.set(proto, (counts.get(proto) ?? 0) + 1);\n }\n let primaryProtocol = 'rest';\n let max = 0;\n for (const [proto, n] of counts) {\n if (n > max) { max = n; primaryProtocol = proto; }\n }\n return { testType: 'api', apiProtocol: primaryProtocol, metadata: { testType: 'api', primaryProtocol } };\n }\n\n const counts = new Map<string, number>();\n for (const t of tests ?? []) {\n if (RUN_NONAPI_SLUGS.has(t.testType)) counts.set(t.testType, (counts.get(t.testType) ?? 0) + 1);\n }\n let mostCommon: 'e2e' | 'mobile' | 'unit' | null = null;\n let max = 0;\n for (const [slug, n] of counts) {\n if (n > max) { max = n; mostCommon = slug as 'e2e' | 'mobile' | 'unit'; }\n }\n return { testType: mostCommon ?? 'e2e' };\n}\n\n/**\n * Back-compat wrapper preserving the original API-only signal shape (some\n * callers/tests only care about the all-API case). Returns null for non-API runs.\n */\nexport function deriveRunApiSignal(\n tests: readonly TestResultForUpload[] | undefined,\n): RunApiSignal | null {\n const sig = deriveRunTestType(tests);\n if (sig.testType !== 'api') return null;\n return { testType: 'api', apiProtocol: sig.apiProtocol ?? 'rest', metadata: sig.metadata ?? { testType: 'api', primaryProtocol: sig.apiProtocol ?? 'rest' } };\n}\n\nexport function buildUploadPayload(\n report: TestRunReport,\n repoGitId: string,\n git: GitMetadata | null,\n ci: CIMetadata | null,\n tests?: readonly TestResultForUpload[],\n cloudFarm?: {\n cloudPlatform?: 'browserstack' | 'lambdatest';\n cloudBuildId?: string;\n cloudSessionId?: string;\n },\n): RunUploadPayload {\n const environment = cloudFarm?.cloudPlatform\n ? cloudFarm.cloudPlatform\n : ci?.provider && ci.provider !== 'unknown' ? ci.provider : 'local';\n const runType = resolveSdkRunTypeFromEnv();\n // Derive the run-level test type (Web / API / Mobile / Unit). When the run is\n // all-API, also stamp each test's apiProtocol so the API report can show\n // per-test protocol badges; for non-API runs nothing is mutated on the tests.\n // NOTE: a MIXED run deliberately does NOT stamp apiProtocol on its api tests —\n // a leaked per-test protocol would trip the server's \"any protocol => api\"\n // run rule and misroute the whole run. The cloud tree instead defaults the\n // badge from the per-test testType. (See the api-classification regression test.)\n const typeSignal = deriveRunTestType(tests);\n const apiSignal = typeSignal.testType === 'api' ? typeSignal : null;\n const effectiveTests = apiSignal && tests\n ? tests.map((t) => (t.apiProtocol ? t : { ...t, apiProtocol: apiSignal.apiProtocol! }))\n : tests;\n const payload: RunUploadPayload = {\n runId: report.testRunId,\n repoGitId,\n startedAt: report.startedAt,\n summary: report.summary,\n timeline: report.timeline,\n environment,\n ...(runType ? { runType } : {}),\n ...(apiSignal\n ? { testType: apiSignal.testType, apiProtocol: apiSignal.apiProtocol, metadata: apiSignal.metadata }\n : { testType: typeSignal.testType }),\n ...(effectiveTests && effectiveTests.length > 0 ? { tests: effectiveTests } : {}),\n ...(git?.branch ? { branch: git.branch } : {}),\n ...(git?.commitSha ? { commit: git.commitSha } : {}),\n ...(git?.commitMessage ? { commitMessage: git.commitMessage } : {}),\n ...(git?.commitAuthor ? { commitAuthor: git.commitAuthor } : {}),\n ...(report.completedAt ? { finishedAt: report.completedAt } : {}),\n ...(report.totalDuration ? { duration: report.totalDuration } : {}),\n ...(ci?.provider ? { ciProvider: ci.provider } : {}),\n ...(ci?.runUrl ? { ciRunUrl: ci.runUrl } : {}),\n ...(cloudFarm?.cloudPlatform ? { cloudPlatform: cloudFarm.cloudPlatform } : {}),\n ...(cloudFarm?.cloudBuildId ? { cloudBuildId: cloudFarm.cloudBuildId } : {}),\n ...(cloudFarm?.cloudSessionId ? { cloudSessionId: cloudFarm.cloudSessionId } : {}),\n };\n\n // The 413 size-guard / strip lives in prepareBody (the single serialization\n // point), so the payload is measured once and stripped only if it actually\n // exceeds the limit AFTER serialization — and the offline-queue replay path\n // gets the identical treatment for free.\n return payload;\n}\n\n// ---------------------------------------------------------------------------\n// Canonical `apiReport` shape mappers\n//\n// The cloud API-workspace inspector reads `apiReport.apiCalls` (ApiCall shape:\n// statusCode/statusText/ok/durationMs/bytesIn/bytesOut) and\n// `apiReport.protocolAssertions` (kind/description/status/detail). The capture\n// layer records ApiCallRecord (responseStatusCode/responseTimeMs) and\n// ApiAssertion (type/expected/actual/expression), so every cloud payload must\n// map to the canonical shape — otherwise the panel renders empty cells (or, for\n// assertions, nothing at all). Coalescing makes them idempotent if a row is\n// already canonical.\n// ---------------------------------------------------------------------------\n\n/** UTF-8 byte length of a string body (0 for null/non-string). */\nfunction bodyBytes(v: unknown): number {\n return typeof v === 'string' ? Buffer.byteLength(v, 'utf-8') : 0;\n}\n\n/** Map captured API calls to the cloud `apiReport.apiCalls` (ApiCall) shape. */\nexport function toApiReportCalls(\n records: readonly Record<string, unknown>[] | null | undefined,\n): Record<string, unknown>[] {\n if (!Array.isArray(records)) return [];\n return records.map((c) => {\n const statusCode = (c.statusCode ?? c.responseStatusCode ?? null) as number | null;\n return {\n // Stable per-call id so protocolAssertions can be attributed to their call\n // (the assertion carries the matching callId). Preserved end-to-end even\n // though today's single-call inspector doesn't yet key on it.\n ...(c.id != null ? { id: c.id } : {}),\n ...(c.protocol != null ? { protocol: c.protocol } : {}),\n method: (c.method ?? null) as string | null,\n url: (c.url ?? null) as string | null,\n statusCode,\n statusText: (c.statusText ?? c.responseStatusText ?? null) as string | null,\n ok: typeof statusCode === 'number' ? statusCode < 400 : ((c.ok ?? null) as boolean | null),\n durationMs: (c.durationMs ?? c.responseTimeMs ?? null) as number | null,\n bytesIn: typeof c.bytesIn === 'number' ? c.bytesIn : bodyBytes(c.responseBody),\n bytesOut: typeof c.bytesOut === 'number' ? c.bytesOut : bodyBytes(c.requestBody),\n requestHeaders: (c.requestHeaders ?? null) as Record<string, string> | null,\n responseHeaders: (c.responseHeaders ?? null) as Record<string, string> | null,\n requestBody: (c.requestBody ?? null) as string | null,\n responseBody: (c.responseBody ?? null) as string | null,\n requestCookies: (c.requestCookies ?? null) as Record<string, unknown>[] | null,\n responseCookies: (c.responseCookies ?? null) as Record<string, unknown>[] | null,\n };\n });\n}\n\n/** Map captured API calls to the generic Network-inspector (network.json) shape. */\nexport function toNetworkRequestsFromApiCalls(\n records: readonly Record<string, unknown>[] | null | undefined,\n): Record<string, unknown>[] {\n if (!Array.isArray(records)) return [];\n return records.map((c) => {\n const status = (c.responseStatusCode ?? c.statusCode ?? 0) as number;\n const ts = (c.timestamp ?? null) as string | null;\n return {\n method: c.method ?? 'GET',\n url: c.url ?? '',\n status,\n statusCode: status,\n type: 'xhr',\n size: bodyBytes(c.responseBody),\n duration: (c.responseTimeMs ?? c.durationMs ?? 0) as number,\n responseTimeMs: (c.responseTimeMs ?? c.durationMs ?? 0) as number,\n requestHeaders: c.requestHeaders ?? null,\n responseHeaders: c.responseHeaders ?? null,\n requestBody: c.requestBody ?? null,\n responseBody: c.responseBody ?? null,\n timestamp: ts,\n startedAt: ts,\n };\n });\n}\n\n/** Map captured assertions to the cloud `apiReport.protocolAssertions` shape. */\nexport function toProtocolAssertions(\n assertions: readonly Record<string, unknown>[] | null | undefined,\n): Record<string, unknown>[] {\n if (!Array.isArray(assertions)) return [];\n const fmt = (v: unknown): string => {\n if (v === null || v === undefined) return String(v);\n if (typeof v === 'string') return v;\n try { return JSON.stringify(v); } catch { return String(v); }\n };\n return assertions.map((a) => {\n const status = a.status === 'failed' ? 'failed' : 'passed';\n const expr = typeof a.expression === 'string' && a.expression.trim() ? a.expression.trim() : null;\n const kind = (a.kind ?? a.type ?? 'assertion') as string;\n const detail =\n status === 'failed'\n ? `expected ${fmt(a.expected)}, received ${fmt(a.actual)}`\n : a.expected !== undefined\n ? `expected ${fmt(a.expected)}`\n : null;\n return {\n // callId links the assertion to its ApiCallRecord (preserved so a future\n // multi-call inspector can attribute assertions per request; harmless for\n // the current single-call view, which shows all of a test's assertions).\n ...(a.callId != null ? { callId: a.callId } : {}),\n kind,\n description: expr ?? (a.description as string | undefined) ?? `${kind} assertion`,\n status,\n detail,\n };\n });\n}\n\n// ---------------------------------------------------------------------------\n// Retry Logic\n// ---------------------------------------------------------------------------\n\nasync function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/** Strip null/undefined values from an object before sending to the cloud API. */\nfunction stripNulls(obj: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(obj)) {\n if (val !== null && val !== undefined) result[key] = val;\n }\n return result;\n}\n\n/**\n * Retry a fetch request with exponential backoff.\n * Handles 429 rate limits (reads Retry-After header), 5xx errors, and network failures.\n * Returns the response on success, or null after all retries exhausted.\n */\nasync function retryWithBackoff(\n url: string,\n init: RequestInit,\n onTokenRefresh?: () => Promise<string | null>,\n timeoutMs: number = DEFAULT_REQUEST_TIMEOUT_MS,\n budget?: Budget,\n): Promise<Response | null> {\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n // ⛔ Refuse rather than start: a request begun with no budget left still\n // pays its own timeout, which is how one stalled upload became ten.\n if (budget?.expired()) return null;\n const slice = budget ? budget.slice(timeoutMs) : timeoutMs;\n if (slice <= 0) return null;\n try {\n const response = await withTimeout(url, init, slice);\n\n if (response.ok) return response;\n\n // 401 — attempt token refresh and retry once\n if (response.status === 401 && onTokenRefresh && attempt === 0) {\n const newToken = await onTokenRefresh();\n if (newToken) {\n const refreshedInit = {\n ...init,\n headers: { ...init.headers as Record<string, string>, Authorization: `Bearer ${newToken}` },\n };\n const retryResponse = await withTimeout(url, refreshedInit, slice);\n if (retryResponse.ok) return retryResponse;\n }\n return null; // Refresh failed or retry failed\n }\n\n // 429 — rate limited, respect Retry-After header\n if (response.status === 429 && attempt < MAX_RETRIES - 1) {\n const retryAfter = response.headers.get('Retry-After');\n const waitMs = retryAfter ? parseInt(retryAfter, 10) * 1000 : RETRY_DELAYS_MS[attempt];\n if (!isNaN(waitMs) && waitMs > 0) {\n await sleep(waitMs);\n } else {\n await sleep(RETRY_DELAYS_MS[attempt]);\n }\n continue;\n }\n\n // 5xx — retry with backoff\n if (response.status >= 500 && attempt < MAX_RETRIES - 1) {\n await sleep(RETRY_DELAYS_MS[attempt]);\n continue;\n }\n\n // Return the response for caller-level handling (e.g., 409)\n return response;\n } catch {\n // Network error — retry with backoff\n if (attempt < MAX_RETRIES - 1) {\n await sleep(RETRY_DELAYS_MS[attempt]);\n continue;\n }\n return null;\n }\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Compression\n// ---------------------------------------------------------------------------\n\nconst gzipAsync = promisify(gzip);\n\n/** A batch upload's per-test rows carry the heavy artifact arrays. */\ntype TestsCarrier = { tests?: Array<Record<string, unknown>> } & Record<string, unknown>;\n\n/**\n * Shed the heaviest per-test arrays so an oversized batch fits under the server\n * limit instead of being lost whole to a 413. `dropApiCalls=false` keeps apiCalls\n * (the API-workspace inspector needs them); `true` drops them too as a last resort.\n */\nfunction stripOversizeTests(payload: TestsCarrier, dropApiCalls: boolean): TestsCarrier {\n if (!Array.isArray(payload.tests)) return payload;\n const tests = payload.tests.map((t) => {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { consoleLogs: _c, networkRequests: _n, apiCalls, ...rest } = t;\n return dropApiCalls ? rest : { ...rest, ...(apiCalls !== undefined ? { apiCalls } : {}) };\n });\n return { ...payload, tests };\n}\n\n/** Every heavy per-test array carried in the batch payload (besides the failure blob). */\n// `visual` belongs here for the same reason as the rest: a comparison carries\n// up to DEFAULT_MAX_CHANGES element records, each with its style, attr and\n// text diffs, so a large visual suite is tens of KB per comparison — not the\n// \"few hundred bytes\" the field's own doc comment once claimed. Omitting it\n// broke the tier contract: tier 3 promises to shed EVERY heavy array, and an\n// oversize visual run instead fell through to tier 4, which drops every\n// per-test row to save the summary. Losing visual detail is the intended\n// sacrifice; losing every test result is not.\nconst HEAVY_TEST_KEYS = ['consoleLogs', 'networkRequests', 'apiCalls', 'actions', 'navigations', 'protocolAssertions', 'visual'];\n\n/**\n * Tier-3 strip: keep only lightweight per-test metadata — drop EVERY heavy array and\n * trim the failure to its message/line — so the per-test results (status, timing,\n * identity) still land even when console/network/apiCalls alone weren't enough.\n */\nfunction stripToMinimalTests(payload: TestsCarrier): TestsCarrier {\n if (!Array.isArray(payload.tests)) return payload;\n const heavy = new Set(HEAVY_TEST_KEYS);\n const tests = payload.tests.map((t) => {\n const out: Record<string, unknown> = {};\n for (const k in t) {\n if (heavy.has(k)) continue;\n if (k === 'failure' && t[k] && typeof t[k] === 'object') {\n const f = t[k] as Record<string, unknown>;\n out.failure = { message: f.message ?? null, line: f.line ?? null };\n } else {\n out[k] = t[k];\n }\n }\n return out;\n });\n return { ...payload, tests };\n}\n\n/**\n * Tier-4 strip (absolute last resort): drop the per-test rows and any run-level heavy\n * arrays entirely, so the run record + summary still land instead of the WHOLE upload\n * being lost to a 413. Bounds the body to the run summary + metadata.\n */\nfunction stripAllTests(payload: TestsCarrier): TestsCarrier {\n const heavyRunKeys = new Set(['tests', 'timeline', 'unifiedTimeline', 'navigations']);\n const out: Record<string, unknown> = {};\n for (const k in payload) if (!heavyRunKeys.has(k)) out[k] = (payload as Record<string, unknown>)[k];\n out.tests = [];\n return out as TestsCarrier;\n}\n\n/**\n * Serialize a batch payload ONCE, apply the 413 size-guard, and gzip large bodies\n * off the main thread. The server enforces its 100MB limit on the DECOMPRESSED body\n * and we gzip here, so the guard trips only for an extreme outlier; when it does it\n * sheds detail in tiers, RE-MEASURING after each, until the body fits — so a run is\n * NEVER lost whole to a 413 (the worst case still uploads the run summary). The tiers:\n * 1. drop consoleLogs/networkRequests (keep apiCalls for the inspector)\n * 2. also drop apiCalls\n * 3. shed all remaining per-test arrays (actions/navigations/assertions) + failure detail\n * 4. drop the per-test rows entirely — run summary only (absolute last resort)\n * Each tier warns; tier 4 is an error. Exported so the offline-queue replay path\n * compresses identically AND bounds the body the same way (otherwise a queued oversize\n * payload re-413s on every flush forever).\n */\nexport async function prepareBody(\n payload: unknown,\n maxBytes: number = MAX_BATCH_PAYLOAD_BYTES,\n): Promise<{ body: BodyInit; headers: Record<string, string> }> {\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n let json = JSON.stringify(payload);\n const carrier = payload as TestsCarrier;\n const testCount = Array.isArray(carrier?.tests) ? carrier.tests.length : 0;\n const over = (s: string): boolean => Buffer.byteLength(s, 'utf-8') > maxBytes;\n\n if (testCount > 0 && over(json)) {\n const limitMb = Math.max(1, Math.round(maxBytes / 1_048_576));\n\n // Tier 1 — drop consoleLogs/networkRequests, keep apiCalls.\n process.stderr.write(\n `[testrelic] WARNING: batch payload exceeds ${limitMb}MB — dropping consoleLogs/networkRequests ` +\n `from ${testCount} test(s) to avoid HTTP 413. Console/network detail will be missing in the cloud.\\n`,\n );\n json = JSON.stringify(stripOversizeTests(carrier, false));\n\n // Tier 2 — also drop apiCalls.\n if (over(json)) {\n process.stderr.write(\n `[testrelic] WARNING: batch still exceeds ${limitMb}MB after dropping logs — also dropping apiCalls ` +\n `from ${testCount} test(s). API request/response detail will be missing in the cloud.\\n`,\n );\n json = JSON.stringify(stripOversizeTests(carrier, true));\n }\n\n // Tier 3 — shed every remaining heavy per-test array + trim failure detail.\n if (over(json)) {\n process.stderr.write(\n `[testrelic] WARNING: batch still exceeds ${limitMb}MB — shedding remaining per-test detail ` +\n `(actions/navigations/assertions) from ${testCount} test(s). Only test results + summary will reach the cloud.\\n`,\n );\n json = JSON.stringify(stripToMinimalTests(carrier));\n }\n\n // Tier 4 — last resort: drop per-test rows so the run summary still lands.\n if (over(json)) {\n process.stderr.write(\n `[testrelic] ERROR: batch still exceeds ${limitMb}MB after shedding all per-test detail — uploading ` +\n `run summary only (${testCount} per-test result(s) omitted) to avoid losing the run to HTTP 413.\\n`,\n );\n json = JSON.stringify(stripAllTests(carrier));\n }\n }\n\n if (Buffer.byteLength(json, 'utf-8') > GZIP_THRESHOLD_BYTES) {\n headers['Content-Encoding'] = 'gzip';\n const gz = await gzipAsync(Buffer.from(json, 'utf-8'));\n // A zero-copy view over the same bytes. Buffer is a Uint8Array, but its\n // ArrayBufferLike generic admits SharedArrayBuffer and so keeps it out of\n // BodyInit.\n //\n // SAFETY: a zlib result is always backed by a plain ArrayBuffer, never a\n // shared one, so narrowing the buffer is sound and avoids copying an\n // already-large compressed payload.\n const backing = gz.buffer as ArrayBuffer;\n return { body: new Uint8Array(backing, gz.byteOffset, gz.byteLength), headers };\n }\n return { body: json, headers };\n}\n\n// ---------------------------------------------------------------------------\n// Batch Upload\n// ---------------------------------------------------------------------------\n\n/**\n * Upload a complete test run to the cloud.\n * POST /runs with the full payload.\n * Returns a structured result (never throws).\n */\nexport async function uploadBatchRun(\n endpoint: string,\n accessToken: string,\n payload: RunUploadPayload,\n onTokenRefresh?: () => Promise<string | null>,\n timeoutMs?: number,\n budget?: Budget,\n): Promise<UploadResult | UploadFailure> {\n const url = `${endpoint}/runs`;\n const { body, headers } = await prepareBody(payload);\n headers['Authorization'] = `Bearer ${accessToken}`;\n\n const response = await retryWithBackoff(\n url,\n { method: 'POST', headers, body },\n onTokenRefresh,\n timeoutMs,\n budget,\n );\n\n if (response?.ok) {\n return { success: true, statusCode: response.status, error: null };\n }\n\n // 409 — duplicate run, treat as success (data already ingested)\n if (response?.status === 409) {\n return { success: true, statusCode: 409, error: null };\n }\n\n return {\n success: false,\n reason: response ? `Upload failed with status ${response.status}` : 'Upload failed after retries',\n statusCode: response?.status ?? null,\n payload: payload as unknown as Record<string, unknown>,\n targetEndpoint: url,\n method: 'POST',\n };\n}\n\n// ---------------------------------------------------------------------------\n// Realtime Upload Functions\n// ---------------------------------------------------------------------------\n\nexport interface RealtimeInitResult {\n readonly runId: string;\n}\n\n/**\n * Initialize a realtime run on the cloud.\n * POST /runs/init\n */\nexport async function initRealtimeRun(\n endpoint: string,\n accessToken: string,\n payload: {\n runId: string;\n repoGitId: string;\n branch: string | null;\n commit: string | null;\n commitMessage?: string | null;\n commitAuthor?: string | null;\n startedAt: string;\n totalTests: number | null;\n ciProvider: string | null;\n ciRunUrl: string | null;\n environment?: string | null;\n cloudPlatform?: string | null;\n cloudBuildId?: string | null;\n cloudSessionId?: string | null;\n runType?: SdkRunType;\n /** Run-level test-type default (e.g. 'e2e'); finalize overwrites with the derived slug. */\n testType?: string;\n },\n timeoutMs?: number,\n): Promise<RealtimeInitResult | null> {\n const url = `${endpoint}/runs/init`;\n try {\n const response = await withTimeout(\n url,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${accessToken}`,\n },\n body: JSON.stringify(stripNulls(payload as unknown as Record<string, unknown>)),\n },\n timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,\n );\n if (!response.ok) return null;\n const responseBody = await response.json() as Record<string, unknown>;\n return { runId: responseBody.runId as string };\n } catch {\n return null;\n }\n}\n\n/**\n * Upload a single test result to the cloud (fire-and-forget).\n * POST /runs/{runId}/tests\n */\nexport function uploadTestResult(\n endpoint: string,\n accessToken: string,\n cloudRunId: string,\n testData: Record<string, unknown>,\n): void {\n const url = `${endpoint}/runs/${cloudRunId}/tests`;\n // Fire-and-forget — intentionally not awaited\n void fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${accessToken}`,\n },\n body: JSON.stringify(testData),\n }).catch(() => {\n // Silently ignore realtime upload errors\n });\n}\n\n/**\n * Finalize a realtime run on the cloud with retry and backoff.\n * POST /runs/{runId}/finalize\n */\nexport async function finalizeRun(\n endpoint: string,\n accessToken: string,\n cloudRunId: string,\n payload: {\n finishedAt: string;\n duration: number;\n summary: Summary;\n commitMessage?: string | null;\n /** Run-level API signal — realtime init ran before any test, so the server learns the run is API here. */\n testType?: string;\n apiProtocol?: string;\n metadata?: Record<string, unknown>;\n },\n): Promise<boolean> {\n const url = `${endpoint}/runs/${cloudRunId}/finalize`;\n const response = await retryWithBackoff(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${accessToken}`,\n },\n body: JSON.stringify(payload),\n });\n return response?.ok ?? false;\n}\n\n/**\n * Fire-and-forget finalize for use in signal handlers (SIGINT/SIGTERM).\n * Does not await the response — the process may exit before it completes,\n * but the HTTP request will still be dispatched to the OS network stack.\n */\nexport function finalizeRunFireAndForget(\n endpoint: string,\n accessToken: string,\n cloudRunId: string,\n startedAt: string,\n): void {\n const url = `${endpoint}/runs/${cloudRunId}/finalize`;\n const now = new Date();\n const payload = {\n finishedAt: now.toISOString(),\n duration: now.getTime() - new Date(startedAt).getTime(),\n summary: {},\n };\n void fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${accessToken}`,\n },\n body: JSON.stringify(payload),\n }).catch(() => {\n // Best-effort — silently ignore errors in signal handlers\n });\n}\n","/**\n * Cloud queue — offline storage and retry for failed uploads.\n *\n * Writes failed upload payloads to individual JSON files in .testrelic/queue/.\n * Flushes queued files on next successful auth, oldest first (FIFO).\n *\n * Constitution Principle VI: all I/O errors caught, never crash test run.\n * Constitution Principle VIII: atomic writes (tmp → rename).\n */\n\nimport {\n mkdirSync,\n writeFileSync,\n readFileSync,\n readdirSync,\n unlinkSync,\n renameSync,\n statSync,\n} from 'node:fs';\nimport { join } from 'node:path';\nimport type { QueueEntry } from '@testrelic/core';\nimport { isValidQueueEntry } from '@testrelic/core';\nimport { prepareBody } from './cloud-upload.js';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst QUEUE_ENTRY_VERSION = '1.0.0';\nconst FLUSH_BATCH_SIZE = 10;\n\n/** Best-effort message for a caught unknown, for queue-flush diagnostics. */\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/** A client error that will never succeed on replay, so the queued entry should be\n * retired rather than retried forever. 408 (timeout) and 429 (rate-limit) ARE\n * transient and are treated as retryable. */\nfunction isPermanentHttpFailure(status: number): boolean {\n return status >= 400 && status < 500 && status !== 408 && status !== 429;\n}\n\n// ---------------------------------------------------------------------------\n// Write to Queue\n// ---------------------------------------------------------------------------\n\n/**\n * Write a failed upload payload to the offline queue.\n * Creates the queue directory if needed. Uses atomic write.\n * Silently fails on disk errors (ENOSPC, permissions).\n */\nexport function writeToQueue(\n queueDirectory: string,\n runId: string,\n type: string,\n reason: string,\n targetEndpoint: string,\n method: string,\n payload: Record<string, unknown>,\n headers: Record<string, string>,\n): void {\n try {\n mkdirSync(queueDirectory, { recursive: true });\n\n const timestamp = Date.now();\n const filename = `${timestamp}-${runId}-${type}.json`;\n const filePath = join(queueDirectory, filename);\n\n const entry: QueueEntry = {\n version: QUEUE_ENTRY_VERSION,\n queuedAt: new Date(timestamp).toISOString(),\n reason,\n retryCount: 0,\n targetEndpoint,\n method,\n payload,\n headers,\n };\n\n const tmpPath = filePath + '.tmp';\n writeFileSync(tmpPath, JSON.stringify(entry, null, 2), 'utf-8');\n renameSync(tmpPath, filePath);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOSPC') {\n process.stderr.write('⚠ TestRelic: Disk full — unable to queue upload data.\\n');\n } else {\n process.stderr.write('⚠ TestRelic: Unable to queue upload data.\\n');\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Queue Flush\n// ---------------------------------------------------------------------------\n\n/**\n * Flush queued uploads to the cloud. Oldest first (FIFO).\n * Stops on first failure (circuit-breaker).\n * Processes in batches of 10 for large queues.\n */\nexport async function flushQueue(\n queueDirectory: string,\n endpoint: string,\n accessToken: string,\n): Promise<void> {\n let files: string[];\n try {\n files = readdirSync(queueDirectory)\n .filter((f) => f.endsWith('.json') && !f.endsWith('.tmp'))\n .sort(); // Lexicographic sort = FIFO by timestamp prefix\n } catch {\n return; // Queue directory doesn't exist or unreadable\n }\n\n if (files.length === 0) return;\n\n // Process in batches\n const batches: string[][] = [];\n for (let i = 0; i < files.length; i += FLUSH_BATCH_SIZE) {\n batches.push(files.slice(i, i + FLUSH_BATCH_SIZE));\n }\n\n for (const batch of batches) {\n for (const file of batch) {\n const filePath = join(queueDirectory, file);\n\n // Stage 1 — load + validate the entry. A bad/unreadable file is a PER-FILE\n // problem: delete it (with a warning) and move on, so one poison entry at the\n // FIFO head can't wedge every later entry behind it (cleanupExpiredQueue would\n // otherwise only clear it by age).\n let queueEntry: QueueEntry;\n try {\n const stat = statSync(filePath);\n if (!stat.isFile()) continue;\n const entry = JSON.parse(readFileSync(filePath, 'utf-8')) as unknown;\n if (!isValidQueueEntry(entry)) {\n process.stderr.write(`⚠ TestRelic: Corrupted queue file deleted: ${file}\\n`);\n unlinkSync(filePath);\n continue;\n }\n queueEntry = entry as QueueEntry;\n } catch (err) {\n process.stderr.write(`⚠ TestRelic: Skipping unreadable queue file ${file}: ${errorMessage(err)}\\n`);\n try { unlinkSync(filePath); } catch { /* ignore */ }\n continue;\n }\n\n // Stage 2 — replay the upload. A failure here is a SERVER/NETWORK problem, not a\n // per-file one, so it's handled distinctly: a permanent rejection retires the\n // entry; a transient one pauses the flush (with a log, so a stuck queue is\n // diagnosable instead of silently stalling).\n try {\n // Replay through the SAME prepareBody used by the direct upload path, so the\n // gzip/Content-Encoding (and the tiered 413 size-guard) stay in lockstep — a\n // payload gzipped or shed on first send must replay identically.\n const { body: flushBody, headers: prepared } = await prepareBody(queueEntry.payload);\n const flushHeaders: Record<string, string> = {\n ...queueEntry.headers,\n ...prepared,\n 'Authorization': `Bearer ${accessToken}`,\n };\n const response = await fetch(queueEntry.targetEndpoint, {\n method: queueEntry.method,\n headers: flushHeaders,\n body: flushBody,\n });\n\n if (response.ok) {\n unlinkSync(filePath);\n } else if (isPermanentHttpFailure(response.status)) {\n // A permanent client error (400/401/403/413/…) will never succeed on replay —\n // move the entry aside so it can't wedge the queue forever.\n process.stderr.write(\n `⚠ TestRelic: Queue entry ${file} rejected (HTTP ${response.status}) — moving aside; it will not be retried.\\n`,\n );\n try { renameSync(filePath, `${filePath}.failed`); }\n catch { try { unlinkSync(filePath); } catch { /* ignore */ } }\n } else {\n // Transient server error (5xx / 408 / 429) — stop and retry the whole queue\n // on the next flush; don't hammer a down server.\n process.stderr.write(\n `⚠ TestRelic: Queue flush paused — server returned HTTP ${response.status} for ${file}; will retry later.\\n`,\n );\n return;\n }\n } catch (err) {\n // Network failure / fetch rejection — server unreachable. Stop and retry the\n // whole queue next flush; log so a persistently stuck queue is visible.\n process.stderr.write(`⚠ TestRelic: Queue flush paused — ${errorMessage(err)}; will retry later.\\n`);\n return;\n }\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Retention Cleanup\n// ---------------------------------------------------------------------------\n\n/**\n * Delete queue files older than maxAge (in milliseconds).\n * Runs before flush on startup. Also ages out `.failed` entries (permanently-rejected\n * uploads moved aside by flushQueue) so they don't accumulate on disk forever — they\n * carry the same `queuedAt`, so the same retention applies.\n */\nexport function cleanupExpiredQueue(\n queueDirectory: string,\n maxAge: number,\n): void {\n try {\n const files = readdirSync(queueDirectory)\n .filter((f) => (f.endsWith('.json') || f.endsWith('.failed')) && !f.endsWith('.tmp'));\n\n const now = Date.now();\n\n for (const file of files) {\n const filePath = join(queueDirectory, file);\n try {\n const raw = readFileSync(filePath, 'utf-8');\n const entry = JSON.parse(raw) as Record<string, unknown>;\n const queuedAt = entry.queuedAt;\n\n if (typeof queuedAt === 'string') {\n const queuedTime = new Date(queuedAt).getTime();\n if (now - queuedTime > maxAge) {\n unlinkSync(filePath);\n }\n }\n } catch {\n // Corrupted file — delete\n try { unlinkSync(filePath); } catch { /* ignore */ }\n }\n }\n } catch {\n // Queue directory doesn't exist — nothing to clean\n }\n}\n","/**\n * CloudClient — manages cloud connectivity lifecycle.\n *\n * Handles auth, mode transitions, repo resolution, token refresh,\n * and queue flush orchestration. Never throws — all errors result\n * in graceful local-mode fallback.\n *\n * Constitution Principle VI: all timers/intervals tracked and cleaned up in dispose().\n */\n\nimport { readFileSync, writeFileSync, mkdirSync, renameSync, existsSync } from 'node:fs';\nimport { join, dirname, basename } from 'node:path';\nimport { createHash } from 'node:crypto';\nimport type { CloudConfig, AuthState, AuthMode, GitMetadata } from '@testrelic/core';\nimport {\n enforceHttps,\n healthCheck,\n exchangeToken,\n refreshAccessToken,\n isAuthError,\n resolveRepo,\n} from './cloud-auth.js';\nimport type { TokenExchangeResult } from './cloud-auth.js';\nimport { collectGitMetadata, normalizeGitRemoteUrl, deriveRepoDisplayName, deriveNonGitProjectId, readPackageJsonName } from './git-metadata.js';\nimport { cleanupExpiredQueue, flushQueue } from './cloud-queue.js';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst TOKEN_REFRESH_BUFFER_MS = 300_000; // 5 minutes\nconst PROJECT_CACHE_TTL_MS = 86_400_000; // 24 hours\nconst HEALTH_CHECK_INTERVAL_MS = 60_000; // 60 seconds\nconst FLUSH_TIMEOUT_MS = 30_000; // 30 seconds\nconst DASHBOARD_SETTINGS_URL = 'https://platform.testrelic.ai/settings/api-keys';\n\n/**\n * A timeout promise whose timer is `unref()`d.\n *\n * These promises are used as the losing side of `Promise.race`, so when the real\n * work wins the timer is still armed. A ref'd timer would keep Node's event loop\n * alive — up to `FLUSH_TIMEOUT_MS` of dead air after every test run finished.\n * Telemetry must never be the reason `playwright test` doesn't exit.\n */\nfunction unrefTimeout(ms: number): Promise<void> {\n return new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, ms);\n if (typeof timer?.unref === 'function') timer.unref();\n });\n}\n\n// ---------------------------------------------------------------------------\n// Repo Cache\n// ---------------------------------------------------------------------------\n\ninterface RepoCache {\n readonly repoId: string;\n readonly gitId: string;\n readonly displayName: string;\n readonly resolvedAt: number;\n readonly apiKeyHash: string;\n}\n\n// ---------------------------------------------------------------------------\n// CloudClient\n// ---------------------------------------------------------------------------\n\nexport class CloudClient {\n private config: CloudConfig | null;\n private authState: AuthState;\n private gitMetadata: GitMetadata | null = null;\n private repoId: string | null = null;\n private failureReason: string | null = null;\n private flushPromise: Promise<void> | null = null;\n private healthCheckTimer: ReturnType<typeof setInterval> | null = null;\n\n constructor(cloudConfig: CloudConfig | null) {\n this.config = cloudConfig;\n this.authState = {\n mode: 'pending',\n accessToken: null,\n refreshToken: null,\n expiresAt: null,\n orgId: null,\n orgName: null,\n userId: null,\n userName: null,\n };\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n\n async initialize(): Promise<void> {\n // No config or no API key → local mode immediately\n if (!this.config || !this.config.apiKey) {\n this.setLocalMode('no_api_key');\n process.stderr.write('ℹ TestRelic: No API key configured. Running in local mode.\\n');\n return;\n }\n\n try {\n // HTTPS enforcement\n enforceHttps(this.config.endpoint);\n\n // Collect git metadata\n this.gitMetadata = collectGitMetadata();\n\n // Health check\n const healthy = await healthCheck(this.config.endpoint);\n if (!healthy) {\n this.setLocalMode('cloud_unreachable');\n process.stderr.write('⚠ TestRelic: Cloud unreachable. Switching to local mode. Data will be queued for later upload.\\n');\n return;\n }\n\n // Token exchange\n const result = await exchangeToken(this.config.endpoint, this.config.apiKey, this.config.timeout);\n if (isAuthError(result)) {\n this.handleAuthError(result.code, result.statusCode);\n return;\n }\n\n // Success — cloud mode\n const tokenResult = result as TokenExchangeResult;\n this.authState = {\n mode: 'cloud',\n accessToken: tokenResult.accessToken,\n refreshToken: tokenResult.refreshToken,\n expiresAt: Date.now() + tokenResult.expiresIn * 1000,\n orgId: tokenResult.orgId,\n orgName: tokenResult.orgName,\n userId: tokenResult.userId,\n userName: tokenResult.userName,\n };\n\n process.stderr.write(`✓ TestRelic: Connected to cloud (${tokenResult.orgName} / ${tokenResult.userName})\\n`);\n\n // Repo resolution\n await this.resolveRepoId();\n\n // Queue cleanup and background flush\n if (this.config.queueDirectory) {\n cleanupExpiredQueue(this.config.queueDirectory, this.config.queueMaxAge);\n this.startBackgroundFlush();\n }\n\n // Start periodic health check for network recovery\n this.startHealthCheck();\n } catch (err) {\n this.setLocalMode('unexpected_error');\n process.stderr.write(`⚠ TestRelic: Unexpected error during cloud initialization. Running in local mode. ${err instanceof Error ? err.message : String(err)}\\n`);\n }\n }\n\n getMode(): AuthMode {\n return this.authState.mode;\n }\n\n isCloudMode(): boolean {\n return this.authState.mode === 'cloud';\n }\n\n isLocalMode(): boolean {\n return this.authState.mode === 'local';\n }\n\n getAccessToken(): string | null {\n return this.authState.accessToken;\n }\n\n getRepoId(): string | null {\n return this.repoId;\n }\n\n getGitMetadata(): GitMetadata | null {\n return this.gitMetadata;\n }\n\n getConfig(): CloudConfig | null {\n return this.config;\n }\n\n getFailureReason(): string | null {\n return this.failureReason;\n }\n\n getEndpoint(): string {\n return this.config?.endpoint ?? 'https://platform.testrelic.ai/api/v1';\n }\n\n /**\n * Ensure the access token is valid. Refreshes if expiring within 5 minutes.\n * On refresh failure, switches to local mode.\n */\n async ensureValidToken(): Promise<boolean> {\n if (!this.isCloudMode() || !this.authState.accessToken) return false;\n\n const expiresAt = this.authState.expiresAt ?? 0;\n if (Date.now() + TOKEN_REFRESH_BUFFER_MS < expiresAt) {\n return true; // Token still valid\n }\n\n // Attempt refresh\n if (!this.config || !this.authState.refreshToken) {\n this.switchToLocalMode('token_expired_no_refresh');\n return false;\n }\n\n try {\n const refreshResult = await refreshAccessToken(\n this.config.endpoint,\n this.authState.refreshToken,\n this.config.timeout,\n );\n\n if (!refreshResult) {\n this.switchToLocalMode('token_refresh_failed');\n return false;\n }\n\n this.authState.accessToken = refreshResult.accessToken;\n this.authState.refreshToken = refreshResult.refreshToken;\n this.authState.expiresAt = Date.now() + refreshResult.expiresIn * 1000;\n return true;\n } catch {\n this.switchToLocalMode('token_refresh_error');\n return false;\n }\n }\n\n /**\n * Switch from cloud to local mode. One-way transition during a run.\n */\n switchToLocalMode(reason: string): void {\n if (this.authState.mode === 'local') return;\n this.authState.mode = 'local';\n this.failureReason = reason;\n process.stderr.write(`⚠ TestRelic: Switched to local mode (${reason}).\\n`);\n }\n\n /**\n * Clean up all resources: timers, pending requests, state.\n */\n async dispose(): Promise<void> {\n // Stop health check\n if (this.healthCheckTimer) {\n clearInterval(this.healthCheckTimer);\n this.healthCheckTimer = null;\n }\n\n // Wait for background flush (with timeout)\n if (this.flushPromise) {\n try {\n await Promise.race([\n this.flushPromise,\n unrefTimeout(FLUSH_TIMEOUT_MS),\n ]);\n } catch {\n // Ignore flush errors during dispose\n }\n this.flushPromise = null;\n }\n\n // Clear auth state\n this.authState = {\n mode: 'local',\n accessToken: null,\n refreshToken: null,\n expiresAt: null,\n orgId: null,\n orgName: null,\n userId: null,\n userName: null,\n };\n this.repoId = null;\n this.gitMetadata = null;\n }\n\n // -------------------------------------------------------------------------\n // Private Methods\n // -------------------------------------------------------------------------\n\n private setLocalMode(reason: string): void {\n this.authState.mode = 'local';\n this.failureReason = reason;\n }\n\n private handleAuthError(code: string, statusCode: number | null): void {\n switch (code) {\n case 'invalid_key':\n this.setLocalMode('invalid_api_key');\n process.stderr.write(\n `⚠ TestRelic: API key is invalid or revoked. Running in local mode.\\n` +\n ` → Manage keys: ${DASHBOARD_SETTINGS_URL}\\n`,\n );\n break;\n case 'expired_key':\n this.setLocalMode('expired_api_key');\n process.stderr.write(\n `⚠ TestRelic: API key has expired. Running in local mode.\\n` +\n ` → Generate a new key: ${DASHBOARD_SETTINGS_URL}\\n`,\n );\n break;\n case 'validation_error':\n this.setLocalMode('validation_error');\n process.stderr.write(\n `⚠ TestRelic: API key format invalid. Running in local mode.\\n` +\n ` → Manage keys: ${DASHBOARD_SETTINGS_URL}\\n`,\n );\n break;\n case 'rate_limited':\n this.setLocalMode('rate_limited');\n process.stderr.write('⚠ TestRelic: Rate limited during authentication. Running in local mode.\\n');\n break;\n case 'timeout':\n this.setLocalMode('auth_timeout');\n process.stderr.write('⚠ TestRelic: Authentication timed out. Running in local mode.\\n');\n break;\n default:\n this.setLocalMode(`auth_error_${statusCode ?? 'unknown'}`);\n process.stderr.write('⚠ TestRelic: Cloud authentication failed. Running in local mode.\\n');\n break;\n }\n }\n\n private async resolveRepoId(): Promise<void> {\n if (!this.config || !this.authState.accessToken) return;\n\n const gitId = this.gitMetadata?.remoteUrl\n ? normalizeGitRemoteUrl(this.gitMetadata.remoteUrl)\n : null;\n\n const displayName = gitId\n ? deriveRepoDisplayName(gitId)\n : this.config.projectName ?? deriveNonGitProjectId(process.cwd());\n\n const effectiveGitId = gitId ?? (this.config.projectName ?? deriveNonGitProjectId(process.cwd()));\n\n // Check cache first\n const cached = this.readRepoCache(effectiveGitId);\n if (cached) {\n this.repoId = cached.repoId;\n return;\n }\n\n // Resolve from cloud\n try {\n const resolved = await resolveRepo(\n this.config.endpoint,\n this.authState.accessToken,\n effectiveGitId,\n displayName,\n this.config.timeout,\n this.gitMetadata?.branch,\n );\n\n if (resolved) {\n this.repoId = resolved.repoId;\n this.writeRepoCache(effectiveGitId, resolved.repoId, resolved.displayName);\n }\n } catch {\n // Non-fatal — repo resolution failure doesn't block uploads\n }\n\n if (!gitId && !this.config.projectName && !readPackageJsonName(process.cwd())) {\n process.stderr.write(\n 'ℹ TestRelic: No git remote or package.json detected. Set project.name in .testrelic for stable project identity.\\n',\n );\n }\n }\n\n private readRepoCache(gitId: string): RepoCache | null {\n if (!this.config) return null;\n const cachePath = join(this.config.queueDirectory, '..', 'cache', 'repo.json');\n try {\n if (!existsSync(cachePath)) return null;\n const raw = readFileSync(cachePath, 'utf-8');\n const cache = JSON.parse(raw) as RepoCache;\n if (cache.gitId !== gitId) return null;\n if (Date.now() - cache.resolvedAt > PROJECT_CACHE_TTL_MS) return null;\n // Check API key hash matches\n const currentKeyHash = this.hashApiKey();\n if (currentKeyHash && cache.apiKeyHash !== currentKeyHash) return null;\n return cache;\n } catch {\n return null;\n }\n }\n\n private writeRepoCache(gitId: string, repoId: string, displayName: string): void {\n if (!this.config) return;\n const cacheDir = join(this.config.queueDirectory, '..', 'cache');\n const cachePath = join(cacheDir, 'repo.json');\n try {\n mkdirSync(cacheDir, { recursive: true });\n const cache: RepoCache = {\n repoId,\n gitId,\n displayName,\n resolvedAt: Date.now(),\n apiKeyHash: this.hashApiKey() ?? '',\n };\n const tmpPath = cachePath + '.tmp';\n writeFileSync(tmpPath, JSON.stringify(cache, null, 2), 'utf-8');\n renameSync(tmpPath, cachePath);\n } catch {\n // Non-fatal\n }\n }\n\n private hashApiKey(): string | null {\n if (!this.config?.apiKey) return null;\n return createHash('sha256').update(this.config.apiKey).digest('hex').substring(0, 16);\n }\n\n private startBackgroundFlush(): void {\n if (!this.config || !this.authState.accessToken) return;\n const accessToken = this.authState.accessToken;\n const queueDir = this.config.queueDirectory;\n const endpoint = this.config.endpoint;\n\n this.flushPromise = Promise.race([\n flushQueue(queueDir, endpoint, accessToken),\n unrefTimeout(FLUSH_TIMEOUT_MS),\n ]).catch(() => {\n // Background flush errors are non-fatal\n });\n }\n\n private startHealthCheck(): void {\n if (!this.config) return;\n const endpoint = this.config.endpoint;\n\n // `unref()`d: this recovery poller is pure telemetry upkeep and must never\n // be the handle that stops the customer's test process from exiting.\n this.healthCheckTimer = setInterval(async () => {\n // Only useful if we've switched to local mode due to connectivity\n if (this.isCloudMode()) return;\n if (!this.failureReason || !['cloud_unreachable', 'auth_timeout', 'network_error'].includes(this.failureReason)) return;\n\n try {\n const healthy = await healthCheck(endpoint);\n if (!healthy) return;\n\n // Attempt re-auth\n if (this.config?.apiKey) {\n const result = await exchangeToken(this.config.endpoint, this.config.apiKey, this.config.timeout);\n if (!isAuthError(result)) {\n const tokenResult = result as TokenExchangeResult;\n this.authState = {\n mode: 'cloud',\n accessToken: tokenResult.accessToken,\n refreshToken: tokenResult.refreshToken,\n expiresAt: Date.now() + tokenResult.expiresIn * 1000,\n orgId: tokenResult.orgId,\n orgName: tokenResult.orgName,\n userId: tokenResult.userId,\n userName: tokenResult.userName,\n };\n this.failureReason = null;\n process.stderr.write('✓ TestRelic: Cloud connectivity restored.\\n');\n this.startBackgroundFlush();\n }\n }\n } catch {\n // Health check errors are silently ignored\n }\n }, HEALTH_CHECK_INTERVAL_MS);\n\n if (typeof this.healthCheckTimer?.unref === 'function') this.healthCheckTimer.unref();\n }\n}\n","/**\n * Test data extraction — parses file-based payloads (v2) and legacy inline payloads (v1).\n *\n * In v2, heavy data (network requests, console logs, API calls) is referenced\n * by file path rather than embedded inline. The reporter moves these files to\n * the report directory without loading them into memory.\n */\n\nimport type {\n NavigationAnnotation,\n ApiAssertion,\n ApiCallsByStatusRange,\n} from '@testrelic/core';\nimport { isTestRelicFilePayload, isTestRelicDataPayload, ATTACHMENT_NAME } from '@testrelic/core';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface AttachmentLike {\n name: string;\n contentType: string;\n body?: Buffer;\n}\n\ninterface AnnotationLike {\n type: string;\n description?: string;\n}\n\n/** Extracted data with file references for heavy data. */\nexport interface ExtractedTestData {\n navigations: NavigationAnnotation[];\n apiAssertions: ApiAssertion[];\n /** Path to JSONL file with network requests, or null */\n networkRequestsFile: string | null;\n networkRequestsCount: number;\n /** Path to JSONL file with console logs, or null */\n consoleLogsFile: string | null;\n consoleLogsCount: number;\n /** Path to JSONL file with API calls, or null */\n apiCallsFile: string | null;\n apiCallsCount: number;\n /** Response-status mix of those calls, tallied at capture time. */\n apiStatusMix: ApiCallsByStatusRange | null;\n}\n\n/** Sum two status mixes. A test can attach more than one payload. */\nfunction addStatusMix(\n a: ApiCallsByStatusRange | null,\n b: ApiCallsByStatusRange | null | undefined,\n): ApiCallsByStatusRange | null {\n if (!b) return a;\n if (!a) return { ...b };\n return {\n '2xx': a['2xx'] + b['2xx'],\n '3xx': a['3xx'] + b['3xx'],\n '4xx': a['4xx'] + b['4xx'],\n '5xx': a['5xx'] + b['5xx'],\n error: a.error + b.error,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Extraction\n// ---------------------------------------------------------------------------\n\n/**\n * Extract test data from Playwright attachments.\n *\n * Supports both v2 file-based payloads (preferred) and v1 inline payloads (legacy fallback).\n * For v1 payloads, data is still inline but wrapped in the same ExtractedTestData shape\n * with file paths set to null.\n */\nexport function extractTestData(\n attachments: readonly AttachmentLike[],\n annotations: readonly AnnotationLike[],\n testTitle: string,\n isSkipped: boolean,\n): ExtractedTestData {\n let navigations: NavigationAnnotation[] = [];\n let apiAssertions: ApiAssertion[] = [];\n let networkRequestsFile: string | null = null;\n let networkRequestsCount = 0;\n let consoleLogsFile: string | null = null;\n let consoleLogsCount = 0;\n let apiCallsFile: string | null = null;\n let apiCallsCount = 0;\n let apiStatusMix: ApiCallsByStatusRange | null = null;\n\n const payloadAttachments = attachments.filter(\n (a) => a.name === ATTACHMENT_NAME && a.body,\n );\n\n let attachmentParsed = false;\n if (payloadAttachments.length > 0) {\n for (const att of payloadAttachments) {\n try {\n const parsed = JSON.parse(att.body!.toString());\n\n // v2 file-based payload (preferred)\n if (isTestRelicFilePayload(parsed)) {\n navigations = navigations.concat(parsed.navigations);\n apiAssertions = apiAssertions.concat(parsed.apiAssertions);\n if (parsed.networkRequestsFile) {\n networkRequestsFile = parsed.networkRequestsFile;\n networkRequestsCount += parsed.networkRequestsCount;\n }\n if (parsed.consoleLogsFile) {\n consoleLogsFile = parsed.consoleLogsFile;\n consoleLogsCount += parsed.consoleLogsCount;\n }\n if (parsed.apiCallsFile) {\n apiCallsFile = parsed.apiCallsFile;\n apiCallsCount += parsed.apiCallsCount;\n }\n apiStatusMix = addStatusMix(apiStatusMix, parsed.apiStatusMix);\n attachmentParsed = true;\n continue;\n }\n\n // v1 inline payload (legacy fallback)\n if (isTestRelicDataPayload(parsed)) {\n navigations = navigations.concat(parsed.navigations);\n if (Array.isArray(parsed.apiAssertions)) {\n apiAssertions = apiAssertions.concat(parsed.apiAssertions as ApiAssertion[]);\n }\n // v1 data is inline — counts tracked but no files\n networkRequestsCount += parsed.networkRequests?.length ?? 0;\n apiCallsCount += parsed.apiCalls?.length ?? 0;\n // v1 payloads carried consoleLogs inline; the interface never\n // declared it, so this is a probe of a legacy shape.\n const payloadLogs = (parsed as unknown as Record<string, unknown>).consoleLogs;\n if (Array.isArray(payloadLogs)) {\n consoleLogsCount += payloadLogs.length;\n }\n attachmentParsed = true;\n }\n } catch {\n process.stderr.write(`[testrelic] Warning: Corrupt attachment for test \"${testTitle}\"\\n`);\n }\n }\n }\n\n if (!attachmentParsed) {\n // Fallback: read navigations from legacy annotations\n navigations = 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\n const hasNoData = navigations.length === 0 && networkRequestsCount === 0 && apiCallsCount === 0;\n if (hasNoData && !isSkipped && !attachmentParsed) {\n process.stderr.write(\n `[testrelic] Warning: No data found for test \"${testTitle}\". ` +\n `Make sure your tests import { test, expect } from '@testrelic/playwright-analytics/fixture' ` +\n `instead of '@playwright/test'. Without the TestRelic fixture, network logs, video sync, ` +\n `and Test Navigation data cannot be captured.\\n`,\n );\n }\n\n return {\n navigations,\n apiAssertions,\n networkRequestsFile,\n networkRequestsCount,\n consoleLogsFile,\n consoleLogsCount,\n apiCallsFile,\n apiCallsCount,\n apiStatusMix,\n };\n}\n","/**\n * Cloud Farm detection for the TestRelic Playwright reporter.\n *\n * When Playwright tests are executed on a remote browser farm (BrowserStack\n * or LambdaTest), the reporter needs to forward `cloudPlatform`,\n * `cloudBuildId`, and `cloudSessionId` to the TestRelic server so the run\n * row can be linked to the vendor session (and, downstream, to the\n * ingested artifacts and Ask-AI tooling).\n *\n * Detection is best-effort and purely metadata-based:\n * 1. Prefer env vars the user sets in their Playwright config\n * (TESTRELIC_CLOUD_PLATFORM, TESTRELIC_CLOUD_BUILD_ID, TESTRELIC_CLOUD_SESSION_ID).\n * 2. Fall back to vendor env vars commonly present in CI\n * (BROWSERSTACK_USERNAME / LT_USERNAME / LAMBDATEST_USERNAME).\n * 3. As a last resort, parse the `PLAYWRIGHT_WS_ENDPOINT` env var if the\n * project pipes its LT/BS wsEndpoint through that convention.\n *\n * The returned values are optional — the SDK simply omits them from\n * `/runs/init` when nothing is detected.\n */\nexport interface CloudFarmMetadata {\n readonly cloudPlatform?: 'browserstack' | 'lambdatest';\n readonly cloudBuildId?: string;\n readonly cloudSessionId?: string;\n}\n\nexport function detectCloudFarm(env: NodeJS.ProcessEnv = process.env): CloudFarmMetadata {\n // 1. Explicit TestRelic env overrides — highest priority.\n const explicitPlatform = normalizePlatform(env.TESTRELIC_CLOUD_PLATFORM);\n if (explicitPlatform) {\n return {\n cloudPlatform: explicitPlatform,\n cloudBuildId: env.TESTRELIC_CLOUD_BUILD_ID || undefined,\n cloudSessionId: env.TESTRELIC_CLOUD_SESSION_ID || undefined,\n };\n }\n\n // 2. Vendor-native env vars.\n if (env.BROWSERSTACK_USERNAME || env.BROWSERSTACK_ACCESS_KEY) {\n return {\n cloudPlatform: 'browserstack',\n cloudBuildId: env.BROWSERSTACK_BUILD_NAME || env.BROWSERSTACK_BUILD_ID || undefined,\n cloudSessionId: env.BROWSERSTACK_SESSION_ID || undefined,\n };\n }\n if (\n env.LT_USERNAME || env.LAMBDATEST_USERNAME ||\n env.LT_ACCESS_KEY || env.LAMBDATEST_ACCESS_KEY\n ) {\n return {\n cloudPlatform: 'lambdatest',\n cloudBuildId: env.LT_BUILD || env.LAMBDATEST_BUILD || undefined,\n cloudSessionId: env.LT_SESSION_ID || env.LAMBDATEST_SESSION_ID || undefined,\n };\n }\n\n // 3. Parse wsEndpoint hostname (opt-in convention).\n const ws = env.PLAYWRIGHT_WS_ENDPOINT;\n if (ws) {\n if (/cdp\\.lambdatest\\.com/i.test(ws)) return { cloudPlatform: 'lambdatest' };\n if (/cdp\\.browserstack\\.com/i.test(ws)) return { cloudPlatform: 'browserstack' };\n }\n\n return {};\n}\n\nfunction normalizePlatform(raw: string | undefined): 'browserstack' | 'lambdatest' | undefined {\n if (!raw) return undefined;\n const v = raw.toLowerCase().trim();\n if (v === 'browserstack' || v === 'bs') return 'browserstack';\n if (v === 'lambdatest' || v === 'lt') return 'lambdatest';\n return undefined;\n}\n","/**\n * Cloud artifact upload — binary file upload via presigned S3 URLs.\n *\n * Flow per artifact:\n * 1. POST /artifacts/upload-url → receive presigned PUT URL + artifactId\n * 2. PUT binary file to the presigned URL\n * 3. POST /artifacts/confirm → mark upload complete\n *\n * Never throws to caller — returns structured results.\n */\n\nimport { createReadStream, statSync } from 'node:fs';\nimport { basename } from 'node:path';\nimport { Readable } from 'node:stream';\nimport type { ReadableStream as WebReadableStream } from 'node:stream/web';\nimport { Budget, putDeadlineMs, withTimeout } from './cloud-deadline.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface ArtifactUploadRequest {\n readonly filePath: string;\n readonly runId: string;\n readonly testId: string;\n readonly type: 'screenshot' | 'video' | 'trace' | 'baseline' | 'diff' | 'dom' | 'other';\n}\n\nexport interface ArtifactUploadResult {\n readonly success: boolean;\n readonly storageKey: string | null;\n readonly artifactId: string | null;\n readonly error: string | null;\n}\n\ninterface UploadUrlResponse {\n readonly artifactId: string;\n readonly uploadUrl: string;\n readonly storageKey: string;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst UPLOAD_CONCURRENCY = 5;\nconst RETRY_DELAYS_MS = [1000, 3000, 9000];\nconst MAX_RETRIES = 3;\n/** The small JSON calls around a transfer (presign, confirm). The transfer\n * itself gets a size-scaled deadline — see cloud-deadline. */\nconst JSON_REQUEST_TIMEOUT_MS = 30_000;\n\nconst CONTENT_TYPE_MAP: Record<string, string> = {\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.webp': 'image/webp',\n '.webm': 'video/webm',\n '.mp4': 'video/mp4',\n '.zip': 'application/zip',\n '.json': 'application/json',\n};\n\n// ---------------------------------------------------------------------------\n// Concurrency Limiter\n// ---------------------------------------------------------------------------\n\nlet activeUploads = 0;\nconst pendingUploads: Array<() => void> = [];\n\nasync function acquireSlot(): Promise<void> {\n if (activeUploads >= UPLOAD_CONCURRENCY) {\n await new Promise<void>((resolve) => pendingUploads.push(resolve));\n }\n activeUploads++;\n}\n\nfunction releaseSlot(): void {\n activeUploads--;\n if (pendingUploads.length > 0) pendingUploads.shift()!();\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nasync function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction getContentType(filePath: string): string {\n const ext = filePath.substring(filePath.lastIndexOf('.')).toLowerCase();\n return CONTENT_TYPE_MAP[ext] ?? 'application/octet-stream';\n}\n\nfunction getFileSize(filePath: string): number {\n try {\n return statSync(filePath).size;\n } catch {\n return 0;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Core Upload\n// ---------------------------------------------------------------------------\n\nasync function requestUploadUrl(\n endpoint: string,\n accessToken: string,\n request: ArtifactUploadRequest,\n sizeBytes: number,\n contentType: string,\n): Promise<UploadUrlResponse | null> {\n const url = `${endpoint}/artifacts/upload-url`;\n const body = JSON.stringify({\n runId: request.runId,\n testId: request.testId,\n fileName: basename(request.filePath),\n contentType,\n type: request.type,\n sizeBytes,\n });\n\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n try {\n const response = await withTimeout(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${accessToken}`,\n },\n body,\n }, JSON_REQUEST_TIMEOUT_MS);\n\n if (response.ok) {\n return (await response.json()) as UploadUrlResponse;\n }\n\n if (response.status >= 500 && attempt < MAX_RETRIES - 1) {\n await sleep(RETRY_DELAYS_MS[attempt]);\n continue;\n }\n\n return null;\n } catch {\n if (attempt < MAX_RETRIES - 1) {\n await sleep(RETRY_DELAYS_MS[attempt]);\n continue;\n }\n return null;\n }\n }\n return null;\n}\n\nasync function putFileToPresignedUrl(\n presignedUrl: string,\n filePath: string,\n contentType: string,\n sizeBytes: number,\n): Promise<boolean> {\n const deadlineMs = putDeadlineMs(sizeBytes, JSON_REQUEST_TIMEOUT_MS);\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n try {\n const nodeStream = createReadStream(filePath);\n const webStream = Readable.toWeb(nodeStream) as WebReadableStream<Uint8Array>;\n\n const response = await withTimeout(\n presignedUrl,\n {\n method: 'PUT',\n headers: {\n 'Content-Type': contentType,\n 'Content-Length': String(sizeBytes),\n },\n body: webStream,\n // Node's fetch requires `duplex` for a stream body; it is not in the\n // DOM RequestInit type, hence the cast rather than a suppression.\n ...({ duplex: 'half' } as Record<string, unknown>),\n } as RequestInit,\n // A transfer's budget follows its size — a 200 MB trace legitimately\n // outlasts a 2 KB POST, and a flat cap would abandon healthy uploads\n // on a slow link while still leaving a wedged socket to undici.\n deadlineMs,\n );\n\n if (response.ok) return true;\n\n if (response.status >= 500 && attempt < MAX_RETRIES - 1) {\n await sleep(RETRY_DELAYS_MS[attempt]);\n continue;\n }\n\n return false;\n } catch {\n if (attempt < MAX_RETRIES - 1) {\n await sleep(RETRY_DELAYS_MS[attempt]);\n continue;\n }\n return false;\n }\n }\n return false;\n}\n\nasync function confirmUpload(\n endpoint: string,\n accessToken: string,\n artifactId: string,\n): Promise<boolean> {\n const url = `${endpoint}/artifacts/confirm`;\n try {\n const response = await withTimeout(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${accessToken}`,\n },\n body: JSON.stringify({ artifactId }),\n }, JSON_REQUEST_TIMEOUT_MS);\n return response.ok;\n } catch {\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Upload a single artifact file to cloud storage via presigned URL.\n * Concurrency-limited and retried. Never throws.\n */\nexport async function uploadArtifact(\n endpoint: string,\n accessToken: string,\n request: ArtifactUploadRequest,\n maxSizeMb: number,\n): Promise<ArtifactUploadResult> {\n const sizeBytes = getFileSize(request.filePath);\n\n if (sizeBytes === 0) {\n return { success: false, storageKey: null, artifactId: null, error: 'file_not_found_or_empty' };\n }\n\n if (sizeBytes > maxSizeMb * 1024 * 1024) {\n return { success: false, storageKey: null, artifactId: null, error: 'file_too_large' };\n }\n\n const contentType = getContentType(request.filePath);\n\n await acquireSlot();\n try {\n const urlResult = await requestUploadUrl(\n endpoint,\n accessToken,\n request,\n sizeBytes,\n contentType,\n );\n\n if (!urlResult) {\n return { success: false, storageKey: null, artifactId: null, error: 'upload_url_request_failed' };\n }\n\n const uploaded = await putFileToPresignedUrl(\n urlResult.uploadUrl,\n request.filePath,\n contentType,\n sizeBytes,\n );\n\n if (!uploaded) {\n return {\n success: false,\n storageKey: urlResult.storageKey,\n artifactId: urlResult.artifactId,\n error: 'presigned_put_failed',\n };\n }\n\n const confirmed = await confirmUpload(endpoint, accessToken, urlResult.artifactId);\n\n return {\n success: confirmed,\n storageKey: urlResult.storageKey,\n artifactId: urlResult.artifactId,\n error: confirmed ? null : 'confirm_failed',\n };\n } finally {\n releaseSlot();\n }\n}\n\n/**\n * Upload multiple artifacts in parallel (bounded by UPLOAD_CONCURRENCY).\n * Returns a map from filePath to the upload result.\n */\nexport async function uploadArtifacts(\n endpoint: string,\n accessToken: string,\n requests: readonly ArtifactUploadRequest[],\n maxSizeMb: number,\n budget?: Budget,\n): Promise<Map<string, ArtifactUploadResult>> {\n const results = new Map<string, ArtifactUploadResult>();\n\n const promises = requests.map(async (req) => {\n // ⛔ The circuit breaker. Without it, a run whose storage endpoint is\n // wedged pays one full deadline PER ARTIFACT — ten screenshots turned a\n // slow upload into an app that would not exit. Once the phase's budget is\n // gone, the rest are recorded as skipped and the process gets to leave;\n // the local report still holds everything, and the next run re-uploads.\n if (budget?.expired()) {\n results.set(req.filePath, {\n success: false,\n storageKey: null,\n artifactId: null,\n error: 'upload_budget_exhausted',\n });\n return;\n }\n const result = await uploadArtifact(endpoint, accessToken, req, maxSizeMb);\n results.set(req.filePath, result);\n });\n\n await Promise.allSettled(promises);\n return results;\n}\n","/**\n * Cloud reporter orchestration — init, upload, and finalize cloud runs.\n *\n * Extracted from reporter.ts to comply with Constitution Principle II (500-line limit).\n * Never throws — all errors result in graceful degradation.\n */\n\nimport { join } from 'node:path';\nimport type {\n CloudConfig,\n TestRunReport,\n Summary,\n TestArtifacts,\n VisualComparison,\n} from '@testrelic/core';\nimport { CloudClient } from './cloud-client.js';\nimport { buildUploadPayload, uploadBatchRun, initRealtimeRun, finalizeRun, deriveRunTestType } from './cloud-upload.js';\nimport { detectCloudFarm } from './cloud-farm-detect.js';\nimport { resolveSdkRunTypeFromEnv } from './run-type-env.js';\nimport type { UploadFailure, TestResultForUpload } from './cloud-upload.js';\nimport { uploadArtifacts } from './cloud-artifact-upload.js';\nimport type { ArtifactUploadRequest } from './cloud-artifact-upload.js';\nimport { writeToQueue } from './cloud-queue.js';\nimport { detectCI } from './ci-detector.js';\nimport { normalizeGitRemoteUrl, deriveNonGitProjectId } from './git-metadata.js';\nimport type { CloudFinalizeOutcome } from './cloud-receipt.js';\nimport { Budget, DEFAULT_UPLOAD_BUDGET_MS } from './cloud-deadline.js';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction getEffectiveGitId(cloudClient: CloudClient): string {\n const gitMeta = cloudClient.getGitMetadata();\n const remoteUrl = gitMeta?.remoteUrl;\n if (remoteUrl) return normalizeGitRemoteUrl(remoteUrl);\n const projectName = cloudClient.getConfig()?.projectName;\n return projectName ?? deriveNonGitProjectId(process.cwd());\n}\n\n// ---------------------------------------------------------------------------\n// Realtime Init\n// ---------------------------------------------------------------------------\n\n/**\n * Initialize a realtime run on the cloud during onBegin.\n * Returns the cloud run ID, or null if init fails or is not applicable.\n */\nexport async function initCloudRun(\n cloudClient: CloudClient,\n uploadStrategy: string | undefined,\n testRunId: string,\n startedAt: string,\n environment?: string,\n): Promise<string | null> {\n if (!cloudClient.isCloudMode()) return null;\n if (uploadStrategy !== 'realtime' && uploadStrategy !== 'both') return null;\n\n const tokenValid = await cloudClient.ensureValidToken();\n if (!tokenValid) return null;\n\n const gitMeta = cloudClient.getGitMetadata();\n const ciMeta = detectCI();\n const cloudFarm = detectCloudFarm();\n const runType = resolveSdkRunTypeFromEnv();\n\n const initResult = await initRealtimeRun(\n cloudClient.getEndpoint(),\n cloudClient.getAccessToken()!,\n {\n runId: testRunId,\n repoGitId: getEffectiveGitId(cloudClient),\n branch: gitMeta?.branch ?? null,\n commit: gitMeta?.commitSha ?? null,\n commitMessage: gitMeta?.commitMessage ?? null,\n commitAuthor: gitMeta?.commitAuthor ?? null,\n startedAt,\n totalTests: null,\n ciProvider: ciMeta?.provider ?? null,\n ciRunUrl: ciMeta?.runUrl ?? null,\n environment: environment ?? (cloudFarm.cloudPlatform ?? null),\n cloudPlatform: cloudFarm.cloudPlatform ?? null,\n cloudBuildId: cloudFarm.cloudBuildId ?? null,\n cloudSessionId: cloudFarm.cloudSessionId ?? null,\n ...(runType ? { runType } : {}),\n // Playwright runs are browser-driven by default; finalize overwrites this\n // with the derived slug once the per-test types are known. Ensures a run\n // that crashes before finalize still carries a non-empty test type.\n testType: 'e2e',\n },\n );\n\n return initResult?.runId ?? null;\n}\n\n// ---------------------------------------------------------------------------\n// Artifact Collection\n// ---------------------------------------------------------------------------\n\nexport interface ArtifactEntry {\n readonly testId: string;\n readonly artifacts: TestArtifacts;\n /** Visual comparisons recorded for this test, if it made any. */\n readonly visual?: readonly VisualComparison[] | null;\n readonly outputDir: string;\n}\n\n/** Which image of a comparison maps to which upload type and storage-key field. */\nconst VISUAL_UPLOAD_ROLES = [\n { field: 'baseline', keyField: 'baselineKey', type: 'baseline' },\n { field: 'actual', keyField: 'actualKey', type: 'screenshot' },\n { field: 'diff', keyField: 'diffKey', type: 'diff' },\n { field: 'baselineDom', keyField: 'baselineDomKey', type: 'dom' },\n { field: 'actualDom', keyField: 'actualDomKey', type: 'dom' },\n] as const;\n\ntype VisualKeyField = (typeof VISUAL_UPLOAD_ROLES)[number]['keyField'];\n\nconst DEFAULT_UPLOAD_ARTIFACTS = true;\nconst DEFAULT_ARTIFACT_MAX_SIZE_MB = 50;\n\n/** Artifact key map plus the counts that go into the run's upload receipt. */\ninterface ArtifactUploadOutcome {\n readonly keyMap: Map<string, TestArtifacts>;\n /** Comparisons with their storage keys filled in, per testId. */\n readonly visualKeyMap: Map<string, VisualComparison[]>;\n /** Artifact files offered for upload. */\n readonly total: number;\n /** Artifact files the platform confirmed it stored. */\n readonly uploaded: number;\n}\n\nconst NO_ARTIFACTS: ArtifactUploadOutcome = {\n keyMap: new Map<string, TestArtifacts>(),\n visualKeyMap: new Map<string, VisualComparison[]>(),\n total: 0,\n uploaded: 0,\n};\n\n/**\n * Upload all binary artifacts and return a map from testId to updated TestArtifacts\n * with cloud storage keys filled in, alongside the offered/confirmed counts.\n *\n * `cloudConfig` may be null when the user authenticated via `testrelic login` (session-based\n * auth) without an explicit apiKey in the reporter config. In that case we fall back to\n * safe defaults so artifacts are still uploaded.\n */\nasync function uploadRunArtifacts(\n cloudClient: CloudClient,\n cloudConfig: CloudConfig | null | undefined,\n runId: string,\n artifactEntries: readonly ArtifactEntry[],\n budget: Budget,\n): Promise<ArtifactUploadOutcome> {\n const result = new Map<string, TestArtifacts>();\n const visualResult = new Map<string, VisualComparison[]>();\n\n const shouldUpload = cloudConfig?.uploadArtifacts ?? DEFAULT_UPLOAD_ARTIFACTS;\n if (!shouldUpload) return NO_ARTIFACTS;\n\n const tokenValid = await cloudClient.ensureValidToken();\n if (!tokenValid) return NO_ARTIFACTS;\n\n const requests: ArtifactUploadRequest[] = [];\n const requestToEntry = new Map<\n string,\n {\n testId: string;\n field: 'screenshot' | 'video' | 'trace' | 'visual';\n visualIndex?: number;\n visualKey?: VisualKeyField;\n }\n >();\n /** testId → comparison index → { baselineKey?, actualKey?, diffKey? } */\n const visualKeys = new Map<string, Map<number, Record<string, string>>>();\n\n for (const entry of artifactEntries) {\n if (entry.artifacts.screenshot) {\n const absPath = join(entry.outputDir, entry.artifacts.screenshot);\n requests.push({ filePath: absPath, runId, testId: entry.testId, type: 'screenshot' });\n requestToEntry.set(absPath, { testId: entry.testId, field: 'screenshot' });\n }\n if (entry.artifacts.video) {\n const absPath = join(entry.outputDir, entry.artifacts.video);\n requests.push({ filePath: absPath, runId, testId: entry.testId, type: 'video' });\n requestToEntry.set(absPath, { testId: entry.testId, field: 'video' });\n }\n if (entry.artifacts.trace) {\n const absPath = join(entry.outputDir, entry.artifacts.trace);\n requests.push({ filePath: absPath, runId, testId: entry.testId, type: 'trace' });\n requestToEntry.set(absPath, { testId: entry.testId, field: 'trace' });\n }\n const comparisons = entry.visual ?? [];\n for (let vi = 0; vi < comparisons.length; vi++) {\n const comparison = comparisons[vi];\n if (!comparison) continue;\n for (const role of VISUAL_UPLOAD_ROLES) {\n const relativePath = comparison[role.field];\n if (!relativePath) continue;\n const absPath = join(entry.outputDir, relativePath);\n requests.push({ filePath: absPath, runId, testId: entry.testId, type: role.type });\n requestToEntry.set(absPath, {\n testId: entry.testId,\n field: 'visual',\n visualIndex: vi,\n visualKey: role.keyField,\n });\n }\n }\n }\n\n if (requests.length === 0) return NO_ARTIFACTS;\n\n const maxSizeMb = cloudConfig?.artifactMaxSizeMb ?? DEFAULT_ARTIFACT_MAX_SIZE_MB;\n const uploadResults = await uploadArtifacts(\n cloudClient.getEndpoint(),\n cloudClient.getAccessToken()!,\n requests,\n maxSizeMb,\n budget,\n );\n\n // Build updated TestArtifacts per testId\n const keysByTest = new Map<\n string,\n { screenshotKey?: string; videoKey?: string; traceKey?: string }\n >();\n for (const [filePath, uploadResult] of uploadResults) {\n if (!uploadResult.success || !uploadResult.storageKey) continue;\n const meta = requestToEntry.get(filePath);\n if (!meta) continue;\n const existing = keysByTest.get(meta.testId) ?? {};\n if (meta.field === 'screenshot') existing.screenshotKey = uploadResult.storageKey;\n if (meta.field === 'video') existing.videoKey = uploadResult.storageKey;\n if (meta.field === 'trace') existing.traceKey = uploadResult.storageKey;\n if (meta.field === 'visual' && meta.visualIndex !== undefined && meta.visualKey) {\n const perTest = visualKeys.get(meta.testId) ?? new Map<number, Record<string, string>>();\n const perComparison = perTest.get(meta.visualIndex) ?? {};\n perComparison[meta.visualKey] = uploadResult.storageKey;\n perTest.set(meta.visualIndex, perComparison);\n visualKeys.set(meta.testId, perTest);\n }\n keysByTest.set(meta.testId, existing);\n }\n\n // Merge with original artifacts\n for (const entry of artifactEntries) {\n if (entry.visual && entry.visual.length > 0) {\n const perTest = visualKeys.get(entry.testId);\n visualResult.set(\n entry.testId,\n entry.visual.map((comparison, index) => ({\n ...comparison,\n ...(perTest?.get(index) ?? {}),\n })),\n );\n }\n const keys = keysByTest.get(entry.testId);\n result.set(entry.testId, {\n ...entry.artifacts,\n ...(keys?.screenshotKey ? { screenshotKey: keys.screenshotKey } : {}),\n ...(keys?.videoKey ? { videoKey: keys.videoKey } : {}),\n ...(keys?.traceKey ? { traceKey: keys.traceKey } : {}),\n });\n }\n\n const uploaded = Array.from(uploadResults.values()).filter((r) => r.success).length;\n if (uploaded > 0) {\n process.stderr.write(`✓ TestRelic: Uploaded ${uploaded} artifact(s) to cloud storage.\\n`);\n }\n\n return { keyMap: result, visualKeyMap: visualResult, total: requests.length, uploaded };\n}\n\n// ---------------------------------------------------------------------------\n// Finalize & Upload\n// ---------------------------------------------------------------------------\n\n/**\n * Finalize realtime run, perform batch upload, and dispose the cloud client.\n * Queues failed uploads for later retry. Never throws.\n *\n * Returns what the platform actually did, so the reporter can write an honest\n * `cloudUpload` receipt into `summary.json` instead of leaving consumers to infer\n * \"uploaded\" from the locally generated `testRunId`.\n *\n * @param artifactEntries - collected test artifacts to upload before the batch payload.\n * When provided, binary files are uploaded and storage keys injected into the report.\n * @param outputDir - root output directory for resolving relative artifact paths.\n */\nexport async function finalizeAndUpload(\n cloudClient: CloudClient,\n cloudConfig: CloudConfig | undefined | null,\n uploadStrategy: string | undefined,\n testRunId: string,\n cloudRunId: string | null,\n report: TestRunReport,\n completedAt: string,\n totalDuration: number,\n summary: Summary,\n artifactEntries?: readonly ArtifactEntry[],\n tests?: readonly TestResultForUpload[],\n): Promise<CloudFinalizeOutcome> {\n const outcome = {\n realtimeFinalizeAttempted: false,\n realtimeFinalized: false,\n batchAttempted: false,\n batchAcknowledged: false,\n artifactsTotal: 0,\n artifactsUploaded: 0,\n failureReason: null as string | null,\n };\n\n // ⛔ ONE budget for the whole phase, started here.\n //\n // This function runs inside Playwright's `onEnd`, which the runner awaits\n // before the process may exit — so everything below is, in the tester's\n // experience, \"the run hasn't finished\". Before this, nothing bounded it:\n // each request used bare `fetch` (undici's 5-minute defaults) inside a\n // 3-attempt retry, so a wedged endpoint held the process for a quarter of an\n // hour per request family, and Ctrl+C could not cancel a request that had no\n // abort signal. Measured on a real run: 11m49s of \"Reporter is still stuck\".\n //\n // Expiry is not data loss: the local report is already written, the queue\n // holds what did not land, and the next run retries.\n const budget = new Budget(cloudConfig?.uploadBudgetMs ?? DEFAULT_UPLOAD_BUDGET_MS);\n /** Keep the FIRST failure — it is the one that explains the rest. */\n const noteFailure = (reason: string): void => {\n if (!outcome.failureReason) outcome.failureReason = reason;\n };\n\n try {\n // Finalize realtime run (with retry; queue on failure for later retry)\n if (cloudClient.isCloudMode() && cloudRunId && (uploadStrategy === 'realtime' || uploadStrategy === 'both')) {\n outcome.realtimeFinalizeAttempted = true;\n const tokenValid = await cloudClient.ensureValidToken();\n const gitMetaForFinalize = cloudClient.getGitMetadata();\n // Realtime /runs/init ran before any test, so the run-level test type is\n // only fully known now. Send the derived slug (Web / API / Mobile / Unit);\n // the api protocol + metadata ride along only when the run is all-API.\n const finalizeTypeSignal = deriveRunTestType(tests);\n const finalizePayload = {\n finishedAt: completedAt,\n duration: totalDuration,\n summary,\n ...(gitMetaForFinalize?.commitMessage ? { commitMessage: gitMetaForFinalize.commitMessage } : {}),\n testType: finalizeTypeSignal.testType,\n ...(finalizeTypeSignal.testType === 'api'\n ? { apiProtocol: finalizeTypeSignal.apiProtocol, metadata: finalizeTypeSignal.metadata }\n : {}),\n };\n if (tokenValid) {\n const finalized = await finalizeRun(\n cloudClient.getEndpoint(),\n cloudClient.getAccessToken()!,\n cloudRunId,\n finalizePayload,\n );\n outcome.realtimeFinalized = finalized;\n if (!finalized) {\n noteFailure('finalize_failed_after_retries');\n const queueDir = cloudConfig?.queueDirectory ?? '.testrelic/queue';\n writeToQueue(\n queueDir,\n testRunId,\n 'finalize',\n 'finalize_failed_after_retries',\n `${cloudClient.getEndpoint()}/runs/${cloudRunId}/finalize`,\n 'POST',\n finalizePayload as unknown as Record<string, unknown>,\n { 'Content-Type': 'application/json' },\n );\n }\n } else {\n noteFailure(cloudClient.getFailureReason() ?? 'token_invalid');\n const queueDir = cloudConfig?.queueDirectory ?? '.testrelic/queue';\n writeToQueue(\n queueDir,\n testRunId,\n 'finalize',\n cloudClient.getFailureReason() ?? 'token_invalid',\n `${cloudClient.getEndpoint()}/runs/${cloudRunId}/finalize`,\n 'POST',\n finalizePayload as unknown as Record<string, unknown>,\n { 'Content-Type': 'application/json' },\n );\n }\n }\n\n // Upload binary artifacts before building the batch payload.\n // cloudConfig may be null when the user authenticated via `testrelic login` — fall back\n // to defaults inside uploadRunArtifacts so artifacts still reach cloud storage.\n let artifactKeyMap = new Map<string, TestArtifacts>();\n let visualKeyMap = new Map<string, VisualComparison[]>();\n if (cloudClient.isCloudMode() && artifactEntries && artifactEntries.length > 0) {\n const artifactOutcome = await uploadRunArtifacts(cloudClient, cloudConfig, testRunId, artifactEntries, budget);\n artifactKeyMap = artifactOutcome.keyMap;\n visualKeyMap = artifactOutcome.visualKeyMap;\n outcome.artifactsTotal = artifactOutcome.total;\n outcome.artifactsUploaded = artifactOutcome.uploaded;\n }\n\n // Batch upload (default, or 'both' mode)\n if (cloudClient.isCloudMode() && (!uploadStrategy || uploadStrategy === 'batch' || uploadStrategy === 'both')) {\n outcome.batchAttempted = true;\n const tokenValid = await cloudClient.ensureValidToken();\n if (tokenValid) {\n const batchGit = cloudClient.getGitMetadata();\n const batchCi = detectCI();\n const batchGitId = getEffectiveGitId(cloudClient);\n\n // Inject artifact storage keys into the report timeline before uploading\n const enrichedReport = injectVisualKeys(\n injectArtifactKeys(report, artifactKeyMap),\n visualKeyMap,\n );\n const payload = buildUploadPayload(\n enrichedReport,\n batchGitId,\n batchGit,\n batchCi,\n testsWithVisualKeys(tests, visualKeyMap),\n detectCloudFarm(),\n );\n\n const result = await uploadBatchRun(\n cloudClient.getEndpoint(),\n cloudClient.getAccessToken()!,\n payload,\n async () => {\n const refreshed = await cloudClient.ensureValidToken();\n return refreshed ? cloudClient.getAccessToken() : null;\n },\n cloudConfig?.timeout,\n budget,\n );\n\n outcome.batchAcknowledged = result.success;\n if (!result.success) {\n const failure = result as UploadFailure;\n noteFailure(failure.reason);\n const queueDir = cloudConfig?.queueDirectory ?? '.testrelic/queue';\n writeToQueue(queueDir, testRunId, 'batch', failure.reason, failure.targetEndpoint, failure.method, failure.payload, { 'Content-Type': 'application/json' });\n }\n } else {\n // Token invalid — queue the payload\n noteFailure(cloudClient.getFailureReason() ?? 'token_invalid');\n const queueDir = cloudConfig?.queueDirectory ?? '.testrelic/queue';\n const queueGit = cloudClient.getGitMetadata();\n const queueCi = detectCI();\n const queueGitId = getEffectiveGitId(cloudClient);\n const queuePayload = buildUploadPayload(report, queueGitId, queueGit, queueCi, undefined, detectCloudFarm());\n writeToQueue(\n queueDir,\n testRunId,\n 'batch',\n cloudClient.getFailureReason() ?? 'token_invalid',\n `${cloudClient.getEndpoint()}/runs`,\n 'POST',\n queuePayload as unknown as Record<string, unknown>,\n { 'Content-Type': 'application/json' },\n );\n }\n }\n\n // Dispose cloud client (clean up timers, flush)\n await cloudClient.dispose();\n } catch (err) {\n // Cloud errors never prevent local report writing — but they must still be\n // reported, or the receipt would claim a clean upload after an exception.\n noteFailure(err instanceof Error ? err.message : 'unexpected_upload_error');\n try { await cloudClient.dispose(); } catch { /* ignore */ }\n }\n return outcome;\n}\n\n/**\n * Inject cloud storage keys into timeline steps that have matching test artifacts.\n * Returns a shallow copy of the report with enriched timeline entries.\n */\n/**\n * Put the cloud storage keys onto the per-test batch array as well.\n *\n * `injectVisualKeys` enriches `report.timeline[].tests[].visual`, but the batch\n * payload's `tests` array is a SEPARATE snapshot taken at `onTestEnd` — long\n * before `uploadRunArtifacts` computes the keys — and it is handed to\n * `buildUploadPayload` verbatim. So the copy the server re-ingests per test had\n * local report-relative paths and no `baselineKey`/`actualKey`/`diffKey`.\n *\n * That is not cosmetic. In STREAMING mode `buildStreamingReport` returns\n * `timeline: []`, so the enriched copy does not exist at all and this array is\n * the only visual record the cloud ever sees — metadata with no way to reach\n * the images, the exact inverse of the bug the `visual` field was added to fix.\n * In batch mode it is worse than useless: two divergent copies of the same\n * comparisons, and the per-test one is documented elsewhere in this pipeline as\n * the authoritative re-ingest that overwrites the other.\n */\nexport function testsWithVisualKeys(\n tests: readonly TestResultForUpload[] | undefined,\n visualKeyMap: Map<string, VisualComparison[]>,\n): readonly TestResultForUpload[] | undefined {\n if (!tests || visualKeyMap.size === 0) return tests;\n return tests.map((test) => {\n const withKeys = visualKeyMap.get(test.testId);\n if (!withKeys) return test;\n return { ...test, visual: withKeys as unknown as Record<string, unknown>[] };\n });\n}\n\n/**\n * Write cloud storage keys back onto each test's visual comparisons.\n *\n * Unlike screenshots and videos, comparisons live on the tests inside a\n * timeline entry rather than on the entry itself, so the rewrite descends\n * into `entry.tests` and leaves everything it does not recognise alone.\n */\nfunction injectVisualKeys(\n report: TestRunReport,\n visualKeyMap: Map<string, VisualComparison[]>,\n): TestRunReport {\n if (visualKeyMap.size === 0) return report;\n\n const enrichedTimeline = report.timeline.map((entry) => {\n // SAFETY: the timeline is a union of shapes; only entries that actually\n // carry a `tests` array are rewritten.\n const tests = (entry as { tests?: unknown }).tests;\n if (!Array.isArray(tests)) return entry;\n\n let changed = false;\n const rewritten = tests.map((test) => {\n const testId = (test as { testId?: string }).testId;\n if (!testId) return test;\n const withKeys = visualKeyMap.get(testId);\n if (!withKeys) return test;\n changed = true;\n return { ...(test as object), visual: withKeys };\n });\n\n return changed ? { ...entry, tests: rewritten } : entry;\n });\n\n return { ...report, timeline: enrichedTimeline as TestRunReport['timeline'] };\n}\n\nfunction injectArtifactKeys(\n report: TestRunReport,\n keyMap: Map<string, TestArtifacts>,\n): TestRunReport {\n if (keyMap.size === 0) return report;\n\n const enrichedTimeline = report.timeline.map((entry) => {\n // `testId` is present on the step arm of the timeline union and absent on\n // the entry arm, so the read is probing rather than asserting.\n const testId = (entry as unknown as Record<string, unknown>).testId as string | undefined;\n if (!testId) return entry;\n\n const keys = keyMap.get(testId);\n if (!keys) return entry;\n\n return {\n ...entry,\n ...(keys.screenshotKey ? { screenshotKey: keys.screenshotKey } : {}),\n ...(keys.videoKey ? { videoKey: keys.videoKey } : {}),\n };\n });\n\n return { ...report, timeline: enrichedTimeline };\n}\n","/**\n * Cloud upload receipt — the honest record of whether a run reached the platform.\n *\n * `summary.json` has always carried `testRunId`, and consumers (TestRelic Studio\n * among them) read its presence as proof the run was uploaded. It is not proof:\n * `testRunId` is `config.testRunId ?? randomUUID()`, generated locally in `onBegin`\n * and written on every run — including a fully offline one, which then renders as\n * \"Uploaded\" behind a dashboard link that 404s.\n *\n * The platform does not mint run ids either — `POST /runs/init` and `POST /runs`\n * both file the run under the id the SDK sent — so there is no server-side id to\n * record instead. What the SDK *does* know, and never wrote down, is whether the\n * platform acknowledged the run. That is what `cloudUpload` carries.\n *\n * The block is written on EVERY run, so a consumer can distinguish three states:\n * - block absent → an SDK too old to report a verdict; treat as unknown\n * - `acknowledged:false` → the run did not reach the platform, and `detail` says why\n * - `acknowledged:true` → the platform accepted the run under `runId`\n *\n * It is written twice per run, both times atomically (tmp + rename): once with the\n * pre-upload state, and again once the upload settles. A process killed between the\n * two leaves the earlier, more conservative verdict on disk — this block under-claims\n * when it is wrong, never over-claims.\n */\n\nimport type { CloudClient } from './cloud-client.js';\n\n/** Upload strategies the reporter actually acts on. Anything else uploads nothing. */\nconst KNOWN_STRATEGIES: ReadonlySet<string> = new Set(['batch', 'realtime', 'both']);\n\n/**\n * Whether the platform acknowledged this run, and what it did with it.\n * Written to `summary.json` as `cloudUpload` on every run.\n */\nexport interface CloudUploadReceipt {\n /** Cloud mode was on and a strategy the reporter acts on was selected. */\n readonly attempted: boolean;\n /** The platform accepted the run — realtime `/runs/init`, or the batch `POST /runs`. */\n readonly acknowledged: boolean;\n /** The id the run was filed under on the platform, or null when never acknowledged. */\n readonly runId: string | null;\n /** The ingest endpoint the run was sent to, or null when nothing was sent. */\n readonly endpoint: string | null;\n /** The run was closed out — realtime finalize returned ok, or the batch upload landed. */\n readonly finalized: boolean;\n /** Artifacts the platform confirmed it stored. */\n readonly artifactsUploaded: number;\n /** Artifacts offered for upload. */\n readonly artifactsTotal: number;\n /** Short plain reason when the run was not acknowledged. Null once it was. */\n readonly detail: string | null;\n}\n\n/** A streaming summary carrying the upload receipt. */\nexport type WithCloudUpload<T> = T & { readonly cloudUpload: CloudUploadReceipt };\n\n/** What `finalizeAndUpload` observed, so the reporter can compose the final receipt. */\nexport interface CloudFinalizeOutcome {\n /** A realtime finalize POST was issued (strategy realtime/both, run was initialised). */\n readonly realtimeFinalizeAttempted: boolean;\n /** That finalize POST returned ok. */\n readonly realtimeFinalized: boolean;\n /** A batch `POST /runs` was issued (strategy batch/both). */\n readonly batchAttempted: boolean;\n /** That batch POST landed (2xx, or 409 duplicate — the data is already ingested). */\n readonly batchAcknowledged: boolean;\n /** Artifacts offered for upload. */\n readonly artifactsTotal: number;\n /** Artifacts the platform confirmed it stored. */\n readonly artifactsUploaded: number;\n /** Short machine reason for the first thing that failed, if anything did. */\n readonly failureReason: string | null;\n}\n\n/** The receipt for a run where nothing was ever wired up to the cloud. */\nexport const UNATTEMPTED_RECEIPT: CloudUploadReceipt = Object.freeze({\n attempted: false,\n acknowledged: false,\n runId: null,\n endpoint: null,\n finalized: false,\n artifactsUploaded: 0,\n artifactsTotal: 0,\n detail: 'cloud reporting was never initialised',\n});\n\n/** Translate a CloudClient failure reason into one short plain sentence. */\nfunction describeLocalMode(reason: string | null): string {\n switch (reason) {\n case 'no_api_key':\n return 'cloud mode off — no API key configured';\n case 'cloud_unreachable':\n case 'network_error':\n return 'cloud endpoint unreachable — the run stayed local';\n case 'invalid_api_key':\n return 'API key rejected as invalid or revoked — the run stayed local';\n case 'expired_api_key':\n return 'API key has expired — the run stayed local';\n case 'validation_error':\n return 'API key format rejected — the run stayed local';\n case 'rate_limited':\n return 'rate limited during authentication — the run stayed local';\n case 'auth_timeout':\n return 'authentication timed out — the run stayed local';\n case 'token_expired_no_refresh':\n case 'token_refresh_failed':\n case 'token_refresh_error':\n return 'access token could not be refreshed — the run stayed local';\n case 'init_error':\n return 'cloud initialisation timed out — the run stayed local';\n case 'unexpected_error':\n return 'cloud initialisation failed — the run stayed local';\n default:\n return reason\n ? `cloud mode off (${reason}) — the run stayed local`\n : 'cloud mode off — the run stayed local';\n }\n}\n\n/**\n * Build the receipt as it stands right after `onBegin` — before any finalize or\n * batch upload has run. This is the verdict a crashed or interrupted run carries.\n */\nexport function buildInitialReceipt(\n cloudClient: CloudClient | null,\n uploadStrategy: string | undefined,\n cloudRunId: string | null,\n): CloudUploadReceipt {\n if (!cloudClient) return UNATTEMPTED_RECEIPT;\n\n if (!cloudClient.isCloudMode()) {\n return {\n ...UNATTEMPTED_RECEIPT,\n detail: describeLocalMode(cloudClient.getFailureReason()),\n };\n }\n\n // `isValidCloudConfig` rejects an unknown strategy upstream (the whole cloud block\n // falls back to defaults), so this should be unreachable through config. It is kept\n // because `finalizeAndUpload` takes `uploadStrategy: string | undefined` from any\n // caller, and an unrecognised value matches neither of its branches — the run then\n // uploads nothing at all, silently. Name that rather than imply it shipped.\n const strategy = uploadStrategy ?? 'batch';\n if (!KNOWN_STRATEGIES.has(strategy)) {\n return {\n ...UNATTEMPTED_RECEIPT,\n detail: `upload strategy '${strategy}' is not recognised — nothing was sent`,\n };\n }\n\n const endpoint = cloudClient.getEndpoint();\n const isRealtime = strategy === 'realtime' || strategy === 'both';\n\n if (isRealtime && cloudRunId) {\n return {\n attempted: true,\n acknowledged: true,\n runId: cloudRunId,\n endpoint,\n finalized: false,\n artifactsUploaded: 0,\n artifactsTotal: 0,\n detail: null,\n };\n }\n\n return {\n attempted: true,\n acknowledged: false,\n runId: null,\n endpoint,\n finalized: false,\n artifactsUploaded: 0,\n artifactsTotal: 0,\n detail: isRealtime\n ? 'run init failed — the platform did not acknowledge this run'\n : `upload runs at the end of the run under the '${strategy}' strategy — not sent yet`,\n };\n}\n\n/**\n * Fold what the upload phase observed into the receipt.\n *\n * `testRunId` is the id the SDK filed the run under; it becomes `runId` only once the\n * platform has actually answered, so an unacknowledged run still reports `runId: null`.\n */\nexport function applyFinalizeOutcome(\n receipt: CloudUploadReceipt,\n outcome: CloudFinalizeOutcome | undefined,\n testRunId: string,\n): CloudUploadReceipt {\n if (!receipt.attempted) return receipt;\n\n if (!outcome) {\n return {\n ...receipt,\n detail: receipt.acknowledged\n ? 'upload timed out before it completed — the run may be incomplete on the platform'\n : 'upload timed out before the platform acknowledged this run',\n acknowledged: receipt.acknowledged,\n };\n }\n\n const acknowledged = receipt.acknowledged || outcome.batchAcknowledged;\n const runId = receipt.runId ?? (outcome.batchAcknowledged ? testRunId : null);\n\n // \"Closed out\" means different things per strategy: realtime has an explicit finalize\n // POST, while a batch upload IS the whole run — landing it closes the run by itself.\n const finalized = outcome.realtimeFinalizeAttempted\n ? outcome.realtimeFinalized\n : outcome.batchAcknowledged;\n\n let detail: string | null = null;\n if (!acknowledged) {\n detail = outcome.failureReason\n ? `upload failed (${outcome.failureReason}) — queued locally for retry`\n : 'upload failed — the platform did not acknowledge this run';\n } else if (!finalized) {\n detail = 'run acknowledged but not closed out — finalize failed and was queued for retry';\n }\n\n return {\n attempted: true,\n acknowledged,\n runId,\n endpoint: receipt.endpoint,\n finalized,\n artifactsUploaded: outcome.artifactsUploaded,\n artifactsTotal: outcome.artifactsTotal,\n detail,\n };\n}\n"]}