@testrelic/core 1.3.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +48 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +209 -2
- package/dist/index.d.ts +209 -2
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
package/dist/index.cjs
CHANGED
|
@@ -20,16 +20,30 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
ATTACHMENT_CONTENT_TYPE: () => ATTACHMENT_CONTENT_TYPE,
|
|
24
|
+
ATTACHMENT_NAME: () => ATTACHMENT_NAME,
|
|
23
25
|
ErrorCode: () => ErrorCode,
|
|
26
|
+
PAYLOAD_VERSION: () => PAYLOAD_VERSION,
|
|
24
27
|
TestRelicError: () => TestRelicError,
|
|
25
28
|
createError: () => createError,
|
|
26
29
|
createLogger: () => createLogger,
|
|
30
|
+
isTestRelicDataPayload: () => isTestRelicDataPayload,
|
|
27
31
|
isValidConfig: () => isValidConfig,
|
|
28
32
|
isValidNavigationType: () => isValidNavigationType,
|
|
29
33
|
isValidTestRunReport: () => isValidTestRunReport
|
|
30
34
|
});
|
|
31
35
|
module.exports = __toCommonJS(index_exports);
|
|
32
36
|
|
|
37
|
+
// src/types.ts
|
|
38
|
+
var PAYLOAD_VERSION = "1.0.0";
|
|
39
|
+
var ATTACHMENT_NAME = "testrelic-data";
|
|
40
|
+
var ATTACHMENT_CONTENT_TYPE = "application/json";
|
|
41
|
+
function isTestRelicDataPayload(obj) {
|
|
42
|
+
if (typeof obj !== "object" || obj === null) return false;
|
|
43
|
+
const record = obj;
|
|
44
|
+
return record.testRelicData === true && typeof record.version === "string" && record.version.length > 0 && Array.isArray(record.navigations) && Array.isArray(record.networkRequests) && Array.isArray(record.apiCalls) && Array.isArray(record.apiAssertions);
|
|
45
|
+
}
|
|
46
|
+
|
|
33
47
|
// src/logger.ts
|
|
34
48
|
var noopHandler = () => {
|
|
35
49
|
};
|
|
@@ -134,6 +148,36 @@ function isValidConfig(input) {
|
|
|
134
148
|
if (obj.openReport !== void 0 && typeof obj.openReport !== "boolean") return false;
|
|
135
149
|
if (obj.htmlReportPath !== void 0 && (typeof obj.htmlReportPath !== "string" || obj.htmlReportPath === "")) return false;
|
|
136
150
|
if (obj.includeArtifacts !== void 0 && typeof obj.includeArtifacts !== "boolean") return false;
|
|
151
|
+
if (obj.trackApiCalls !== void 0 && typeof obj.trackApiCalls !== "boolean") return false;
|
|
152
|
+
if (obj.captureRequestHeaders !== void 0 && typeof obj.captureRequestHeaders !== "boolean") return false;
|
|
153
|
+
if (obj.captureResponseHeaders !== void 0 && typeof obj.captureResponseHeaders !== "boolean") return false;
|
|
154
|
+
if (obj.captureRequestBody !== void 0 && typeof obj.captureRequestBody !== "boolean") return false;
|
|
155
|
+
if (obj.captureResponseBody !== void 0 && typeof obj.captureResponseBody !== "boolean") return false;
|
|
156
|
+
if (obj.captureAssertions !== void 0 && typeof obj.captureAssertions !== "boolean") return false;
|
|
157
|
+
if (obj.redactHeaders !== void 0) {
|
|
158
|
+
if (!Array.isArray(obj.redactHeaders)) return false;
|
|
159
|
+
for (const h of obj.redactHeaders) {
|
|
160
|
+
if (typeof h !== "string") return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (obj.redactBodyFields !== void 0) {
|
|
164
|
+
if (!Array.isArray(obj.redactBodyFields)) return false;
|
|
165
|
+
for (const f of obj.redactBodyFields) {
|
|
166
|
+
if (typeof f !== "string") return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (obj.apiIncludeUrls !== void 0) {
|
|
170
|
+
if (!Array.isArray(obj.apiIncludeUrls)) return false;
|
|
171
|
+
for (const p of obj.apiIncludeUrls) {
|
|
172
|
+
if (typeof p !== "string" && !(p instanceof RegExp)) return false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (obj.apiExcludeUrls !== void 0) {
|
|
176
|
+
if (!Array.isArray(obj.apiExcludeUrls)) return false;
|
|
177
|
+
for (const p of obj.apiExcludeUrls) {
|
|
178
|
+
if (typeof p !== "string" && !(p instanceof RegExp)) return false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
137
181
|
return true;
|
|
138
182
|
}
|
|
139
183
|
function isValidTestRunReport(value) {
|
|
@@ -157,10 +201,14 @@ function isValidTestRunReport(value) {
|
|
|
157
201
|
}
|
|
158
202
|
// Annotate the CommonJS export names for ESM import in node:
|
|
159
203
|
0 && (module.exports = {
|
|
204
|
+
ATTACHMENT_CONTENT_TYPE,
|
|
205
|
+
ATTACHMENT_NAME,
|
|
160
206
|
ErrorCode,
|
|
207
|
+
PAYLOAD_VERSION,
|
|
161
208
|
TestRelicError,
|
|
162
209
|
createError,
|
|
163
210
|
createLogger,
|
|
211
|
+
isTestRelicDataPayload,
|
|
164
212
|
isValidConfig,
|
|
165
213
|
isValidNavigationType,
|
|
166
214
|
isValidTestRunReport
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/logger.ts","../src/errors.ts","../src/validation.ts"],"sourcesContent":["// @testrelic/core — barrel export\n// Re-exports only, no implementation (Constitution II)\n\nexport type {\n NavigationType,\n TestRunReport,\n Summary,\n CIMetadata,\n CIProvider,\n TimelineEntry,\n TestResult,\n TestStatus,\n TestType,\n FailureDiagnostic,\n NetworkStats,\n ResourceBreakdown,\n ResourceType,\n CapturedNetworkRequest,\n ReporterConfig,\n TestArtifacts,\n NavigationAnnotation,\n MergeOptions,\n} from './types.js';\n\nexport type { Logger, LoggerOptions, LogLevel } from './logger.js';\nexport { createLogger } from './logger.js';\n\nexport { ErrorCode, TestRelicError, createError } from './errors.js';\n\nexport {\n isValidConfig,\n isValidNavigationType,\n isValidTestRunReport,\n} from './validation.js';\n","/**\n * @testrelic/core — Configurable logger\n *\n * Provides a Logger interface with a no-op default.\n * Never uses console.* directly (Constitution SDK Constraint).\n */\n\nexport interface Logger {\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n debug(message: string, ...args: unknown[]): void;\n}\n\nexport interface LoggerOptions {\n readonly handler?: (level: LogLevel, message: string, args: unknown[]) => void;\n}\n\nexport type LogLevel = 'info' | 'warn' | 'error' | 'debug';\n\ntype LogHandler = (level: LogLevel, message: string, args: unknown[]) => void;\n\nconst noopHandler: LogHandler = () => {};\n\nexport function createLogger(options?: LoggerOptions): Logger {\n const handler = options?.handler ?? noopHandler;\n\n return {\n info(message: string, ...args: unknown[]) {\n handler('info', message, args);\n },\n warn(message: string, ...args: unknown[]) {\n handler('warn', message, args);\n },\n error(message: string, ...args: unknown[]) {\n handler('error', message, args);\n },\n debug(message: string, ...args: unknown[]) {\n handler('debug', message, args);\n },\n };\n}\n","/**\n * @testrelic/core — Structured error factory\n *\n * Machine-readable error codes for programmatic handling.\n */\n\nexport const ErrorCode = {\n CONFIG_INVALID: 'TESTRELIC_CONFIG_INVALID',\n OUTPUT_WRITE_FAILED: 'TESTRELIC_OUTPUT_WRITE_FAILED',\n MERGE_INVALID_SCHEMA: 'TESTRELIC_MERGE_INVALID_SCHEMA',\n MERGE_READ_FAILED: 'TESTRELIC_MERGE_READ_FAILED',\n NAVIGATION_FLUSH_FAILED: 'TESTRELIC_NAVIGATION_FLUSH_FAILED',\n CODE_EXTRACT_FAILED: 'TESTRELIC_CODE_EXTRACT_FAILED',\n HTML_REPORT_FAILED: 'TESTRELIC_HTML_REPORT_FAILED',\n} as const;\n\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\nexport class TestRelicError extends Error {\n readonly code: ErrorCode;\n\n constructor(code: ErrorCode, message: string, cause?: unknown) {\n super(message);\n this.name = 'TestRelicError';\n this.code = code;\n if (cause !== undefined) {\n Object.defineProperty(this, 'cause', { value: cause, writable: false, enumerable: false });\n }\n }\n}\n\nexport function createError(\n code: ErrorCode,\n message: string,\n cause?: unknown,\n): TestRelicError {\n return new TestRelicError(code, message, cause);\n}\n","/**\n * @testrelic/core — Lightweight runtime type guards\n *\n * Hand-written guards, no runtime schema libraries (Constitution SDK Constraint).\n */\n\nimport type {\n ReporterConfig,\n NavigationType,\n TestRunReport,\n} from './types.js';\n\nconst NAVIGATION_TYPES = new Set<string>([\n 'goto', 'navigation', 'back', 'forward', 'refresh',\n 'spa_route', 'spa_replace', 'hash_change', 'link_click',\n 'form_submit', 'redirect', 'popstate', 'page_load',\n 'manual_record', 'fallback', 'dummy',\n]);\n\nconst DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\n\nexport function isValidNavigationType(value: unknown): value is NavigationType {\n return typeof value === 'string' && NAVIGATION_TYPES.has(value);\n}\n\nfunction hasNoPrototypePollution(obj: unknown): boolean {\n if (typeof obj !== 'object' || obj === null) return true;\n for (const key of Object.keys(obj)) {\n if (DANGEROUS_KEYS.has(key)) return false;\n }\n return true;\n}\n\nexport function isValidConfig(input: unknown): input is ReporterConfig {\n if (typeof input !== 'object' || input === null) return false;\n if (!hasNoPrototypePollution(input)) return false;\n\n const obj = input as Record<string, unknown>;\n\n if (obj.outputPath !== undefined && typeof obj.outputPath !== 'string') return false;\n if (obj.includeStackTrace !== undefined && typeof obj.includeStackTrace !== 'boolean') return false;\n if (obj.includeCodeSnippets !== undefined && typeof obj.includeCodeSnippets !== 'boolean') return false;\n if (obj.codeContextLines !== undefined && typeof obj.codeContextLines !== 'number') return false;\n if (obj.includeNetworkStats !== undefined && typeof obj.includeNetworkStats !== 'boolean') return false;\n\n if (obj.navigationTypes !== undefined && obj.navigationTypes !== null) {\n if (!Array.isArray(obj.navigationTypes)) return false;\n for (const t of obj.navigationTypes) {\n if (!isValidNavigationType(t)) return false;\n }\n }\n\n if (obj.redactPatterns !== undefined) {\n if (!Array.isArray(obj.redactPatterns)) return false;\n for (const p of obj.redactPatterns) {\n if (typeof p !== 'string' && !(p instanceof RegExp)) return false;\n }\n }\n\n if (obj.testRunId !== undefined && obj.testRunId !== null && typeof obj.testRunId !== 'string') return false;\n\n if (obj.metadata !== undefined && obj.metadata !== null) {\n if (typeof obj.metadata !== 'object') return false;\n if (!hasNoPrototypePollution(obj.metadata)) return false;\n }\n\n if (obj.openReport !== undefined && typeof obj.openReport !== 'boolean') return false;\n if (obj.htmlReportPath !== undefined && (typeof obj.htmlReportPath !== 'string' || obj.htmlReportPath === '')) return false;\n if (obj.includeArtifacts !== undefined && typeof obj.includeArtifacts !== 'boolean') return false;\n\n return true;\n}\n\nexport function isValidTestRunReport(value: unknown): value is TestRunReport {\n if (typeof value !== 'object' || value === null) return false;\n\n const obj = value as Record<string, unknown>;\n\n if (typeof obj.schemaVersion !== 'string') return false;\n if (typeof obj.testRunId !== 'string') return false;\n if (typeof obj.startedAt !== 'string') return false;\n if (typeof obj.completedAt !== 'string') return false;\n if (typeof obj.totalDuration !== 'number') return false;\n if (typeof obj.summary !== 'object' || obj.summary === null) return false;\n if (!Array.isArray(obj.timeline)) return false;\n\n const summary = obj.summary as Record<string, unknown>;\n if (typeof summary.total !== 'number') return false;\n if (typeof summary.passed !== 'number') return false;\n if (typeof summary.failed !== 'number') return false;\n if (typeof summary.flaky !== 'number') return false;\n if (typeof summary.skipped !== 'number') return false;\n // timedout is optional for backward compat with schema 1.0.0\n if (summary.timedout !== undefined && typeof summary.timedout !== 'number') return false;\n\n return true;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/types.ts","../src/logger.ts","../src/errors.ts","../src/validation.ts"],"sourcesContent":["// @testrelic/core — barrel export\n// Re-exports only, no implementation (Constitution II)\n\nexport type {\n NavigationType,\n TestRunReport,\n Summary,\n CIMetadata,\n CIProvider,\n TimelineEntry,\n TestResult,\n TestStatus,\n TestType,\n FailureDiagnostic,\n NetworkStats,\n ResourceBreakdown,\n ResourceType,\n CapturedNetworkRequest,\n ApiCallRecord,\n ApiAssertion,\n AssertionType,\n AssertionLocation,\n ReporterConfig,\n TestArtifacts,\n NavigationAnnotation,\n MergeOptions,\n StepTestIdentity,\n ApiCallStepRequest,\n ApiCallStepResponse,\n StepAssertion,\n NavigationStep,\n ApiCallStep,\n TimelineStep,\n TestRelicDataPayload,\n} from './types.js';\n\nexport {\n PAYLOAD_VERSION,\n ATTACHMENT_NAME,\n ATTACHMENT_CONTENT_TYPE,\n isTestRelicDataPayload,\n} from './types.js';\n\nexport type { Logger, LoggerOptions, LogLevel } from './logger.js';\nexport { createLogger } from './logger.js';\n\nexport { ErrorCode, TestRelicError, createError } from './errors.js';\n\nexport {\n isValidConfig,\n isValidNavigationType,\n isValidTestRunReport,\n} from './validation.js';\n","/**\n * @testrelic/core — Shared type definitions\n *\n * All interfaces for the JSON output schema, reporter configuration,\n * and internal fixture-to-reporter communication.\n *\n * Schema Version: 1.0.0\n */\n\n// ---------------------------------------------------------------------------\n// Navigation Types\n// ---------------------------------------------------------------------------\n\nexport type NavigationType =\n | 'goto'\n | 'navigation'\n | 'back'\n | 'forward'\n | 'refresh'\n | 'spa_route'\n | 'spa_replace'\n | 'hash_change'\n | 'link_click'\n | 'form_submit'\n | 'redirect'\n | 'popstate'\n | 'page_load'\n | 'manual_record'\n | 'fallback'\n | 'dummy';\n\n// ---------------------------------------------------------------------------\n// Output Schema Types (JSON report structure)\n// ---------------------------------------------------------------------------\n\nexport interface TestRunReport {\n readonly schemaVersion: string;\n readonly testRunId: string;\n readonly startedAt: string;\n readonly completedAt: string;\n readonly totalDuration: number;\n readonly summary: Summary;\n readonly ci: CIMetadata | null;\n readonly metadata: Record<string, unknown> | null;\n readonly timeline: readonly (TimelineEntry | TimelineStep)[];\n readonly shardRunIds: string[] | null;\n}\n\n/** Breakdown of API calls by HTTP response status range. */\nexport interface ApiCallsByStatusRange {\n readonly '2xx': number;\n readonly '3xx': number;\n readonly '4xx': number;\n readonly '5xx': number;\n readonly error: number;\n}\n\n/** Statistical distribution of API call response times in milliseconds. */\nexport interface ApiResponseTimeStats {\n readonly avg: number;\n readonly min: number;\n readonly max: number;\n readonly p50: number;\n readonly p95: number;\n readonly p99: number;\n}\n\nexport interface Summary {\n readonly total: number;\n readonly passed: number;\n readonly failed: number;\n readonly flaky: number;\n readonly skipped: number;\n readonly timedout: number;\n readonly totalApiCalls: number;\n readonly uniqueApiUrls: number;\n readonly apiCallsByMethod: Record<string, number>;\n readonly apiCallsByStatusRange: ApiCallsByStatusRange;\n readonly apiResponseTime: ApiResponseTimeStats | null;\n readonly totalAssertions: number;\n readonly passedAssertions: number;\n readonly failedAssertions: number;\n readonly totalNavigations: number;\n readonly uniqueNavigationUrls: number;\n readonly totalTimelineSteps: number;\n}\n\nexport interface CIMetadata {\n readonly provider: CIProvider;\n readonly buildId: string | null;\n readonly commitSha: string | null;\n readonly branch: string | null;\n}\n\nexport type CIProvider =\n | 'github-actions'\n | 'gitlab-ci'\n | 'jenkins'\n | 'circleci'\n | 'unknown';\n\nexport interface TimelineEntry {\n readonly url: string;\n readonly navigationType: NavigationType;\n readonly visitedAt: string;\n readonly duration: number;\n readonly specFile: string;\n readonly domContentLoadedAt: string | null;\n readonly networkIdleAt: string | null;\n readonly networkStats: NetworkStats | null;\n readonly tests: TestResult[];\n}\n\nexport interface TestResult {\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 tags: string[];\n readonly failure: FailureDiagnostic | null;\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 apiCalls: readonly ApiCallRecord[] | null;\n readonly apiAssertions: readonly ApiAssertion[] | null;\n}\n\nexport type ResourceType = 'xhr' | 'document' | 'script' | 'stylesheet' | 'image' | 'font' | 'other';\n\nexport interface CapturedNetworkRequest {\n readonly url: string;\n readonly method: string;\n readonly resourceType: ResourceType;\n readonly statusCode: number;\n readonly responseTimeMs: number;\n readonly startedAt: string;\n readonly requestHeaders: Record<string, string> | null;\n readonly requestBody: string | null;\n readonly responseBody: string | null;\n readonly responseHeaders: Record<string, string> | null;\n readonly contentType: string | null;\n readonly responseSize: number;\n readonly requestBodyTruncated: boolean;\n readonly responseBodyTruncated: boolean;\n readonly isBinary: boolean;\n readonly error: string | null;\n}\n\nexport interface TestArtifacts {\n readonly screenshot?: string;\n readonly video?: string;\n}\n\nexport type TestStatus = 'passed' | 'failed' | 'skipped' | 'flaky' | 'timedout';\n\nexport type TestType = 'e2e' | 'api' | 'unit' | 'unknown';\n\nexport interface FailureDiagnostic {\n readonly message: string;\n readonly line: number | null;\n readonly code: string | null;\n readonly stack: string | null;\n}\n\nexport interface NetworkStats {\n readonly totalRequests: number;\n readonly failedRequests: number;\n readonly failedRequestUrls: string[];\n readonly totalBytes: number;\n readonly byType: ResourceBreakdown;\n}\n\nexport interface ResourceBreakdown {\n readonly xhr: number;\n readonly document: number;\n readonly script: number;\n readonly stylesheet: number;\n readonly image: number;\n readonly font: number;\n readonly other: number;\n}\n\n// ---------------------------------------------------------------------------\n// API Call Record (captured from Playwright APIRequestContext proxy)\n// ---------------------------------------------------------------------------\n\nexport interface ApiCallRecord {\n /** Unique call identifier within the test (e.g., \"api-call-0\") */\n readonly id: string;\n /** ISO 8601 timestamp when the call was initiated */\n readonly timestamp: string;\n /** HTTP method: GET, POST, PUT, PATCH, DELETE, HEAD, or custom via fetch() */\n readonly method: string;\n /** Full resolved URL (after baseURL resolution by Playwright) */\n readonly url: string;\n /** All request headers sent, or null if unavailable */\n readonly requestHeaders: Record<string, string> | null;\n /** Request payload as string (JSON-serialized for objects). Null for bodiless methods */\n readonly requestBody: string | null;\n /** HTTP response status code, or null if request errored before response */\n readonly responseStatusCode: number | null;\n /** HTTP response status text, or null if request errored before response */\n readonly responseStatusText: string | null;\n /** All response headers received, or null if request errored before response */\n readonly responseHeaders: Record<string, string> | null;\n /** Response body: string for text/JSON, base64 for binary. Null on error */\n readonly responseBody: string | null;\n /** Duration in milliseconds from request initiation to response received */\n readonly responseTimeMs: number;\n /** Whether the response body is stored as a base64-encoded binary string */\n readonly isBinary: boolean;\n /** Error message if the call failed (network error, timeout), null on success */\n readonly error: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// API Assertion Types (captured from assertion tracking)\n// ---------------------------------------------------------------------------\n\n/** Categorization of the assertion being made. */\nexport type AssertionType =\n | 'status'\n | 'statusOk'\n | 'header'\n | 'bodyField'\n | 'bodyMatch'\n | 'bodyContains'\n | 'custom';\n\n/** Source location where an assertion was made in the test file. */\nexport interface AssertionLocation {\n /** Absolute or relative file path to the test file */\n readonly file: string;\n /** Line number (1-based) */\n readonly line: number;\n /** Column number (1-based), if available */\n readonly column?: number;\n}\n\n/** A single captured assertion on API response data. */\nexport interface ApiAssertion {\n /** Links to ApiCallRecord.id from the API proxy */\n readonly callId: string;\n /** Category of assertion */\n readonly type: AssertionType;\n /** What the test expected */\n readonly expected: unknown;\n /** What was actually received */\n readonly actual: unknown;\n /** Whether the assertion passed or failed */\n readonly status: 'passed' | 'failed';\n /** Source location of the assertion in the test file */\n readonly location: AssertionLocation;\n /** String representation of the assertion expression (best-effort) */\n readonly expression?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Unified Timeline Types (navigation + API call steps)\n// ---------------------------------------------------------------------------\n\n/** Test identity block attached to every timeline step. */\nexport interface StepTestIdentity {\n readonly title: string;\n readonly fullTitle: readonly string[];\n readonly status: TestStatus;\n readonly duration: number;\n readonly retries: number;\n readonly retry: number;\n readonly tags: readonly string[];\n readonly failure: FailureDiagnostic | null;\n}\n\n/** Request details within an API call step. */\nexport interface ApiCallStepRequest {\n readonly headers: Record<string, string> | null;\n readonly body: unknown;\n}\n\n/** Response details within an API call step. */\nexport interface ApiCallStepResponse {\n readonly statusCode: number;\n readonly statusText: string;\n readonly headers: Record<string, string> | null;\n readonly body: unknown;\n}\n\n/** An assertion linked to an API call step (callId omitted — implicit from parent). */\nexport interface StepAssertion {\n readonly type: AssertionType;\n readonly expected: unknown;\n readonly actual: unknown;\n readonly status: 'passed' | 'failed';\n readonly location: AssertionLocation;\n readonly expression?: string;\n}\n\n/** A browser navigation event in the unified timeline. */\nexport interface NavigationStep {\n readonly index: number;\n readonly type: 'navigation';\n readonly url: string;\n readonly timestamp: string;\n readonly durationOnUrl: number;\n readonly navigationType: NavigationType;\n readonly domContentLoadedAt: string | null;\n readonly networkIdleAt: string | null;\n readonly networkStats: NetworkStats | null;\n readonly specFile: string;\n readonly test: StepTestIdentity;\n}\n\n/** An API request event in the unified timeline. */\nexport interface ApiCallStep {\n readonly index: number;\n readonly type: 'api_call';\n readonly callId: string;\n readonly method: string;\n readonly url: string;\n readonly timestamp: string;\n readonly responseTime: number | null;\n readonly request: ApiCallStepRequest;\n readonly response: ApiCallStepResponse | null;\n readonly error?: string | null;\n readonly assertions: readonly StepAssertion[];\n readonly specFile: string;\n readonly test: StepTestIdentity;\n}\n\n/** Discriminated union of all timeline step types. */\nexport type TimelineStep = NavigationStep | ApiCallStep;\n\n// ---------------------------------------------------------------------------\n// Configuration Types (reporter tuple options)\n// ---------------------------------------------------------------------------\n\nexport interface ReporterConfig {\n readonly outputPath?: string;\n readonly includeStackTrace?: boolean;\n readonly includeCodeSnippets?: boolean;\n readonly codeContextLines?: number;\n readonly includeNetworkStats?: boolean;\n readonly navigationTypes?: NavigationType[] | null;\n readonly redactPatterns?: (string | RegExp)[];\n readonly testRunId?: string | null;\n readonly metadata?: Record<string, unknown> | null;\n readonly openReport?: boolean;\n readonly htmlReportPath?: string;\n readonly includeArtifacts?: boolean;\n /** When false, disables API call interception in the unified fixture. Default: true */\n readonly trackApiCalls?: boolean;\n /** When true, suppresses console summary output. Default: false */\n readonly quiet?: boolean;\n /** Capture request headers for API calls. Default: true */\n readonly captureRequestHeaders?: boolean;\n /** Capture response headers for API calls. Default: true */\n readonly captureResponseHeaders?: boolean;\n /** Capture request body for API calls. Default: true */\n readonly captureRequestBody?: boolean;\n /** Capture response body for API calls. Default: true */\n readonly captureResponseBody?: boolean;\n /** Capture API assertions (both pass and fail). Default: true */\n readonly captureAssertions?: boolean;\n /**\n * Header names whose values are replaced with \"[REDACTED]\".\n * Case-insensitive matching. Default: ['authorization', 'cookie', 'set-cookie', 'x-api-key']\n */\n readonly redactHeaders?: string[];\n /**\n * Body field names whose values are replaced with \"[REDACTED]\" at any depth.\n * Default: ['password', 'secret', 'token', 'apiKey', 'api_key']\n */\n readonly redactBodyFields?: string[];\n /**\n * Only track API calls matching these URL patterns.\n * Default: undefined (track all)\n */\n readonly apiIncludeUrls?: (string | RegExp)[];\n /**\n * Exclude API calls matching these URL patterns. Takes precedence over include.\n * Default: undefined (exclude none)\n */\n readonly apiExcludeUrls?: (string | RegExp)[];\n}\n\n// ---------------------------------------------------------------------------\n// Internal Types (fixture → reporter communication)\n// ---------------------------------------------------------------------------\n\nexport interface NavigationAnnotation {\n readonly url: string;\n readonly navigationType: NavigationType;\n readonly timestamp: string;\n readonly domContentLoadedAt?: string;\n readonly networkIdleAt?: string;\n readonly networkStats?: NetworkStats;\n}\n\n// ---------------------------------------------------------------------------\n// Worker-Safe Data Payload (worker → reporter communication)\n// ---------------------------------------------------------------------------\n\n/** Consolidated data payload sent from a worker fixture to the reporter. */\nexport interface TestRelicDataPayload {\n /** Marker to identify TestRelic attachments. Always `true`. */\n readonly testRelicData: true;\n /** Payload format version (semver). */\n readonly version: string;\n /** Navigation events collected during the test. */\n readonly navigations: readonly NavigationAnnotation[];\n /** Captured network requests during the test. */\n readonly networkRequests: readonly CapturedNetworkRequest[];\n /** API calls made via APIRequestContext proxy. */\n readonly apiCalls: readonly ApiCallRecord[];\n /** Assertions captured on API responses. */\n readonly apiAssertions: readonly ApiAssertion[];\n}\n\n/** Current payload format version. */\nexport const PAYLOAD_VERSION = '1.0.0';\n\n/** Attachment name used for the consolidated payload. */\nexport const ATTACHMENT_NAME = 'testrelic-data';\n\n/** Attachment content type. */\nexport const ATTACHMENT_CONTENT_TYPE = 'application/json';\n\n/** Type guard: validates that a parsed object is a TestRelicDataPayload. */\nexport function isTestRelicDataPayload(obj: unknown): obj is TestRelicDataPayload {\n if (typeof obj !== 'object' || obj === null) return false;\n const record = obj as Record<string, unknown>;\n return (\n record.testRelicData === true &&\n typeof record.version === 'string' &&\n record.version.length > 0 &&\n Array.isArray(record.navigations) &&\n Array.isArray(record.networkRequests) &&\n Array.isArray(record.apiCalls) &&\n Array.isArray(record.apiAssertions)\n );\n}\n\n// ---------------------------------------------------------------------------\n// Merge CLI Types\n// ---------------------------------------------------------------------------\n\nexport interface MergeOptions {\n readonly output: string;\n readonly testRunId?: string;\n}\n","/**\n * @testrelic/core — Configurable logger\n *\n * Provides a Logger interface with a no-op default.\n * Never uses console.* directly (Constitution SDK Constraint).\n */\n\nexport interface Logger {\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n debug(message: string, ...args: unknown[]): void;\n}\n\nexport interface LoggerOptions {\n readonly handler?: (level: LogLevel, message: string, args: unknown[]) => void;\n}\n\nexport type LogLevel = 'info' | 'warn' | 'error' | 'debug';\n\ntype LogHandler = (level: LogLevel, message: string, args: unknown[]) => void;\n\nconst noopHandler: LogHandler = () => {};\n\nexport function createLogger(options?: LoggerOptions): Logger {\n const handler = options?.handler ?? noopHandler;\n\n return {\n info(message: string, ...args: unknown[]) {\n handler('info', message, args);\n },\n warn(message: string, ...args: unknown[]) {\n handler('warn', message, args);\n },\n error(message: string, ...args: unknown[]) {\n handler('error', message, args);\n },\n debug(message: string, ...args: unknown[]) {\n handler('debug', message, args);\n },\n };\n}\n","/**\n * @testrelic/core — Structured error factory\n *\n * Machine-readable error codes for programmatic handling.\n */\n\nexport const ErrorCode = {\n CONFIG_INVALID: 'TESTRELIC_CONFIG_INVALID',\n OUTPUT_WRITE_FAILED: 'TESTRELIC_OUTPUT_WRITE_FAILED',\n MERGE_INVALID_SCHEMA: 'TESTRELIC_MERGE_INVALID_SCHEMA',\n MERGE_READ_FAILED: 'TESTRELIC_MERGE_READ_FAILED',\n NAVIGATION_FLUSH_FAILED: 'TESTRELIC_NAVIGATION_FLUSH_FAILED',\n CODE_EXTRACT_FAILED: 'TESTRELIC_CODE_EXTRACT_FAILED',\n HTML_REPORT_FAILED: 'TESTRELIC_HTML_REPORT_FAILED',\n} as const;\n\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\nexport class TestRelicError extends Error {\n readonly code: ErrorCode;\n\n constructor(code: ErrorCode, message: string, cause?: unknown) {\n super(message);\n this.name = 'TestRelicError';\n this.code = code;\n if (cause !== undefined) {\n Object.defineProperty(this, 'cause', { value: cause, writable: false, enumerable: false });\n }\n }\n}\n\nexport function createError(\n code: ErrorCode,\n message: string,\n cause?: unknown,\n): TestRelicError {\n return new TestRelicError(code, message, cause);\n}\n","/**\n * @testrelic/core — Lightweight runtime type guards\n *\n * Hand-written guards, no runtime schema libraries (Constitution SDK Constraint).\n */\n\nimport type {\n ReporterConfig,\n NavigationType,\n TestRunReport,\n} from './types.js';\n\nconst NAVIGATION_TYPES = new Set<string>([\n 'goto', 'navigation', 'back', 'forward', 'refresh',\n 'spa_route', 'spa_replace', 'hash_change', 'link_click',\n 'form_submit', 'redirect', 'popstate', 'page_load',\n 'manual_record', 'fallback', 'dummy',\n]);\n\nconst DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\n\nexport function isValidNavigationType(value: unknown): value is NavigationType {\n return typeof value === 'string' && NAVIGATION_TYPES.has(value);\n}\n\nfunction hasNoPrototypePollution(obj: unknown): boolean {\n if (typeof obj !== 'object' || obj === null) return true;\n for (const key of Object.keys(obj)) {\n if (DANGEROUS_KEYS.has(key)) return false;\n }\n return true;\n}\n\nexport function isValidConfig(input: unknown): input is ReporterConfig {\n if (typeof input !== 'object' || input === null) return false;\n if (!hasNoPrototypePollution(input)) return false;\n\n const obj = input as Record<string, unknown>;\n\n if (obj.outputPath !== undefined && typeof obj.outputPath !== 'string') return false;\n if (obj.includeStackTrace !== undefined && typeof obj.includeStackTrace !== 'boolean') return false;\n if (obj.includeCodeSnippets !== undefined && typeof obj.includeCodeSnippets !== 'boolean') return false;\n if (obj.codeContextLines !== undefined && typeof obj.codeContextLines !== 'number') return false;\n if (obj.includeNetworkStats !== undefined && typeof obj.includeNetworkStats !== 'boolean') return false;\n\n if (obj.navigationTypes !== undefined && obj.navigationTypes !== null) {\n if (!Array.isArray(obj.navigationTypes)) return false;\n for (const t of obj.navigationTypes) {\n if (!isValidNavigationType(t)) return false;\n }\n }\n\n if (obj.redactPatterns !== undefined) {\n if (!Array.isArray(obj.redactPatterns)) return false;\n for (const p of obj.redactPatterns) {\n if (typeof p !== 'string' && !(p instanceof RegExp)) return false;\n }\n }\n\n if (obj.testRunId !== undefined && obj.testRunId !== null && typeof obj.testRunId !== 'string') return false;\n\n if (obj.metadata !== undefined && obj.metadata !== null) {\n if (typeof obj.metadata !== 'object') return false;\n if (!hasNoPrototypePollution(obj.metadata)) return false;\n }\n\n if (obj.openReport !== undefined && typeof obj.openReport !== 'boolean') return false;\n if (obj.htmlReportPath !== undefined && (typeof obj.htmlReportPath !== 'string' || obj.htmlReportPath === '')) return false;\n if (obj.includeArtifacts !== undefined && typeof obj.includeArtifacts !== 'boolean') return false;\n\n // API tracking configuration\n if (obj.trackApiCalls !== undefined && typeof obj.trackApiCalls !== 'boolean') return false;\n if (obj.captureRequestHeaders !== undefined && typeof obj.captureRequestHeaders !== 'boolean') return false;\n if (obj.captureResponseHeaders !== undefined && typeof obj.captureResponseHeaders !== 'boolean') return false;\n if (obj.captureRequestBody !== undefined && typeof obj.captureRequestBody !== 'boolean') return false;\n if (obj.captureResponseBody !== undefined && typeof obj.captureResponseBody !== 'boolean') return false;\n if (obj.captureAssertions !== undefined && typeof obj.captureAssertions !== 'boolean') return false;\n\n if (obj.redactHeaders !== undefined) {\n if (!Array.isArray(obj.redactHeaders)) return false;\n for (const h of obj.redactHeaders) {\n if (typeof h !== 'string') return false;\n }\n }\n\n if (obj.redactBodyFields !== undefined) {\n if (!Array.isArray(obj.redactBodyFields)) return false;\n for (const f of obj.redactBodyFields) {\n if (typeof f !== 'string') return false;\n }\n }\n\n if (obj.apiIncludeUrls !== undefined) {\n if (!Array.isArray(obj.apiIncludeUrls)) return false;\n for (const p of obj.apiIncludeUrls) {\n if (typeof p !== 'string' && !(p instanceof RegExp)) return false;\n }\n }\n\n if (obj.apiExcludeUrls !== undefined) {\n if (!Array.isArray(obj.apiExcludeUrls)) return false;\n for (const p of obj.apiExcludeUrls) {\n if (typeof p !== 'string' && !(p instanceof RegExp)) return false;\n }\n }\n\n return true;\n}\n\nexport function isValidTestRunReport(value: unknown): value is TestRunReport {\n if (typeof value !== 'object' || value === null) return false;\n\n const obj = value as Record<string, unknown>;\n\n if (typeof obj.schemaVersion !== 'string') return false;\n if (typeof obj.testRunId !== 'string') return false;\n if (typeof obj.startedAt !== 'string') return false;\n if (typeof obj.completedAt !== 'string') return false;\n if (typeof obj.totalDuration !== 'number') return false;\n if (typeof obj.summary !== 'object' || obj.summary === null) return false;\n if (!Array.isArray(obj.timeline)) return false;\n\n const summary = obj.summary as Record<string, unknown>;\n if (typeof summary.total !== 'number') return false;\n if (typeof summary.passed !== 'number') return false;\n if (typeof summary.failed !== 'number') return false;\n if (typeof summary.flaky !== 'number') return false;\n if (typeof summary.skipped !== 'number') return false;\n // timedout is optional for backward compat with schema 1.0.0\n if (summary.timedout !== undefined && typeof summary.timedout !== 'number') return false;\n\n return true;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -16,9 +16,26 @@ interface TestRunReport {
|
|
|
16
16
|
readonly summary: Summary;
|
|
17
17
|
readonly ci: CIMetadata | null;
|
|
18
18
|
readonly metadata: Record<string, unknown> | null;
|
|
19
|
-
readonly timeline: TimelineEntry[];
|
|
19
|
+
readonly timeline: readonly (TimelineEntry | TimelineStep)[];
|
|
20
20
|
readonly shardRunIds: string[] | null;
|
|
21
21
|
}
|
|
22
|
+
/** Breakdown of API calls by HTTP response status range. */
|
|
23
|
+
interface ApiCallsByStatusRange {
|
|
24
|
+
readonly '2xx': number;
|
|
25
|
+
readonly '3xx': number;
|
|
26
|
+
readonly '4xx': number;
|
|
27
|
+
readonly '5xx': number;
|
|
28
|
+
readonly error: number;
|
|
29
|
+
}
|
|
30
|
+
/** Statistical distribution of API call response times in milliseconds. */
|
|
31
|
+
interface ApiResponseTimeStats {
|
|
32
|
+
readonly avg: number;
|
|
33
|
+
readonly min: number;
|
|
34
|
+
readonly max: number;
|
|
35
|
+
readonly p50: number;
|
|
36
|
+
readonly p95: number;
|
|
37
|
+
readonly p99: number;
|
|
38
|
+
}
|
|
22
39
|
interface Summary {
|
|
23
40
|
readonly total: number;
|
|
24
41
|
readonly passed: number;
|
|
@@ -26,6 +43,17 @@ interface Summary {
|
|
|
26
43
|
readonly flaky: number;
|
|
27
44
|
readonly skipped: number;
|
|
28
45
|
readonly timedout: number;
|
|
46
|
+
readonly totalApiCalls: number;
|
|
47
|
+
readonly uniqueApiUrls: number;
|
|
48
|
+
readonly apiCallsByMethod: Record<string, number>;
|
|
49
|
+
readonly apiCallsByStatusRange: ApiCallsByStatusRange;
|
|
50
|
+
readonly apiResponseTime: ApiResponseTimeStats | null;
|
|
51
|
+
readonly totalAssertions: number;
|
|
52
|
+
readonly passedAssertions: number;
|
|
53
|
+
readonly failedAssertions: number;
|
|
54
|
+
readonly totalNavigations: number;
|
|
55
|
+
readonly uniqueNavigationUrls: number;
|
|
56
|
+
readonly totalTimelineSteps: number;
|
|
29
57
|
}
|
|
30
58
|
interface CIMetadata {
|
|
31
59
|
readonly provider: CIProvider;
|
|
@@ -64,6 +92,8 @@ interface TestResult {
|
|
|
64
92
|
readonly actualStatus: TestStatus;
|
|
65
93
|
readonly artifacts: TestArtifacts | null;
|
|
66
94
|
readonly networkRequests: CapturedNetworkRequest[] | null;
|
|
95
|
+
readonly apiCalls: readonly ApiCallRecord[] | null;
|
|
96
|
+
readonly apiAssertions: readonly ApiAssertion[] | null;
|
|
67
97
|
}
|
|
68
98
|
type ResourceType = 'xhr' | 'document' | 'script' | 'stylesheet' | 'image' | 'font' | 'other';
|
|
69
99
|
interface CapturedNetworkRequest {
|
|
@@ -112,6 +142,126 @@ interface ResourceBreakdown {
|
|
|
112
142
|
readonly font: number;
|
|
113
143
|
readonly other: number;
|
|
114
144
|
}
|
|
145
|
+
interface ApiCallRecord {
|
|
146
|
+
/** Unique call identifier within the test (e.g., "api-call-0") */
|
|
147
|
+
readonly id: string;
|
|
148
|
+
/** ISO 8601 timestamp when the call was initiated */
|
|
149
|
+
readonly timestamp: string;
|
|
150
|
+
/** HTTP method: GET, POST, PUT, PATCH, DELETE, HEAD, or custom via fetch() */
|
|
151
|
+
readonly method: string;
|
|
152
|
+
/** Full resolved URL (after baseURL resolution by Playwright) */
|
|
153
|
+
readonly url: string;
|
|
154
|
+
/** All request headers sent, or null if unavailable */
|
|
155
|
+
readonly requestHeaders: Record<string, string> | null;
|
|
156
|
+
/** Request payload as string (JSON-serialized for objects). Null for bodiless methods */
|
|
157
|
+
readonly requestBody: string | null;
|
|
158
|
+
/** HTTP response status code, or null if request errored before response */
|
|
159
|
+
readonly responseStatusCode: number | null;
|
|
160
|
+
/** HTTP response status text, or null if request errored before response */
|
|
161
|
+
readonly responseStatusText: string | null;
|
|
162
|
+
/** All response headers received, or null if request errored before response */
|
|
163
|
+
readonly responseHeaders: Record<string, string> | null;
|
|
164
|
+
/** Response body: string for text/JSON, base64 for binary. Null on error */
|
|
165
|
+
readonly responseBody: string | null;
|
|
166
|
+
/** Duration in milliseconds from request initiation to response received */
|
|
167
|
+
readonly responseTimeMs: number;
|
|
168
|
+
/** Whether the response body is stored as a base64-encoded binary string */
|
|
169
|
+
readonly isBinary: boolean;
|
|
170
|
+
/** Error message if the call failed (network error, timeout), null on success */
|
|
171
|
+
readonly error: string | null;
|
|
172
|
+
}
|
|
173
|
+
/** Categorization of the assertion being made. */
|
|
174
|
+
type AssertionType = 'status' | 'statusOk' | 'header' | 'bodyField' | 'bodyMatch' | 'bodyContains' | 'custom';
|
|
175
|
+
/** Source location where an assertion was made in the test file. */
|
|
176
|
+
interface AssertionLocation {
|
|
177
|
+
/** Absolute or relative file path to the test file */
|
|
178
|
+
readonly file: string;
|
|
179
|
+
/** Line number (1-based) */
|
|
180
|
+
readonly line: number;
|
|
181
|
+
/** Column number (1-based), if available */
|
|
182
|
+
readonly column?: number;
|
|
183
|
+
}
|
|
184
|
+
/** A single captured assertion on API response data. */
|
|
185
|
+
interface ApiAssertion {
|
|
186
|
+
/** Links to ApiCallRecord.id from the API proxy */
|
|
187
|
+
readonly callId: string;
|
|
188
|
+
/** Category of assertion */
|
|
189
|
+
readonly type: AssertionType;
|
|
190
|
+
/** What the test expected */
|
|
191
|
+
readonly expected: unknown;
|
|
192
|
+
/** What was actually received */
|
|
193
|
+
readonly actual: unknown;
|
|
194
|
+
/** Whether the assertion passed or failed */
|
|
195
|
+
readonly status: 'passed' | 'failed';
|
|
196
|
+
/** Source location of the assertion in the test file */
|
|
197
|
+
readonly location: AssertionLocation;
|
|
198
|
+
/** String representation of the assertion expression (best-effort) */
|
|
199
|
+
readonly expression?: string;
|
|
200
|
+
}
|
|
201
|
+
/** Test identity block attached to every timeline step. */
|
|
202
|
+
interface StepTestIdentity {
|
|
203
|
+
readonly title: string;
|
|
204
|
+
readonly fullTitle: readonly string[];
|
|
205
|
+
readonly status: TestStatus;
|
|
206
|
+
readonly duration: number;
|
|
207
|
+
readonly retries: number;
|
|
208
|
+
readonly retry: number;
|
|
209
|
+
readonly tags: readonly string[];
|
|
210
|
+
readonly failure: FailureDiagnostic | null;
|
|
211
|
+
}
|
|
212
|
+
/** Request details within an API call step. */
|
|
213
|
+
interface ApiCallStepRequest {
|
|
214
|
+
readonly headers: Record<string, string> | null;
|
|
215
|
+
readonly body: unknown;
|
|
216
|
+
}
|
|
217
|
+
/** Response details within an API call step. */
|
|
218
|
+
interface ApiCallStepResponse {
|
|
219
|
+
readonly statusCode: number;
|
|
220
|
+
readonly statusText: string;
|
|
221
|
+
readonly headers: Record<string, string> | null;
|
|
222
|
+
readonly body: unknown;
|
|
223
|
+
}
|
|
224
|
+
/** An assertion linked to an API call step (callId omitted — implicit from parent). */
|
|
225
|
+
interface StepAssertion {
|
|
226
|
+
readonly type: AssertionType;
|
|
227
|
+
readonly expected: unknown;
|
|
228
|
+
readonly actual: unknown;
|
|
229
|
+
readonly status: 'passed' | 'failed';
|
|
230
|
+
readonly location: AssertionLocation;
|
|
231
|
+
readonly expression?: string;
|
|
232
|
+
}
|
|
233
|
+
/** A browser navigation event in the unified timeline. */
|
|
234
|
+
interface NavigationStep {
|
|
235
|
+
readonly index: number;
|
|
236
|
+
readonly type: 'navigation';
|
|
237
|
+
readonly url: string;
|
|
238
|
+
readonly timestamp: string;
|
|
239
|
+
readonly durationOnUrl: number;
|
|
240
|
+
readonly navigationType: NavigationType;
|
|
241
|
+
readonly domContentLoadedAt: string | null;
|
|
242
|
+
readonly networkIdleAt: string | null;
|
|
243
|
+
readonly networkStats: NetworkStats | null;
|
|
244
|
+
readonly specFile: string;
|
|
245
|
+
readonly test: StepTestIdentity;
|
|
246
|
+
}
|
|
247
|
+
/** An API request event in the unified timeline. */
|
|
248
|
+
interface ApiCallStep {
|
|
249
|
+
readonly index: number;
|
|
250
|
+
readonly type: 'api_call';
|
|
251
|
+
readonly callId: string;
|
|
252
|
+
readonly method: string;
|
|
253
|
+
readonly url: string;
|
|
254
|
+
readonly timestamp: string;
|
|
255
|
+
readonly responseTime: number | null;
|
|
256
|
+
readonly request: ApiCallStepRequest;
|
|
257
|
+
readonly response: ApiCallStepResponse | null;
|
|
258
|
+
readonly error?: string | null;
|
|
259
|
+
readonly assertions: readonly StepAssertion[];
|
|
260
|
+
readonly specFile: string;
|
|
261
|
+
readonly test: StepTestIdentity;
|
|
262
|
+
}
|
|
263
|
+
/** Discriminated union of all timeline step types. */
|
|
264
|
+
type TimelineStep = NavigationStep | ApiCallStep;
|
|
115
265
|
interface ReporterConfig {
|
|
116
266
|
readonly outputPath?: string;
|
|
117
267
|
readonly includeStackTrace?: boolean;
|
|
@@ -125,6 +275,40 @@ interface ReporterConfig {
|
|
|
125
275
|
readonly openReport?: boolean;
|
|
126
276
|
readonly htmlReportPath?: string;
|
|
127
277
|
readonly includeArtifacts?: boolean;
|
|
278
|
+
/** When false, disables API call interception in the unified fixture. Default: true */
|
|
279
|
+
readonly trackApiCalls?: boolean;
|
|
280
|
+
/** When true, suppresses console summary output. Default: false */
|
|
281
|
+
readonly quiet?: boolean;
|
|
282
|
+
/** Capture request headers for API calls. Default: true */
|
|
283
|
+
readonly captureRequestHeaders?: boolean;
|
|
284
|
+
/** Capture response headers for API calls. Default: true */
|
|
285
|
+
readonly captureResponseHeaders?: boolean;
|
|
286
|
+
/** Capture request body for API calls. Default: true */
|
|
287
|
+
readonly captureRequestBody?: boolean;
|
|
288
|
+
/** Capture response body for API calls. Default: true */
|
|
289
|
+
readonly captureResponseBody?: boolean;
|
|
290
|
+
/** Capture API assertions (both pass and fail). Default: true */
|
|
291
|
+
readonly captureAssertions?: boolean;
|
|
292
|
+
/**
|
|
293
|
+
* Header names whose values are replaced with "[REDACTED]".
|
|
294
|
+
* Case-insensitive matching. Default: ['authorization', 'cookie', 'set-cookie', 'x-api-key']
|
|
295
|
+
*/
|
|
296
|
+
readonly redactHeaders?: string[];
|
|
297
|
+
/**
|
|
298
|
+
* Body field names whose values are replaced with "[REDACTED]" at any depth.
|
|
299
|
+
* Default: ['password', 'secret', 'token', 'apiKey', 'api_key']
|
|
300
|
+
*/
|
|
301
|
+
readonly redactBodyFields?: string[];
|
|
302
|
+
/**
|
|
303
|
+
* Only track API calls matching these URL patterns.
|
|
304
|
+
* Default: undefined (track all)
|
|
305
|
+
*/
|
|
306
|
+
readonly apiIncludeUrls?: (string | RegExp)[];
|
|
307
|
+
/**
|
|
308
|
+
* Exclude API calls matching these URL patterns. Takes precedence over include.
|
|
309
|
+
* Default: undefined (exclude none)
|
|
310
|
+
*/
|
|
311
|
+
readonly apiExcludeUrls?: (string | RegExp)[];
|
|
128
312
|
}
|
|
129
313
|
interface NavigationAnnotation {
|
|
130
314
|
readonly url: string;
|
|
@@ -134,6 +318,29 @@ interface NavigationAnnotation {
|
|
|
134
318
|
readonly networkIdleAt?: string;
|
|
135
319
|
readonly networkStats?: NetworkStats;
|
|
136
320
|
}
|
|
321
|
+
/** Consolidated data payload sent from a worker fixture to the reporter. */
|
|
322
|
+
interface TestRelicDataPayload {
|
|
323
|
+
/** Marker to identify TestRelic attachments. Always `true`. */
|
|
324
|
+
readonly testRelicData: true;
|
|
325
|
+
/** Payload format version (semver). */
|
|
326
|
+
readonly version: string;
|
|
327
|
+
/** Navigation events collected during the test. */
|
|
328
|
+
readonly navigations: readonly NavigationAnnotation[];
|
|
329
|
+
/** Captured network requests during the test. */
|
|
330
|
+
readonly networkRequests: readonly CapturedNetworkRequest[];
|
|
331
|
+
/** API calls made via APIRequestContext proxy. */
|
|
332
|
+
readonly apiCalls: readonly ApiCallRecord[];
|
|
333
|
+
/** Assertions captured on API responses. */
|
|
334
|
+
readonly apiAssertions: readonly ApiAssertion[];
|
|
335
|
+
}
|
|
336
|
+
/** Current payload format version. */
|
|
337
|
+
declare const PAYLOAD_VERSION = "1.0.0";
|
|
338
|
+
/** Attachment name used for the consolidated payload. */
|
|
339
|
+
declare const ATTACHMENT_NAME = "testrelic-data";
|
|
340
|
+
/** Attachment content type. */
|
|
341
|
+
declare const ATTACHMENT_CONTENT_TYPE = "application/json";
|
|
342
|
+
/** Type guard: validates that a parsed object is a TestRelicDataPayload. */
|
|
343
|
+
declare function isTestRelicDataPayload(obj: unknown): obj is TestRelicDataPayload;
|
|
137
344
|
interface MergeOptions {
|
|
138
345
|
readonly output: string;
|
|
139
346
|
readonly testRunId?: string;
|
|
@@ -188,4 +395,4 @@ declare function isValidNavigationType(value: unknown): value is NavigationType;
|
|
|
188
395
|
declare function isValidConfig(input: unknown): input is ReporterConfig;
|
|
189
396
|
declare function isValidTestRunReport(value: unknown): value is TestRunReport;
|
|
190
397
|
|
|
191
|
-
export { type CIMetadata, type CIProvider, type CapturedNetworkRequest, ErrorCode, type FailureDiagnostic, type LogLevel, type Logger, type LoggerOptions, type MergeOptions, type NavigationAnnotation, type NavigationType, type NetworkStats, type ReporterConfig, type ResourceBreakdown, type ResourceType, type Summary, type TestArtifacts, TestRelicError, type TestResult, type TestRunReport, type TestStatus, type TestType, type TimelineEntry, createError, createLogger, isValidConfig, isValidNavigationType, isValidTestRunReport };
|
|
398
|
+
export { ATTACHMENT_CONTENT_TYPE, ATTACHMENT_NAME, type ApiAssertion, type ApiCallRecord, type ApiCallStep, type ApiCallStepRequest, type ApiCallStepResponse, type AssertionLocation, type AssertionType, type CIMetadata, type CIProvider, type CapturedNetworkRequest, ErrorCode, type FailureDiagnostic, type LogLevel, type Logger, type LoggerOptions, type MergeOptions, type NavigationAnnotation, type NavigationStep, type NavigationType, type NetworkStats, PAYLOAD_VERSION, type ReporterConfig, type ResourceBreakdown, type ResourceType, type StepAssertion, type StepTestIdentity, type Summary, type TestArtifacts, type TestRelicDataPayload, TestRelicError, type TestResult, type TestRunReport, type TestStatus, type TestType, type TimelineEntry, type TimelineStep, createError, createLogger, isTestRelicDataPayload, isValidConfig, isValidNavigationType, isValidTestRunReport };
|
package/dist/index.d.ts
CHANGED
|
@@ -16,9 +16,26 @@ interface TestRunReport {
|
|
|
16
16
|
readonly summary: Summary;
|
|
17
17
|
readonly ci: CIMetadata | null;
|
|
18
18
|
readonly metadata: Record<string, unknown> | null;
|
|
19
|
-
readonly timeline: TimelineEntry[];
|
|
19
|
+
readonly timeline: readonly (TimelineEntry | TimelineStep)[];
|
|
20
20
|
readonly shardRunIds: string[] | null;
|
|
21
21
|
}
|
|
22
|
+
/** Breakdown of API calls by HTTP response status range. */
|
|
23
|
+
interface ApiCallsByStatusRange {
|
|
24
|
+
readonly '2xx': number;
|
|
25
|
+
readonly '3xx': number;
|
|
26
|
+
readonly '4xx': number;
|
|
27
|
+
readonly '5xx': number;
|
|
28
|
+
readonly error: number;
|
|
29
|
+
}
|
|
30
|
+
/** Statistical distribution of API call response times in milliseconds. */
|
|
31
|
+
interface ApiResponseTimeStats {
|
|
32
|
+
readonly avg: number;
|
|
33
|
+
readonly min: number;
|
|
34
|
+
readonly max: number;
|
|
35
|
+
readonly p50: number;
|
|
36
|
+
readonly p95: number;
|
|
37
|
+
readonly p99: number;
|
|
38
|
+
}
|
|
22
39
|
interface Summary {
|
|
23
40
|
readonly total: number;
|
|
24
41
|
readonly passed: number;
|
|
@@ -26,6 +43,17 @@ interface Summary {
|
|
|
26
43
|
readonly flaky: number;
|
|
27
44
|
readonly skipped: number;
|
|
28
45
|
readonly timedout: number;
|
|
46
|
+
readonly totalApiCalls: number;
|
|
47
|
+
readonly uniqueApiUrls: number;
|
|
48
|
+
readonly apiCallsByMethod: Record<string, number>;
|
|
49
|
+
readonly apiCallsByStatusRange: ApiCallsByStatusRange;
|
|
50
|
+
readonly apiResponseTime: ApiResponseTimeStats | null;
|
|
51
|
+
readonly totalAssertions: number;
|
|
52
|
+
readonly passedAssertions: number;
|
|
53
|
+
readonly failedAssertions: number;
|
|
54
|
+
readonly totalNavigations: number;
|
|
55
|
+
readonly uniqueNavigationUrls: number;
|
|
56
|
+
readonly totalTimelineSteps: number;
|
|
29
57
|
}
|
|
30
58
|
interface CIMetadata {
|
|
31
59
|
readonly provider: CIProvider;
|
|
@@ -64,6 +92,8 @@ interface TestResult {
|
|
|
64
92
|
readonly actualStatus: TestStatus;
|
|
65
93
|
readonly artifacts: TestArtifacts | null;
|
|
66
94
|
readonly networkRequests: CapturedNetworkRequest[] | null;
|
|
95
|
+
readonly apiCalls: readonly ApiCallRecord[] | null;
|
|
96
|
+
readonly apiAssertions: readonly ApiAssertion[] | null;
|
|
67
97
|
}
|
|
68
98
|
type ResourceType = 'xhr' | 'document' | 'script' | 'stylesheet' | 'image' | 'font' | 'other';
|
|
69
99
|
interface CapturedNetworkRequest {
|
|
@@ -112,6 +142,126 @@ interface ResourceBreakdown {
|
|
|
112
142
|
readonly font: number;
|
|
113
143
|
readonly other: number;
|
|
114
144
|
}
|
|
145
|
+
interface ApiCallRecord {
|
|
146
|
+
/** Unique call identifier within the test (e.g., "api-call-0") */
|
|
147
|
+
readonly id: string;
|
|
148
|
+
/** ISO 8601 timestamp when the call was initiated */
|
|
149
|
+
readonly timestamp: string;
|
|
150
|
+
/** HTTP method: GET, POST, PUT, PATCH, DELETE, HEAD, or custom via fetch() */
|
|
151
|
+
readonly method: string;
|
|
152
|
+
/** Full resolved URL (after baseURL resolution by Playwright) */
|
|
153
|
+
readonly url: string;
|
|
154
|
+
/** All request headers sent, or null if unavailable */
|
|
155
|
+
readonly requestHeaders: Record<string, string> | null;
|
|
156
|
+
/** Request payload as string (JSON-serialized for objects). Null for bodiless methods */
|
|
157
|
+
readonly requestBody: string | null;
|
|
158
|
+
/** HTTP response status code, or null if request errored before response */
|
|
159
|
+
readonly responseStatusCode: number | null;
|
|
160
|
+
/** HTTP response status text, or null if request errored before response */
|
|
161
|
+
readonly responseStatusText: string | null;
|
|
162
|
+
/** All response headers received, or null if request errored before response */
|
|
163
|
+
readonly responseHeaders: Record<string, string> | null;
|
|
164
|
+
/** Response body: string for text/JSON, base64 for binary. Null on error */
|
|
165
|
+
readonly responseBody: string | null;
|
|
166
|
+
/** Duration in milliseconds from request initiation to response received */
|
|
167
|
+
readonly responseTimeMs: number;
|
|
168
|
+
/** Whether the response body is stored as a base64-encoded binary string */
|
|
169
|
+
readonly isBinary: boolean;
|
|
170
|
+
/** Error message if the call failed (network error, timeout), null on success */
|
|
171
|
+
readonly error: string | null;
|
|
172
|
+
}
|
|
173
|
+
/** Categorization of the assertion being made. */
|
|
174
|
+
type AssertionType = 'status' | 'statusOk' | 'header' | 'bodyField' | 'bodyMatch' | 'bodyContains' | 'custom';
|
|
175
|
+
/** Source location where an assertion was made in the test file. */
|
|
176
|
+
interface AssertionLocation {
|
|
177
|
+
/** Absolute or relative file path to the test file */
|
|
178
|
+
readonly file: string;
|
|
179
|
+
/** Line number (1-based) */
|
|
180
|
+
readonly line: number;
|
|
181
|
+
/** Column number (1-based), if available */
|
|
182
|
+
readonly column?: number;
|
|
183
|
+
}
|
|
184
|
+
/** A single captured assertion on API response data. */
|
|
185
|
+
interface ApiAssertion {
|
|
186
|
+
/** Links to ApiCallRecord.id from the API proxy */
|
|
187
|
+
readonly callId: string;
|
|
188
|
+
/** Category of assertion */
|
|
189
|
+
readonly type: AssertionType;
|
|
190
|
+
/** What the test expected */
|
|
191
|
+
readonly expected: unknown;
|
|
192
|
+
/** What was actually received */
|
|
193
|
+
readonly actual: unknown;
|
|
194
|
+
/** Whether the assertion passed or failed */
|
|
195
|
+
readonly status: 'passed' | 'failed';
|
|
196
|
+
/** Source location of the assertion in the test file */
|
|
197
|
+
readonly location: AssertionLocation;
|
|
198
|
+
/** String representation of the assertion expression (best-effort) */
|
|
199
|
+
readonly expression?: string;
|
|
200
|
+
}
|
|
201
|
+
/** Test identity block attached to every timeline step. */
|
|
202
|
+
interface StepTestIdentity {
|
|
203
|
+
readonly title: string;
|
|
204
|
+
readonly fullTitle: readonly string[];
|
|
205
|
+
readonly status: TestStatus;
|
|
206
|
+
readonly duration: number;
|
|
207
|
+
readonly retries: number;
|
|
208
|
+
readonly retry: number;
|
|
209
|
+
readonly tags: readonly string[];
|
|
210
|
+
readonly failure: FailureDiagnostic | null;
|
|
211
|
+
}
|
|
212
|
+
/** Request details within an API call step. */
|
|
213
|
+
interface ApiCallStepRequest {
|
|
214
|
+
readonly headers: Record<string, string> | null;
|
|
215
|
+
readonly body: unknown;
|
|
216
|
+
}
|
|
217
|
+
/** Response details within an API call step. */
|
|
218
|
+
interface ApiCallStepResponse {
|
|
219
|
+
readonly statusCode: number;
|
|
220
|
+
readonly statusText: string;
|
|
221
|
+
readonly headers: Record<string, string> | null;
|
|
222
|
+
readonly body: unknown;
|
|
223
|
+
}
|
|
224
|
+
/** An assertion linked to an API call step (callId omitted — implicit from parent). */
|
|
225
|
+
interface StepAssertion {
|
|
226
|
+
readonly type: AssertionType;
|
|
227
|
+
readonly expected: unknown;
|
|
228
|
+
readonly actual: unknown;
|
|
229
|
+
readonly status: 'passed' | 'failed';
|
|
230
|
+
readonly location: AssertionLocation;
|
|
231
|
+
readonly expression?: string;
|
|
232
|
+
}
|
|
233
|
+
/** A browser navigation event in the unified timeline. */
|
|
234
|
+
interface NavigationStep {
|
|
235
|
+
readonly index: number;
|
|
236
|
+
readonly type: 'navigation';
|
|
237
|
+
readonly url: string;
|
|
238
|
+
readonly timestamp: string;
|
|
239
|
+
readonly durationOnUrl: number;
|
|
240
|
+
readonly navigationType: NavigationType;
|
|
241
|
+
readonly domContentLoadedAt: string | null;
|
|
242
|
+
readonly networkIdleAt: string | null;
|
|
243
|
+
readonly networkStats: NetworkStats | null;
|
|
244
|
+
readonly specFile: string;
|
|
245
|
+
readonly test: StepTestIdentity;
|
|
246
|
+
}
|
|
247
|
+
/** An API request event in the unified timeline. */
|
|
248
|
+
interface ApiCallStep {
|
|
249
|
+
readonly index: number;
|
|
250
|
+
readonly type: 'api_call';
|
|
251
|
+
readonly callId: string;
|
|
252
|
+
readonly method: string;
|
|
253
|
+
readonly url: string;
|
|
254
|
+
readonly timestamp: string;
|
|
255
|
+
readonly responseTime: number | null;
|
|
256
|
+
readonly request: ApiCallStepRequest;
|
|
257
|
+
readonly response: ApiCallStepResponse | null;
|
|
258
|
+
readonly error?: string | null;
|
|
259
|
+
readonly assertions: readonly StepAssertion[];
|
|
260
|
+
readonly specFile: string;
|
|
261
|
+
readonly test: StepTestIdentity;
|
|
262
|
+
}
|
|
263
|
+
/** Discriminated union of all timeline step types. */
|
|
264
|
+
type TimelineStep = NavigationStep | ApiCallStep;
|
|
115
265
|
interface ReporterConfig {
|
|
116
266
|
readonly outputPath?: string;
|
|
117
267
|
readonly includeStackTrace?: boolean;
|
|
@@ -125,6 +275,40 @@ interface ReporterConfig {
|
|
|
125
275
|
readonly openReport?: boolean;
|
|
126
276
|
readonly htmlReportPath?: string;
|
|
127
277
|
readonly includeArtifacts?: boolean;
|
|
278
|
+
/** When false, disables API call interception in the unified fixture. Default: true */
|
|
279
|
+
readonly trackApiCalls?: boolean;
|
|
280
|
+
/** When true, suppresses console summary output. Default: false */
|
|
281
|
+
readonly quiet?: boolean;
|
|
282
|
+
/** Capture request headers for API calls. Default: true */
|
|
283
|
+
readonly captureRequestHeaders?: boolean;
|
|
284
|
+
/** Capture response headers for API calls. Default: true */
|
|
285
|
+
readonly captureResponseHeaders?: boolean;
|
|
286
|
+
/** Capture request body for API calls. Default: true */
|
|
287
|
+
readonly captureRequestBody?: boolean;
|
|
288
|
+
/** Capture response body for API calls. Default: true */
|
|
289
|
+
readonly captureResponseBody?: boolean;
|
|
290
|
+
/** Capture API assertions (both pass and fail). Default: true */
|
|
291
|
+
readonly captureAssertions?: boolean;
|
|
292
|
+
/**
|
|
293
|
+
* Header names whose values are replaced with "[REDACTED]".
|
|
294
|
+
* Case-insensitive matching. Default: ['authorization', 'cookie', 'set-cookie', 'x-api-key']
|
|
295
|
+
*/
|
|
296
|
+
readonly redactHeaders?: string[];
|
|
297
|
+
/**
|
|
298
|
+
* Body field names whose values are replaced with "[REDACTED]" at any depth.
|
|
299
|
+
* Default: ['password', 'secret', 'token', 'apiKey', 'api_key']
|
|
300
|
+
*/
|
|
301
|
+
readonly redactBodyFields?: string[];
|
|
302
|
+
/**
|
|
303
|
+
* Only track API calls matching these URL patterns.
|
|
304
|
+
* Default: undefined (track all)
|
|
305
|
+
*/
|
|
306
|
+
readonly apiIncludeUrls?: (string | RegExp)[];
|
|
307
|
+
/**
|
|
308
|
+
* Exclude API calls matching these URL patterns. Takes precedence over include.
|
|
309
|
+
* Default: undefined (exclude none)
|
|
310
|
+
*/
|
|
311
|
+
readonly apiExcludeUrls?: (string | RegExp)[];
|
|
128
312
|
}
|
|
129
313
|
interface NavigationAnnotation {
|
|
130
314
|
readonly url: string;
|
|
@@ -134,6 +318,29 @@ interface NavigationAnnotation {
|
|
|
134
318
|
readonly networkIdleAt?: string;
|
|
135
319
|
readonly networkStats?: NetworkStats;
|
|
136
320
|
}
|
|
321
|
+
/** Consolidated data payload sent from a worker fixture to the reporter. */
|
|
322
|
+
interface TestRelicDataPayload {
|
|
323
|
+
/** Marker to identify TestRelic attachments. Always `true`. */
|
|
324
|
+
readonly testRelicData: true;
|
|
325
|
+
/** Payload format version (semver). */
|
|
326
|
+
readonly version: string;
|
|
327
|
+
/** Navigation events collected during the test. */
|
|
328
|
+
readonly navigations: readonly NavigationAnnotation[];
|
|
329
|
+
/** Captured network requests during the test. */
|
|
330
|
+
readonly networkRequests: readonly CapturedNetworkRequest[];
|
|
331
|
+
/** API calls made via APIRequestContext proxy. */
|
|
332
|
+
readonly apiCalls: readonly ApiCallRecord[];
|
|
333
|
+
/** Assertions captured on API responses. */
|
|
334
|
+
readonly apiAssertions: readonly ApiAssertion[];
|
|
335
|
+
}
|
|
336
|
+
/** Current payload format version. */
|
|
337
|
+
declare const PAYLOAD_VERSION = "1.0.0";
|
|
338
|
+
/** Attachment name used for the consolidated payload. */
|
|
339
|
+
declare const ATTACHMENT_NAME = "testrelic-data";
|
|
340
|
+
/** Attachment content type. */
|
|
341
|
+
declare const ATTACHMENT_CONTENT_TYPE = "application/json";
|
|
342
|
+
/** Type guard: validates that a parsed object is a TestRelicDataPayload. */
|
|
343
|
+
declare function isTestRelicDataPayload(obj: unknown): obj is TestRelicDataPayload;
|
|
137
344
|
interface MergeOptions {
|
|
138
345
|
readonly output: string;
|
|
139
346
|
readonly testRunId?: string;
|
|
@@ -188,4 +395,4 @@ declare function isValidNavigationType(value: unknown): value is NavigationType;
|
|
|
188
395
|
declare function isValidConfig(input: unknown): input is ReporterConfig;
|
|
189
396
|
declare function isValidTestRunReport(value: unknown): value is TestRunReport;
|
|
190
397
|
|
|
191
|
-
export { type CIMetadata, type CIProvider, type CapturedNetworkRequest, ErrorCode, type FailureDiagnostic, type LogLevel, type Logger, type LoggerOptions, type MergeOptions, type NavigationAnnotation, type NavigationType, type NetworkStats, type ReporterConfig, type ResourceBreakdown, type ResourceType, type Summary, type TestArtifacts, TestRelicError, type TestResult, type TestRunReport, type TestStatus, type TestType, type TimelineEntry, createError, createLogger, isValidConfig, isValidNavigationType, isValidTestRunReport };
|
|
398
|
+
export { ATTACHMENT_CONTENT_TYPE, ATTACHMENT_NAME, type ApiAssertion, type ApiCallRecord, type ApiCallStep, type ApiCallStepRequest, type ApiCallStepResponse, type AssertionLocation, type AssertionType, type CIMetadata, type CIProvider, type CapturedNetworkRequest, ErrorCode, type FailureDiagnostic, type LogLevel, type Logger, type LoggerOptions, type MergeOptions, type NavigationAnnotation, type NavigationStep, type NavigationType, type NetworkStats, PAYLOAD_VERSION, type ReporterConfig, type ResourceBreakdown, type ResourceType, type StepAssertion, type StepTestIdentity, type Summary, type TestArtifacts, type TestRelicDataPayload, TestRelicError, type TestResult, type TestRunReport, type TestStatus, type TestType, type TimelineEntry, type TimelineStep, createError, createLogger, isTestRelicDataPayload, isValidConfig, isValidNavigationType, isValidTestRunReport };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var PAYLOAD_VERSION = "1.0.0";
|
|
3
|
+
var ATTACHMENT_NAME = "testrelic-data";
|
|
4
|
+
var ATTACHMENT_CONTENT_TYPE = "application/json";
|
|
5
|
+
function isTestRelicDataPayload(obj) {
|
|
6
|
+
if (typeof obj !== "object" || obj === null) return false;
|
|
7
|
+
const record = obj;
|
|
8
|
+
return record.testRelicData === true && typeof record.version === "string" && record.version.length > 0 && Array.isArray(record.navigations) && Array.isArray(record.networkRequests) && Array.isArray(record.apiCalls) && Array.isArray(record.apiAssertions);
|
|
9
|
+
}
|
|
10
|
+
|
|
1
11
|
// src/logger.ts
|
|
2
12
|
var noopHandler = () => {
|
|
3
13
|
};
|
|
@@ -102,6 +112,36 @@ function isValidConfig(input) {
|
|
|
102
112
|
if (obj.openReport !== void 0 && typeof obj.openReport !== "boolean") return false;
|
|
103
113
|
if (obj.htmlReportPath !== void 0 && (typeof obj.htmlReportPath !== "string" || obj.htmlReportPath === "")) return false;
|
|
104
114
|
if (obj.includeArtifacts !== void 0 && typeof obj.includeArtifacts !== "boolean") return false;
|
|
115
|
+
if (obj.trackApiCalls !== void 0 && typeof obj.trackApiCalls !== "boolean") return false;
|
|
116
|
+
if (obj.captureRequestHeaders !== void 0 && typeof obj.captureRequestHeaders !== "boolean") return false;
|
|
117
|
+
if (obj.captureResponseHeaders !== void 0 && typeof obj.captureResponseHeaders !== "boolean") return false;
|
|
118
|
+
if (obj.captureRequestBody !== void 0 && typeof obj.captureRequestBody !== "boolean") return false;
|
|
119
|
+
if (obj.captureResponseBody !== void 0 && typeof obj.captureResponseBody !== "boolean") return false;
|
|
120
|
+
if (obj.captureAssertions !== void 0 && typeof obj.captureAssertions !== "boolean") return false;
|
|
121
|
+
if (obj.redactHeaders !== void 0) {
|
|
122
|
+
if (!Array.isArray(obj.redactHeaders)) return false;
|
|
123
|
+
for (const h of obj.redactHeaders) {
|
|
124
|
+
if (typeof h !== "string") return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (obj.redactBodyFields !== void 0) {
|
|
128
|
+
if (!Array.isArray(obj.redactBodyFields)) return false;
|
|
129
|
+
for (const f of obj.redactBodyFields) {
|
|
130
|
+
if (typeof f !== "string") return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (obj.apiIncludeUrls !== void 0) {
|
|
134
|
+
if (!Array.isArray(obj.apiIncludeUrls)) return false;
|
|
135
|
+
for (const p of obj.apiIncludeUrls) {
|
|
136
|
+
if (typeof p !== "string" && !(p instanceof RegExp)) return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (obj.apiExcludeUrls !== void 0) {
|
|
140
|
+
if (!Array.isArray(obj.apiExcludeUrls)) return false;
|
|
141
|
+
for (const p of obj.apiExcludeUrls) {
|
|
142
|
+
if (typeof p !== "string" && !(p instanceof RegExp)) return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
105
145
|
return true;
|
|
106
146
|
}
|
|
107
147
|
function isValidTestRunReport(value) {
|
|
@@ -124,10 +164,14 @@ function isValidTestRunReport(value) {
|
|
|
124
164
|
return true;
|
|
125
165
|
}
|
|
126
166
|
export {
|
|
167
|
+
ATTACHMENT_CONTENT_TYPE,
|
|
168
|
+
ATTACHMENT_NAME,
|
|
127
169
|
ErrorCode,
|
|
170
|
+
PAYLOAD_VERSION,
|
|
128
171
|
TestRelicError,
|
|
129
172
|
createError,
|
|
130
173
|
createLogger,
|
|
174
|
+
isTestRelicDataPayload,
|
|
131
175
|
isValidConfig,
|
|
132
176
|
isValidNavigationType,
|
|
133
177
|
isValidTestRunReport
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/logger.ts","../src/errors.ts","../src/validation.ts"],"sourcesContent":["/**\n * @testrelic/core — Configurable logger\n *\n * Provides a Logger interface with a no-op default.\n * Never uses console.* directly (Constitution SDK Constraint).\n */\n\nexport interface Logger {\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n debug(message: string, ...args: unknown[]): void;\n}\n\nexport interface LoggerOptions {\n readonly handler?: (level: LogLevel, message: string, args: unknown[]) => void;\n}\n\nexport type LogLevel = 'info' | 'warn' | 'error' | 'debug';\n\ntype LogHandler = (level: LogLevel, message: string, args: unknown[]) => void;\n\nconst noopHandler: LogHandler = () => {};\n\nexport function createLogger(options?: LoggerOptions): Logger {\n const handler = options?.handler ?? noopHandler;\n\n return {\n info(message: string, ...args: unknown[]) {\n handler('info', message, args);\n },\n warn(message: string, ...args: unknown[]) {\n handler('warn', message, args);\n },\n error(message: string, ...args: unknown[]) {\n handler('error', message, args);\n },\n debug(message: string, ...args: unknown[]) {\n handler('debug', message, args);\n },\n };\n}\n","/**\n * @testrelic/core — Structured error factory\n *\n * Machine-readable error codes for programmatic handling.\n */\n\nexport const ErrorCode = {\n CONFIG_INVALID: 'TESTRELIC_CONFIG_INVALID',\n OUTPUT_WRITE_FAILED: 'TESTRELIC_OUTPUT_WRITE_FAILED',\n MERGE_INVALID_SCHEMA: 'TESTRELIC_MERGE_INVALID_SCHEMA',\n MERGE_READ_FAILED: 'TESTRELIC_MERGE_READ_FAILED',\n NAVIGATION_FLUSH_FAILED: 'TESTRELIC_NAVIGATION_FLUSH_FAILED',\n CODE_EXTRACT_FAILED: 'TESTRELIC_CODE_EXTRACT_FAILED',\n HTML_REPORT_FAILED: 'TESTRELIC_HTML_REPORT_FAILED',\n} as const;\n\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\nexport class TestRelicError extends Error {\n readonly code: ErrorCode;\n\n constructor(code: ErrorCode, message: string, cause?: unknown) {\n super(message);\n this.name = 'TestRelicError';\n this.code = code;\n if (cause !== undefined) {\n Object.defineProperty(this, 'cause', { value: cause, writable: false, enumerable: false });\n }\n }\n}\n\nexport function createError(\n code: ErrorCode,\n message: string,\n cause?: unknown,\n): TestRelicError {\n return new TestRelicError(code, message, cause);\n}\n","/**\n * @testrelic/core — Lightweight runtime type guards\n *\n * Hand-written guards, no runtime schema libraries (Constitution SDK Constraint).\n */\n\nimport type {\n ReporterConfig,\n NavigationType,\n TestRunReport,\n} from './types.js';\n\nconst NAVIGATION_TYPES = new Set<string>([\n 'goto', 'navigation', 'back', 'forward', 'refresh',\n 'spa_route', 'spa_replace', 'hash_change', 'link_click',\n 'form_submit', 'redirect', 'popstate', 'page_load',\n 'manual_record', 'fallback', 'dummy',\n]);\n\nconst DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\n\nexport function isValidNavigationType(value: unknown): value is NavigationType {\n return typeof value === 'string' && NAVIGATION_TYPES.has(value);\n}\n\nfunction hasNoPrototypePollution(obj: unknown): boolean {\n if (typeof obj !== 'object' || obj === null) return true;\n for (const key of Object.keys(obj)) {\n if (DANGEROUS_KEYS.has(key)) return false;\n }\n return true;\n}\n\nexport function isValidConfig(input: unknown): input is ReporterConfig {\n if (typeof input !== 'object' || input === null) return false;\n if (!hasNoPrototypePollution(input)) return false;\n\n const obj = input as Record<string, unknown>;\n\n if (obj.outputPath !== undefined && typeof obj.outputPath !== 'string') return false;\n if (obj.includeStackTrace !== undefined && typeof obj.includeStackTrace !== 'boolean') return false;\n if (obj.includeCodeSnippets !== undefined && typeof obj.includeCodeSnippets !== 'boolean') return false;\n if (obj.codeContextLines !== undefined && typeof obj.codeContextLines !== 'number') return false;\n if (obj.includeNetworkStats !== undefined && typeof obj.includeNetworkStats !== 'boolean') return false;\n\n if (obj.navigationTypes !== undefined && obj.navigationTypes !== null) {\n if (!Array.isArray(obj.navigationTypes)) return false;\n for (const t of obj.navigationTypes) {\n if (!isValidNavigationType(t)) return false;\n }\n }\n\n if (obj.redactPatterns !== undefined) {\n if (!Array.isArray(obj.redactPatterns)) return false;\n for (const p of obj.redactPatterns) {\n if (typeof p !== 'string' && !(p instanceof RegExp)) return false;\n }\n }\n\n if (obj.testRunId !== undefined && obj.testRunId !== null && typeof obj.testRunId !== 'string') return false;\n\n if (obj.metadata !== undefined && obj.metadata !== null) {\n if (typeof obj.metadata !== 'object') return false;\n if (!hasNoPrototypePollution(obj.metadata)) return false;\n }\n\n if (obj.openReport !== undefined && typeof obj.openReport !== 'boolean') return false;\n if (obj.htmlReportPath !== undefined && (typeof obj.htmlReportPath !== 'string' || obj.htmlReportPath === '')) return false;\n if (obj.includeArtifacts !== undefined && typeof obj.includeArtifacts !== 'boolean') return false;\n\n return true;\n}\n\nexport function isValidTestRunReport(value: unknown): value is TestRunReport {\n if (typeof value !== 'object' || value === null) return false;\n\n const obj = value as Record<string, unknown>;\n\n if (typeof obj.schemaVersion !== 'string') return false;\n if (typeof obj.testRunId !== 'string') return false;\n if (typeof obj.startedAt !== 'string') return false;\n if (typeof obj.completedAt !== 'string') return false;\n if (typeof obj.totalDuration !== 'number') return false;\n if (typeof obj.summary !== 'object' || obj.summary === null) return false;\n if (!Array.isArray(obj.timeline)) return false;\n\n const summary = obj.summary as Record<string, unknown>;\n if (typeof summary.total !== 'number') return false;\n if (typeof summary.passed !== 'number') return false;\n if (typeof summary.failed !== 'number') return false;\n if (typeof summary.flaky !== 'number') return false;\n if (typeof summary.skipped !== 'number') return false;\n // timedout is optional for backward compat with schema 1.0.0\n if (summary.timedout !== undefined && typeof summary.timedout !== 'number') return false;\n\n return true;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/logger.ts","../src/errors.ts","../src/validation.ts"],"sourcesContent":["/**\n * @testrelic/core — Shared type definitions\n *\n * All interfaces for the JSON output schema, reporter configuration,\n * and internal fixture-to-reporter communication.\n *\n * Schema Version: 1.0.0\n */\n\n// ---------------------------------------------------------------------------\n// Navigation Types\n// ---------------------------------------------------------------------------\n\nexport type NavigationType =\n | 'goto'\n | 'navigation'\n | 'back'\n | 'forward'\n | 'refresh'\n | 'spa_route'\n | 'spa_replace'\n | 'hash_change'\n | 'link_click'\n | 'form_submit'\n | 'redirect'\n | 'popstate'\n | 'page_load'\n | 'manual_record'\n | 'fallback'\n | 'dummy';\n\n// ---------------------------------------------------------------------------\n// Output Schema Types (JSON report structure)\n// ---------------------------------------------------------------------------\n\nexport interface TestRunReport {\n readonly schemaVersion: string;\n readonly testRunId: string;\n readonly startedAt: string;\n readonly completedAt: string;\n readonly totalDuration: number;\n readonly summary: Summary;\n readonly ci: CIMetadata | null;\n readonly metadata: Record<string, unknown> | null;\n readonly timeline: readonly (TimelineEntry | TimelineStep)[];\n readonly shardRunIds: string[] | null;\n}\n\n/** Breakdown of API calls by HTTP response status range. */\nexport interface ApiCallsByStatusRange {\n readonly '2xx': number;\n readonly '3xx': number;\n readonly '4xx': number;\n readonly '5xx': number;\n readonly error: number;\n}\n\n/** Statistical distribution of API call response times in milliseconds. */\nexport interface ApiResponseTimeStats {\n readonly avg: number;\n readonly min: number;\n readonly max: number;\n readonly p50: number;\n readonly p95: number;\n readonly p99: number;\n}\n\nexport interface Summary {\n readonly total: number;\n readonly passed: number;\n readonly failed: number;\n readonly flaky: number;\n readonly skipped: number;\n readonly timedout: number;\n readonly totalApiCalls: number;\n readonly uniqueApiUrls: number;\n readonly apiCallsByMethod: Record<string, number>;\n readonly apiCallsByStatusRange: ApiCallsByStatusRange;\n readonly apiResponseTime: ApiResponseTimeStats | null;\n readonly totalAssertions: number;\n readonly passedAssertions: number;\n readonly failedAssertions: number;\n readonly totalNavigations: number;\n readonly uniqueNavigationUrls: number;\n readonly totalTimelineSteps: number;\n}\n\nexport interface CIMetadata {\n readonly provider: CIProvider;\n readonly buildId: string | null;\n readonly commitSha: string | null;\n readonly branch: string | null;\n}\n\nexport type CIProvider =\n | 'github-actions'\n | 'gitlab-ci'\n | 'jenkins'\n | 'circleci'\n | 'unknown';\n\nexport interface TimelineEntry {\n readonly url: string;\n readonly navigationType: NavigationType;\n readonly visitedAt: string;\n readonly duration: number;\n readonly specFile: string;\n readonly domContentLoadedAt: string | null;\n readonly networkIdleAt: string | null;\n readonly networkStats: NetworkStats | null;\n readonly tests: TestResult[];\n}\n\nexport interface TestResult {\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 tags: string[];\n readonly failure: FailureDiagnostic | null;\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 apiCalls: readonly ApiCallRecord[] | null;\n readonly apiAssertions: readonly ApiAssertion[] | null;\n}\n\nexport type ResourceType = 'xhr' | 'document' | 'script' | 'stylesheet' | 'image' | 'font' | 'other';\n\nexport interface CapturedNetworkRequest {\n readonly url: string;\n readonly method: string;\n readonly resourceType: ResourceType;\n readonly statusCode: number;\n readonly responseTimeMs: number;\n readonly startedAt: string;\n readonly requestHeaders: Record<string, string> | null;\n readonly requestBody: string | null;\n readonly responseBody: string | null;\n readonly responseHeaders: Record<string, string> | null;\n readonly contentType: string | null;\n readonly responseSize: number;\n readonly requestBodyTruncated: boolean;\n readonly responseBodyTruncated: boolean;\n readonly isBinary: boolean;\n readonly error: string | null;\n}\n\nexport interface TestArtifacts {\n readonly screenshot?: string;\n readonly video?: string;\n}\n\nexport type TestStatus = 'passed' | 'failed' | 'skipped' | 'flaky' | 'timedout';\n\nexport type TestType = 'e2e' | 'api' | 'unit' | 'unknown';\n\nexport interface FailureDiagnostic {\n readonly message: string;\n readonly line: number | null;\n readonly code: string | null;\n readonly stack: string | null;\n}\n\nexport interface NetworkStats {\n readonly totalRequests: number;\n readonly failedRequests: number;\n readonly failedRequestUrls: string[];\n readonly totalBytes: number;\n readonly byType: ResourceBreakdown;\n}\n\nexport interface ResourceBreakdown {\n readonly xhr: number;\n readonly document: number;\n readonly script: number;\n readonly stylesheet: number;\n readonly image: number;\n readonly font: number;\n readonly other: number;\n}\n\n// ---------------------------------------------------------------------------\n// API Call Record (captured from Playwright APIRequestContext proxy)\n// ---------------------------------------------------------------------------\n\nexport interface ApiCallRecord {\n /** Unique call identifier within the test (e.g., \"api-call-0\") */\n readonly id: string;\n /** ISO 8601 timestamp when the call was initiated */\n readonly timestamp: string;\n /** HTTP method: GET, POST, PUT, PATCH, DELETE, HEAD, or custom via fetch() */\n readonly method: string;\n /** Full resolved URL (after baseURL resolution by Playwright) */\n readonly url: string;\n /** All request headers sent, or null if unavailable */\n readonly requestHeaders: Record<string, string> | null;\n /** Request payload as string (JSON-serialized for objects). Null for bodiless methods */\n readonly requestBody: string | null;\n /** HTTP response status code, or null if request errored before response */\n readonly responseStatusCode: number | null;\n /** HTTP response status text, or null if request errored before response */\n readonly responseStatusText: string | null;\n /** All response headers received, or null if request errored before response */\n readonly responseHeaders: Record<string, string> | null;\n /** Response body: string for text/JSON, base64 for binary. Null on error */\n readonly responseBody: string | null;\n /** Duration in milliseconds from request initiation to response received */\n readonly responseTimeMs: number;\n /** Whether the response body is stored as a base64-encoded binary string */\n readonly isBinary: boolean;\n /** Error message if the call failed (network error, timeout), null on success */\n readonly error: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// API Assertion Types (captured from assertion tracking)\n// ---------------------------------------------------------------------------\n\n/** Categorization of the assertion being made. */\nexport type AssertionType =\n | 'status'\n | 'statusOk'\n | 'header'\n | 'bodyField'\n | 'bodyMatch'\n | 'bodyContains'\n | 'custom';\n\n/** Source location where an assertion was made in the test file. */\nexport interface AssertionLocation {\n /** Absolute or relative file path to the test file */\n readonly file: string;\n /** Line number (1-based) */\n readonly line: number;\n /** Column number (1-based), if available */\n readonly column?: number;\n}\n\n/** A single captured assertion on API response data. */\nexport interface ApiAssertion {\n /** Links to ApiCallRecord.id from the API proxy */\n readonly callId: string;\n /** Category of assertion */\n readonly type: AssertionType;\n /** What the test expected */\n readonly expected: unknown;\n /** What was actually received */\n readonly actual: unknown;\n /** Whether the assertion passed or failed */\n readonly status: 'passed' | 'failed';\n /** Source location of the assertion in the test file */\n readonly location: AssertionLocation;\n /** String representation of the assertion expression (best-effort) */\n readonly expression?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Unified Timeline Types (navigation + API call steps)\n// ---------------------------------------------------------------------------\n\n/** Test identity block attached to every timeline step. */\nexport interface StepTestIdentity {\n readonly title: string;\n readonly fullTitle: readonly string[];\n readonly status: TestStatus;\n readonly duration: number;\n readonly retries: number;\n readonly retry: number;\n readonly tags: readonly string[];\n readonly failure: FailureDiagnostic | null;\n}\n\n/** Request details within an API call step. */\nexport interface ApiCallStepRequest {\n readonly headers: Record<string, string> | null;\n readonly body: unknown;\n}\n\n/** Response details within an API call step. */\nexport interface ApiCallStepResponse {\n readonly statusCode: number;\n readonly statusText: string;\n readonly headers: Record<string, string> | null;\n readonly body: unknown;\n}\n\n/** An assertion linked to an API call step (callId omitted — implicit from parent). */\nexport interface StepAssertion {\n readonly type: AssertionType;\n readonly expected: unknown;\n readonly actual: unknown;\n readonly status: 'passed' | 'failed';\n readonly location: AssertionLocation;\n readonly expression?: string;\n}\n\n/** A browser navigation event in the unified timeline. */\nexport interface NavigationStep {\n readonly index: number;\n readonly type: 'navigation';\n readonly url: string;\n readonly timestamp: string;\n readonly durationOnUrl: number;\n readonly navigationType: NavigationType;\n readonly domContentLoadedAt: string | null;\n readonly networkIdleAt: string | null;\n readonly networkStats: NetworkStats | null;\n readonly specFile: string;\n readonly test: StepTestIdentity;\n}\n\n/** An API request event in the unified timeline. */\nexport interface ApiCallStep {\n readonly index: number;\n readonly type: 'api_call';\n readonly callId: string;\n readonly method: string;\n readonly url: string;\n readonly timestamp: string;\n readonly responseTime: number | null;\n readonly request: ApiCallStepRequest;\n readonly response: ApiCallStepResponse | null;\n readonly error?: string | null;\n readonly assertions: readonly StepAssertion[];\n readonly specFile: string;\n readonly test: StepTestIdentity;\n}\n\n/** Discriminated union of all timeline step types. */\nexport type TimelineStep = NavigationStep | ApiCallStep;\n\n// ---------------------------------------------------------------------------\n// Configuration Types (reporter tuple options)\n// ---------------------------------------------------------------------------\n\nexport interface ReporterConfig {\n readonly outputPath?: string;\n readonly includeStackTrace?: boolean;\n readonly includeCodeSnippets?: boolean;\n readonly codeContextLines?: number;\n readonly includeNetworkStats?: boolean;\n readonly navigationTypes?: NavigationType[] | null;\n readonly redactPatterns?: (string | RegExp)[];\n readonly testRunId?: string | null;\n readonly metadata?: Record<string, unknown> | null;\n readonly openReport?: boolean;\n readonly htmlReportPath?: string;\n readonly includeArtifacts?: boolean;\n /** When false, disables API call interception in the unified fixture. Default: true */\n readonly trackApiCalls?: boolean;\n /** When true, suppresses console summary output. Default: false */\n readonly quiet?: boolean;\n /** Capture request headers for API calls. Default: true */\n readonly captureRequestHeaders?: boolean;\n /** Capture response headers for API calls. Default: true */\n readonly captureResponseHeaders?: boolean;\n /** Capture request body for API calls. Default: true */\n readonly captureRequestBody?: boolean;\n /** Capture response body for API calls. Default: true */\n readonly captureResponseBody?: boolean;\n /** Capture API assertions (both pass and fail). Default: true */\n readonly captureAssertions?: boolean;\n /**\n * Header names whose values are replaced with \"[REDACTED]\".\n * Case-insensitive matching. Default: ['authorization', 'cookie', 'set-cookie', 'x-api-key']\n */\n readonly redactHeaders?: string[];\n /**\n * Body field names whose values are replaced with \"[REDACTED]\" at any depth.\n * Default: ['password', 'secret', 'token', 'apiKey', 'api_key']\n */\n readonly redactBodyFields?: string[];\n /**\n * Only track API calls matching these URL patterns.\n * Default: undefined (track all)\n */\n readonly apiIncludeUrls?: (string | RegExp)[];\n /**\n * Exclude API calls matching these URL patterns. Takes precedence over include.\n * Default: undefined (exclude none)\n */\n readonly apiExcludeUrls?: (string | RegExp)[];\n}\n\n// ---------------------------------------------------------------------------\n// Internal Types (fixture → reporter communication)\n// ---------------------------------------------------------------------------\n\nexport interface NavigationAnnotation {\n readonly url: string;\n readonly navigationType: NavigationType;\n readonly timestamp: string;\n readonly domContentLoadedAt?: string;\n readonly networkIdleAt?: string;\n readonly networkStats?: NetworkStats;\n}\n\n// ---------------------------------------------------------------------------\n// Worker-Safe Data Payload (worker → reporter communication)\n// ---------------------------------------------------------------------------\n\n/** Consolidated data payload sent from a worker fixture to the reporter. */\nexport interface TestRelicDataPayload {\n /** Marker to identify TestRelic attachments. Always `true`. */\n readonly testRelicData: true;\n /** Payload format version (semver). */\n readonly version: string;\n /** Navigation events collected during the test. */\n readonly navigations: readonly NavigationAnnotation[];\n /** Captured network requests during the test. */\n readonly networkRequests: readonly CapturedNetworkRequest[];\n /** API calls made via APIRequestContext proxy. */\n readonly apiCalls: readonly ApiCallRecord[];\n /** Assertions captured on API responses. */\n readonly apiAssertions: readonly ApiAssertion[];\n}\n\n/** Current payload format version. */\nexport const PAYLOAD_VERSION = '1.0.0';\n\n/** Attachment name used for the consolidated payload. */\nexport const ATTACHMENT_NAME = 'testrelic-data';\n\n/** Attachment content type. */\nexport const ATTACHMENT_CONTENT_TYPE = 'application/json';\n\n/** Type guard: validates that a parsed object is a TestRelicDataPayload. */\nexport function isTestRelicDataPayload(obj: unknown): obj is TestRelicDataPayload {\n if (typeof obj !== 'object' || obj === null) return false;\n const record = obj as Record<string, unknown>;\n return (\n record.testRelicData === true &&\n typeof record.version === 'string' &&\n record.version.length > 0 &&\n Array.isArray(record.navigations) &&\n Array.isArray(record.networkRequests) &&\n Array.isArray(record.apiCalls) &&\n Array.isArray(record.apiAssertions)\n );\n}\n\n// ---------------------------------------------------------------------------\n// Merge CLI Types\n// ---------------------------------------------------------------------------\n\nexport interface MergeOptions {\n readonly output: string;\n readonly testRunId?: string;\n}\n","/**\n * @testrelic/core — Configurable logger\n *\n * Provides a Logger interface with a no-op default.\n * Never uses console.* directly (Constitution SDK Constraint).\n */\n\nexport interface Logger {\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n debug(message: string, ...args: unknown[]): void;\n}\n\nexport interface LoggerOptions {\n readonly handler?: (level: LogLevel, message: string, args: unknown[]) => void;\n}\n\nexport type LogLevel = 'info' | 'warn' | 'error' | 'debug';\n\ntype LogHandler = (level: LogLevel, message: string, args: unknown[]) => void;\n\nconst noopHandler: LogHandler = () => {};\n\nexport function createLogger(options?: LoggerOptions): Logger {\n const handler = options?.handler ?? noopHandler;\n\n return {\n info(message: string, ...args: unknown[]) {\n handler('info', message, args);\n },\n warn(message: string, ...args: unknown[]) {\n handler('warn', message, args);\n },\n error(message: string, ...args: unknown[]) {\n handler('error', message, args);\n },\n debug(message: string, ...args: unknown[]) {\n handler('debug', message, args);\n },\n };\n}\n","/**\n * @testrelic/core — Structured error factory\n *\n * Machine-readable error codes for programmatic handling.\n */\n\nexport const ErrorCode = {\n CONFIG_INVALID: 'TESTRELIC_CONFIG_INVALID',\n OUTPUT_WRITE_FAILED: 'TESTRELIC_OUTPUT_WRITE_FAILED',\n MERGE_INVALID_SCHEMA: 'TESTRELIC_MERGE_INVALID_SCHEMA',\n MERGE_READ_FAILED: 'TESTRELIC_MERGE_READ_FAILED',\n NAVIGATION_FLUSH_FAILED: 'TESTRELIC_NAVIGATION_FLUSH_FAILED',\n CODE_EXTRACT_FAILED: 'TESTRELIC_CODE_EXTRACT_FAILED',\n HTML_REPORT_FAILED: 'TESTRELIC_HTML_REPORT_FAILED',\n} as const;\n\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\nexport class TestRelicError extends Error {\n readonly code: ErrorCode;\n\n constructor(code: ErrorCode, message: string, cause?: unknown) {\n super(message);\n this.name = 'TestRelicError';\n this.code = code;\n if (cause !== undefined) {\n Object.defineProperty(this, 'cause', { value: cause, writable: false, enumerable: false });\n }\n }\n}\n\nexport function createError(\n code: ErrorCode,\n message: string,\n cause?: unknown,\n): TestRelicError {\n return new TestRelicError(code, message, cause);\n}\n","/**\n * @testrelic/core — Lightweight runtime type guards\n *\n * Hand-written guards, no runtime schema libraries (Constitution SDK Constraint).\n */\n\nimport type {\n ReporterConfig,\n NavigationType,\n TestRunReport,\n} from './types.js';\n\nconst NAVIGATION_TYPES = new Set<string>([\n 'goto', 'navigation', 'back', 'forward', 'refresh',\n 'spa_route', 'spa_replace', 'hash_change', 'link_click',\n 'form_submit', 'redirect', 'popstate', 'page_load',\n 'manual_record', 'fallback', 'dummy',\n]);\n\nconst DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\n\nexport function isValidNavigationType(value: unknown): value is NavigationType {\n return typeof value === 'string' && NAVIGATION_TYPES.has(value);\n}\n\nfunction hasNoPrototypePollution(obj: unknown): boolean {\n if (typeof obj !== 'object' || obj === null) return true;\n for (const key of Object.keys(obj)) {\n if (DANGEROUS_KEYS.has(key)) return false;\n }\n return true;\n}\n\nexport function isValidConfig(input: unknown): input is ReporterConfig {\n if (typeof input !== 'object' || input === null) return false;\n if (!hasNoPrototypePollution(input)) return false;\n\n const obj = input as Record<string, unknown>;\n\n if (obj.outputPath !== undefined && typeof obj.outputPath !== 'string') return false;\n if (obj.includeStackTrace !== undefined && typeof obj.includeStackTrace !== 'boolean') return false;\n if (obj.includeCodeSnippets !== undefined && typeof obj.includeCodeSnippets !== 'boolean') return false;\n if (obj.codeContextLines !== undefined && typeof obj.codeContextLines !== 'number') return false;\n if (obj.includeNetworkStats !== undefined && typeof obj.includeNetworkStats !== 'boolean') return false;\n\n if (obj.navigationTypes !== undefined && obj.navigationTypes !== null) {\n if (!Array.isArray(obj.navigationTypes)) return false;\n for (const t of obj.navigationTypes) {\n if (!isValidNavigationType(t)) return false;\n }\n }\n\n if (obj.redactPatterns !== undefined) {\n if (!Array.isArray(obj.redactPatterns)) return false;\n for (const p of obj.redactPatterns) {\n if (typeof p !== 'string' && !(p instanceof RegExp)) return false;\n }\n }\n\n if (obj.testRunId !== undefined && obj.testRunId !== null && typeof obj.testRunId !== 'string') return false;\n\n if (obj.metadata !== undefined && obj.metadata !== null) {\n if (typeof obj.metadata !== 'object') return false;\n if (!hasNoPrototypePollution(obj.metadata)) return false;\n }\n\n if (obj.openReport !== undefined && typeof obj.openReport !== 'boolean') return false;\n if (obj.htmlReportPath !== undefined && (typeof obj.htmlReportPath !== 'string' || obj.htmlReportPath === '')) return false;\n if (obj.includeArtifacts !== undefined && typeof obj.includeArtifacts !== 'boolean') return false;\n\n // API tracking configuration\n if (obj.trackApiCalls !== undefined && typeof obj.trackApiCalls !== 'boolean') return false;\n if (obj.captureRequestHeaders !== undefined && typeof obj.captureRequestHeaders !== 'boolean') return false;\n if (obj.captureResponseHeaders !== undefined && typeof obj.captureResponseHeaders !== 'boolean') return false;\n if (obj.captureRequestBody !== undefined && typeof obj.captureRequestBody !== 'boolean') return false;\n if (obj.captureResponseBody !== undefined && typeof obj.captureResponseBody !== 'boolean') return false;\n if (obj.captureAssertions !== undefined && typeof obj.captureAssertions !== 'boolean') return false;\n\n if (obj.redactHeaders !== undefined) {\n if (!Array.isArray(obj.redactHeaders)) return false;\n for (const h of obj.redactHeaders) {\n if (typeof h !== 'string') return false;\n }\n }\n\n if (obj.redactBodyFields !== undefined) {\n if (!Array.isArray(obj.redactBodyFields)) return false;\n for (const f of obj.redactBodyFields) {\n if (typeof f !== 'string') return false;\n }\n }\n\n if (obj.apiIncludeUrls !== undefined) {\n if (!Array.isArray(obj.apiIncludeUrls)) return false;\n for (const p of obj.apiIncludeUrls) {\n if (typeof p !== 'string' && !(p instanceof RegExp)) return false;\n }\n }\n\n if (obj.apiExcludeUrls !== undefined) {\n if (!Array.isArray(obj.apiExcludeUrls)) return false;\n for (const p of obj.apiExcludeUrls) {\n if (typeof p !== 'string' && !(p instanceof RegExp)) return false;\n }\n }\n\n return true;\n}\n\nexport function isValidTestRunReport(value: unknown): value is TestRunReport {\n if (typeof value !== 'object' || value === null) return false;\n\n const obj = value as Record<string, unknown>;\n\n if (typeof obj.schemaVersion !== 'string') return false;\n if (typeof obj.testRunId !== 'string') return false;\n if (typeof obj.startedAt !== 'string') return false;\n if (typeof obj.completedAt !== 'string') return false;\n if (typeof obj.totalDuration !== 'number') return false;\n if (typeof obj.summary !== 'object' || obj.summary === null) return false;\n if (!Array.isArray(obj.timeline)) return false;\n\n const summary = obj.summary as Record<string, unknown>;\n if (typeof summary.total !== 'number') return false;\n if (typeof summary.passed !== 'number') return false;\n if (typeof summary.failed !== 'number') return false;\n if (typeof summary.flaky !== 'number') return false;\n if (typeof summary.skipped !== 'number') return false;\n // timedout is optional for backward compat with schema 1.0.0\n if (summary.timedout !== undefined && typeof summary.timedout !== 'number') return false;\n\n return true;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testrelic/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Shared types, logger, errors, and validation for TestRelic packages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -22,6 +22,12 @@
|
|
|
22
22
|
"files": [
|
|
23
23
|
"dist"
|
|
24
24
|
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsup",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"test:watch": "vitest",
|
|
29
|
+
"typecheck": "tsc --noEmit"
|
|
30
|
+
},
|
|
25
31
|
"devDependencies": {
|
|
26
32
|
"@types/node": "^18.0.0",
|
|
27
33
|
"typescript": "^5.5.0",
|
|
@@ -32,11 +38,5 @@
|
|
|
32
38
|
"node": ">=18"
|
|
33
39
|
},
|
|
34
40
|
"license": "MIT",
|
|
35
|
-
"homepage": "https://testrelic.
|
|
36
|
-
|
|
37
|
-
"build": "tsup",
|
|
38
|
-
"test": "vitest run",
|
|
39
|
-
"test:watch": "vitest",
|
|
40
|
-
"typecheck": "tsc --noEmit"
|
|
41
|
-
}
|
|
42
|
-
}
|
|
41
|
+
"homepage": "https://testrelic.ai"
|
|
42
|
+
}
|