qase-javascript-commons 2.5.4 → 2.5.6

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/changelog.md CHANGED
@@ -1,3 +1,25 @@
1
+ # qase-javascript-commons@2.5.6
2
+
3
+ ## What's new
4
+
5
+ - Added Network Profiler framework for automatic HTTP/HTTPS request interception during test execution.
6
+ - Added `StepType.REQUEST` step type for network request data in test results.
7
+ - Added configurable profiler options in `qase.config.json` (`profilers` and `networkProfiler` fields).
8
+
9
+ # qase-javascript-commons@2.5.5
10
+
11
+ ## What's new
12
+
13
+ Significantly improved startup performance by optimizing host data collection:
14
+
15
+ - Replaced slow `npm list --depth=10 --json` fallback with fast `require.resolve()`-based package version lookup.
16
+ - Replaced `execSync('node --version')` with `process.version`.
17
+ - Replaced `execSync('npm --version')` with `process.env.npm_config_user_agent` parsing (with execSync fallback).
18
+ - Eliminated duplicate `getHostInfo()` call in `ReportReporter.complete()` by passing pre-collected host data from `QaseReporter`.
19
+ - Included `report` mode in the `needsHostData` guard so host data is collected once during init.
20
+
21
+ Worst-case startup time reduced from ~10-25 seconds to ~55 ms.
22
+
1
23
  # qase-javascript-commons@2.5.4
2
24
 
3
25
  ## What's new
@@ -21,6 +21,7 @@ export declare class ClientV2 extends ClientV1 {
21
21
  private createBaseResultStep;
22
22
  private processTextStep;
23
23
  private processGherkinStep;
24
+ private processRequestStep;
24
25
  private getExecution;
25
26
  private getRelation;
26
27
  private getDefaultSuiteRelation;
@@ -173,9 +173,12 @@ class ClientV2 extends clientV1_1.ClientV1 {
173
173
  if (step.step_type === models_1.StepType.TEXT) {
174
174
  this.processTextStep(step, resultStep, testTitle);
175
175
  }
176
- else {
176
+ else if (step.step_type === models_1.StepType.GHERKIN) {
177
177
  this.processGherkinStep(step, resultStep);
178
178
  }
179
+ else if (step.step_type === models_1.StepType.REQUEST) {
180
+ this.processRequestStep(step, resultStep);
181
+ }
179
182
  if (step.steps.length > 0) {
180
183
  resultStep.steps = await this.transformSteps(step.steps, testTitle);
181
184
  }
@@ -215,6 +218,13 @@ class ClientV2 extends clientV1_1.ClientV1 {
215
218
  const stepData = step.data;
216
219
  resultStep.data.action = stepData.keyword;
217
220
  }
221
+ processRequestStep(step, resultStep) {
222
+ if (!('request_method' in step.data) || !resultStep.data) {
223
+ return;
224
+ }
225
+ const stepData = step.data;
226
+ resultStep.data.action = `${stepData.request_method} ${stepData.request_url}`;
227
+ }
218
228
  getExecution(exec) {
219
229
  return {
220
230
  status: ClientV2.statusMap[exec.status],
@@ -298,5 +298,29 @@ export declare const configValidationSchema: {
298
298
  };
299
299
  };
300
300
  };
301
+ profilers: {
302
+ type: string;
303
+ items: {
304
+ type: string;
305
+ };
306
+ nullable: boolean;
307
+ };
308
+ networkProfiler: {
309
+ type: string;
310
+ nullable: boolean;
311
+ properties: {
312
+ skip_domains: {
313
+ type: string;
314
+ items: {
315
+ type: string;
316
+ };
317
+ nullable: boolean;
318
+ };
319
+ track_on_fail: {
320
+ type: string;
321
+ nullable: boolean;
322
+ };
323
+ };
324
+ };
301
325
  };
302
326
  };
@@ -282,5 +282,27 @@ exports.configValidationSchema = {
282
282
  },
283
283
  },
284
284
  },
285
+ profilers: {
286
+ type: 'array',
287
+ items: {
288
+ type: 'string',
289
+ },
290
+ nullable: true,
291
+ },
292
+ networkProfiler: {
293
+ type: 'object',
294
+ nullable: true,
295
+ properties: {
296
+ skip_domains: {
297
+ type: 'array',
298
+ items: { type: 'string' },
299
+ nullable: true,
300
+ },
301
+ track_on_fail: {
302
+ type: 'boolean',
303
+ nullable: true,
304
+ },
305
+ },
306
+ },
285
307
  },
286
308
  };
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ export * from './models';
6
6
  export * from './options';
7
7
  export * from './reporters';
8
8
  export * from './writer';
9
+ export * from './profilers';
9
10
  export * from './utils/get-package-version';
10
11
  export * from './utils/mimeTypes';
11
12
  export * from './utils/project-mapping-utils';
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@ __exportStar(require("./models"), exports);
22
22
  __exportStar(require("./options"), exports);
23
23
  __exportStar(require("./reporters"), exports);
24
24
  __exportStar(require("./writer"), exports);
25
+ __exportStar(require("./profilers"), exports);
25
26
  __exportStar(require("./utils/get-package-version"), exports);
26
27
  __exportStar(require("./utils/mimeTypes"), exports);
27
28
  __exportStar(require("./utils/project-mapping-utils"), exports);
@@ -2,6 +2,7 @@ export { TestResultType } from './test-result';
2
2
  export type { Relation, Suite, SuiteData, TestopsProjectMapping } from './test-result';
3
3
  export { TestExecution, TestStatusEnum } from './test-execution';
4
4
  export { TestStepType, StepType } from './test-step';
5
+ export type { StepRequestData, StepTextData, StepGherkinData } from './step-data';
5
6
  export { StepStatusEnum } from './step-execution';
6
7
  export type { Attachment } from './attachment';
7
8
  export type { Report } from './report';
@@ -8,3 +8,12 @@ export interface StepGherkinData {
8
8
  name: string;
9
9
  line: number;
10
10
  }
11
+ export interface StepRequestData {
12
+ request_method: string;
13
+ request_url: string;
14
+ request_headers: Record<string, string> | null;
15
+ request_body: string | null;
16
+ status_code: number | null;
17
+ response_body: string | null;
18
+ response_headers: Record<string, string> | null;
19
+ }
@@ -1,14 +1,15 @@
1
- import { StepGherkinData, StepTextData } from './step-data';
1
+ import { StepGherkinData, StepRequestData, StepTextData } from './step-data';
2
2
  import { StepExecution } from './step-execution';
3
3
  import { Attachment } from './attachment';
4
4
  export declare enum StepType {
5
5
  TEXT = "text",
6
- GHERKIN = "gherkin"
6
+ GHERKIN = "gherkin",
7
+ REQUEST = "request"
7
8
  }
8
9
  export declare class TestStepType {
9
10
  id: string;
10
11
  step_type: StepType;
11
- data: StepTextData | StepGherkinData;
12
+ data: StepTextData | StepGherkinData | StepRequestData;
12
13
  parent_id: string | null;
13
14
  execution: StepExecution;
14
15
  attachments: Attachment[];
@@ -6,6 +6,7 @@ var StepType;
6
6
  (function (StepType) {
7
7
  StepType["TEXT"] = "text";
8
8
  StepType["GHERKIN"] = "gherkin";
9
+ StepType["REQUEST"] = "request";
9
10
  })(StepType || (exports.StepType = StepType = {}));
10
11
  class TestStepType {
11
12
  id;
@@ -29,13 +30,24 @@ class TestStepType {
29
30
  data: null,
30
31
  };
31
32
  }
32
- else {
33
+ else if (type === StepType.GHERKIN) {
33
34
  this.data = {
34
35
  keyword: '',
35
36
  name: '',
36
37
  line: 0,
37
38
  };
38
39
  }
40
+ else {
41
+ this.data = {
42
+ request_method: '',
43
+ request_url: '',
44
+ request_headers: null,
45
+ request_body: null,
46
+ status_code: null,
47
+ response_body: null,
48
+ response_headers: null,
49
+ };
50
+ }
39
51
  }
40
52
  }
41
53
  exports.TestStepType = TestStepType;
@@ -31,6 +31,11 @@ export type OptionsType = {
31
31
  /** Multi-project configuration (used when mode is testops_multi). */
32
32
  testops_multi?: TestOpsMultiConfigType | undefined;
33
33
  report?: RecursivePartial<AdditionalReportOptionsType> | undefined;
34
+ profilers?: string[] | undefined;
35
+ networkProfiler?: {
36
+ skip_domains?: string[] | undefined;
37
+ track_on_fail?: boolean | undefined;
38
+ } | undefined;
34
39
  };
35
40
  export type FrameworkOptionsType<F extends string, O> = {
36
41
  framework?: Partial<Record<F, O>>;
@@ -0,0 +1,5 @@
1
+ export declare abstract class AbstractProfiler {
2
+ abstract enable(): void;
3
+ abstract disable(): void;
4
+ abstract restore(): void;
5
+ }
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AbstractProfiler = void 0;
4
+ class AbstractProfiler {
5
+ }
6
+ exports.AbstractProfiler = AbstractProfiler;
@@ -0,0 +1,60 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { TestStepType } from '../models/test-step';
3
+ interface ProfilerRef {
4
+ shouldSkip(url: string): boolean;
5
+ trackOnFail: boolean;
6
+ fallbackSteps: TestStepType[];
7
+ }
8
+ /**
9
+ * FetchInterceptor subscribes to undici diagnostics_channel events to capture
10
+ * outgoing fetch() calls as REQUEST-type steps in the AsyncLocalStorage store.
11
+ *
12
+ * Works with Node.js 18+ global fetch (which uses undici internally).
13
+ * Gracefully skips subscription on Node < 18 (no global fetch).
14
+ *
15
+ * Key design decisions:
16
+ * - WeakMap keyed on undici request objects (same reference across all events)
17
+ * - ALS context captured at undici:request:create time, NOT at headers time.
18
+ * This is critical because undici connection pooling can break AsyncLocalStorage
19
+ * propagation between the create and headers events when connections are reused.
20
+ * - globalThis.fetch wrapping intercepts error response bodies (undici:request:bodyChunkReceived
21
+ * is not available in Node 22's built-in undici)
22
+ * - All handlers wrapped in try/catch (INTC-06: silent failure)
23
+ *
24
+ * Event flow:
25
+ * undici:request:create → capture method, URL, start time, ALS context → WeakMap
26
+ * undici:request:headers → build step using WeakMap data, push to ALS accumulator
27
+ * undici:request:trailers → cleanup WeakMap entry
28
+ * fetch wrapper → update response_body for error responses (>= 400)
29
+ */
30
+ export declare class FetchInterceptor {
31
+ private readonly store;
32
+ private readonly profiler;
33
+ private readonly pendingRequests;
34
+ private readonly bodyHandlers;
35
+ private readonly createHandler;
36
+ private readonly headersHandler;
37
+ private readonly trailersHandler;
38
+ private origFetch;
39
+ constructor(store: AsyncLocalStorage<TestStepType[]>, profiler: ProfilerRef);
40
+ /**
41
+ * Subscribe to undici diagnostics_channel events and wrap global fetch for
42
+ * error response body capture.
43
+ *
44
+ * No-op on Node < 18 (no global fetch) or if diagnostics_channel unavailable.
45
+ */
46
+ subscribe(): void;
47
+ /**
48
+ * Unsubscribe from diagnostics_channel events and restore original global fetch.
49
+ */
50
+ unsubscribe(): void;
51
+ private handleCreate;
52
+ private handleHeaders;
53
+ private handleTrailers;
54
+ /**
55
+ * Called by the fetch wrapper when an error response body is available.
56
+ * Finds the most recently added step for this URL/status and updates its response_body.
57
+ */
58
+ private applyResponseBody;
59
+ }
60
+ export {};
@@ -0,0 +1,277 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FetchInterceptor = void 0;
4
+ const http_interceptor_1 = require("./http-interceptor");
5
+ // ─── FetchInterceptor ─────────────────────────────────────────────────────────
6
+ /**
7
+ * FetchInterceptor subscribes to undici diagnostics_channel events to capture
8
+ * outgoing fetch() calls as REQUEST-type steps in the AsyncLocalStorage store.
9
+ *
10
+ * Works with Node.js 18+ global fetch (which uses undici internally).
11
+ * Gracefully skips subscription on Node < 18 (no global fetch).
12
+ *
13
+ * Key design decisions:
14
+ * - WeakMap keyed on undici request objects (same reference across all events)
15
+ * - ALS context captured at undici:request:create time, NOT at headers time.
16
+ * This is critical because undici connection pooling can break AsyncLocalStorage
17
+ * propagation between the create and headers events when connections are reused.
18
+ * - globalThis.fetch wrapping intercepts error response bodies (undici:request:bodyChunkReceived
19
+ * is not available in Node 22's built-in undici)
20
+ * - All handlers wrapped in try/catch (INTC-06: silent failure)
21
+ *
22
+ * Event flow:
23
+ * undici:request:create → capture method, URL, start time, ALS context → WeakMap
24
+ * undici:request:headers → build step using WeakMap data, push to ALS accumulator
25
+ * undici:request:trailers → cleanup WeakMap entry
26
+ * fetch wrapper → update response_body for error responses (>= 400)
27
+ */
28
+ class FetchInterceptor {
29
+ store;
30
+ profiler;
31
+ // WeakMap keyed on undici request objects (same reference across create/headers/trailers events)
32
+ pendingRequests = new WeakMap();
33
+ // Map from step to body-update callback (for error response body capture)
34
+ bodyHandlers = new WeakMap();
35
+ // Bound handler references (needed for subscribe/unsubscribe identity)
36
+ createHandler;
37
+ headersHandler;
38
+ trailersHandler;
39
+ // Saved original fetch for wrapping/unwrapping
40
+ origFetch = null;
41
+ constructor(store, profiler) {
42
+ this.store = store;
43
+ this.profiler = profiler;
44
+ // Bind handlers as arrow functions for subscribe/unsubscribe reference identity
45
+ this.createHandler = (msg) => {
46
+ try {
47
+ this.handleCreate(msg);
48
+ }
49
+ catch {
50
+ // INTC-06: silent failure
51
+ }
52
+ };
53
+ this.headersHandler = (msg) => {
54
+ try {
55
+ this.handleHeaders(msg);
56
+ }
57
+ catch {
58
+ // INTC-06: silent failure
59
+ }
60
+ };
61
+ this.trailersHandler = (msg) => {
62
+ try {
63
+ this.handleTrailers(msg);
64
+ }
65
+ catch {
66
+ // INTC-06: silent failure
67
+ }
68
+ };
69
+ }
70
+ /**
71
+ * Subscribe to undici diagnostics_channel events and wrap global fetch for
72
+ * error response body capture.
73
+ *
74
+ * No-op on Node < 18 (no global fetch) or if diagnostics_channel unavailable.
75
+ */
76
+ subscribe() {
77
+ // Guard: require global fetch (Node 18+)
78
+ if (typeof globalThis.fetch !== 'function') {
79
+ return;
80
+ }
81
+ let dc;
82
+ try {
83
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
84
+ dc = require('node:diagnostics_channel');
85
+ }
86
+ catch {
87
+ return; // diagnostics_channel unavailable — skip entirely
88
+ }
89
+ if (typeof dc.subscribe !== 'function') {
90
+ return;
91
+ }
92
+ dc.subscribe('undici:request:create', this.createHandler);
93
+ dc.subscribe('undici:request:headers', this.headersHandler);
94
+ dc.subscribe('undici:request:trailers', this.trailersHandler);
95
+ // Wrap global fetch to intercept error response bodies
96
+ // (undici:request:bodyChunkReceived not available in Node 22 built-in undici)
97
+ this.origFetch = globalThis.fetch;
98
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
99
+ const self = this;
100
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
101
+ globalThis.fetch = async function wrappedFetch(input, init) {
102
+ const origFetch = self.origFetch;
103
+ if (!origFetch) {
104
+ throw new Error('fetch not available');
105
+ }
106
+ const response = await origFetch.call(globalThis, input, init);
107
+ // Intercept error responses to capture body
108
+ if (response.status >= 400 && self.profiler.trackOnFail) {
109
+ try {
110
+ // Clone to read body without consuming the original response
111
+ const cloned = response.clone();
112
+ void cloned.text().then((bodyText) => {
113
+ try {
114
+ self.applyResponseBody(input, response.status, bodyText);
115
+ }
116
+ catch {
117
+ // INTC-06: silent failure
118
+ }
119
+ });
120
+ }
121
+ catch {
122
+ // INTC-06: silent failure
123
+ }
124
+ }
125
+ return response;
126
+ };
127
+ }
128
+ /**
129
+ * Unsubscribe from diagnostics_channel events and restore original global fetch.
130
+ */
131
+ unsubscribe() {
132
+ // Restore original fetch
133
+ if (this.origFetch !== null) {
134
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
135
+ globalThis.fetch = this.origFetch;
136
+ this.origFetch = null;
137
+ }
138
+ let dc;
139
+ try {
140
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
141
+ dc = require('node:diagnostics_channel');
142
+ }
143
+ catch {
144
+ return;
145
+ }
146
+ // Feature-detect unsubscribe (Pitfall 5: not available in all Node versions)
147
+ if (typeof dc.unsubscribe !== 'function') {
148
+ return;
149
+ }
150
+ try {
151
+ dc.unsubscribe('undici:request:create', this.createHandler);
152
+ }
153
+ catch {
154
+ // silent
155
+ }
156
+ try {
157
+ dc.unsubscribe('undici:request:headers', this.headersHandler);
158
+ }
159
+ catch {
160
+ // silent
161
+ }
162
+ try {
163
+ dc.unsubscribe('undici:request:trailers', this.trailersHandler);
164
+ }
165
+ catch {
166
+ // silent
167
+ }
168
+ }
169
+ // ─── Private event handlers ─────────────────────────────────────────────────
170
+ handleCreate(msg) {
171
+ const message = msg;
172
+ const req = message.request;
173
+ if (!req)
174
+ return;
175
+ // CRITICAL: Capture ALS context HERE (at create time), not in headersHandler.
176
+ // Due to undici connection pooling, store.getStore() may return undefined in
177
+ // the headers event when connections are reused across test runs.
178
+ const acc = this.store.getStore();
179
+ // acc may be null/undefined when outside a run() context — use fallback accumulator in that case
180
+ const method = typeof req['method'] === 'string' ? req['method'] : 'GET';
181
+ const origin = typeof req['origin'] === 'string' ? req['origin'] : '';
182
+ const path = typeof req['path'] === 'string' ? req['path'] : '/';
183
+ const url = `${origin}${path}`;
184
+ // Check shouldSkip
185
+ if (this.profiler.shouldSkip(url)) {
186
+ return;
187
+ }
188
+ this.pendingRequests.set(req, {
189
+ method,
190
+ url,
191
+ startTime: Date.now(),
192
+ alsContext: acc ?? null,
193
+ });
194
+ }
195
+ handleHeaders(msg) {
196
+ const message = msg;
197
+ const req = message.request;
198
+ if (!req)
199
+ return;
200
+ const pending = this.pendingRequests.get(req);
201
+ if (!pending)
202
+ return;
203
+ // Use the ALS context captured at create time — do NOT call store.getStore() here.
204
+ // Connection pooling in undici can break ALS propagation between events.
205
+ const acc = pending.alsContext;
206
+ const endTime = Date.now();
207
+ const statusCode = message.response?.statusCode ?? 0;
208
+ const step = (0, http_interceptor_1.buildRequestStep)({
209
+ method: pending.method,
210
+ url: pending.url,
211
+ statusCode,
212
+ responseBody: null, // filled in later for errors via fetch wrapper
213
+ responseHeaders: null,
214
+ startTime: pending.startTime,
215
+ endTime,
216
+ });
217
+ if (acc !== null) {
218
+ acc.push(step);
219
+ }
220
+ else {
221
+ // No ALS context — push to fallback accumulator (for Jest/Vitest/WDIO workers)
222
+ this.profiler.fallbackSteps.push(step);
223
+ }
224
+ // Register body handler for error responses
225
+ if (statusCode >= 400 && this.profiler.trackOnFail) {
226
+ this.bodyHandlers.set(step, (bodyText) => {
227
+ step.data.response_body = bodyText;
228
+ });
229
+ }
230
+ }
231
+ handleTrailers(msg) {
232
+ const message = msg;
233
+ const req = message.request;
234
+ if (!req)
235
+ return;
236
+ // Clean up pending state
237
+ this.pendingRequests.delete(req);
238
+ }
239
+ /**
240
+ * Called by the fetch wrapper when an error response body is available.
241
+ * Finds the most recently added step for this URL/status and updates its response_body.
242
+ */
243
+ applyResponseBody(input, statusCode, bodyText) {
244
+ const url = typeof input === 'string'
245
+ ? input
246
+ : input instanceof URL
247
+ ? input.toString()
248
+ : input.url;
249
+ // Get all accumulated steps — use ALS context if available, fallback accumulator otherwise
250
+ const searchArray = this.store.getStore() ?? this.profiler.fallbackSteps;
251
+ if (searchArray.length === 0)
252
+ return;
253
+ // Find the last step that matches this response and has null body
254
+ for (let i = searchArray.length - 1; i >= 0; i--) {
255
+ const step = searchArray[i];
256
+ if (!step)
257
+ continue;
258
+ const data = step.data;
259
+ if (data.status_code !== statusCode)
260
+ continue;
261
+ if (data.response_body !== null)
262
+ continue;
263
+ // Check if URL matches (handle query strings, trailing slashes)
264
+ const stepUrl = data.request_url ?? '';
265
+ const inputUrlBase = url.split('?')[0] ?? url;
266
+ if (url.includes(stepUrl) || stepUrl.includes(inputUrlBase) || stepUrl === inputUrlBase) {
267
+ const handler = this.bodyHandlers.get(step);
268
+ if (handler) {
269
+ handler(bodyText);
270
+ this.bodyHandlers.delete(step);
271
+ return;
272
+ }
273
+ }
274
+ }
275
+ }
276
+ }
277
+ exports.FetchInterceptor = FetchInterceptor;
@@ -0,0 +1,69 @@
1
+ import http from 'node:http';
2
+ import { AsyncLocalStorage } from 'node:async_hooks';
3
+ import { TestStepType } from '../models/test-step';
4
+ interface ProfilerRef {
5
+ shouldSkip(url: string): boolean;
6
+ trackOnFail: boolean;
7
+ fallbackSteps: TestStepType[];
8
+ }
9
+ export interface RequestInfo {
10
+ method: string;
11
+ url: string;
12
+ }
13
+ /**
14
+ * Handles the 3 `http.request()` call signatures:
15
+ * - (url: string, options?, callback?)
16
+ * - (url: URL, options?, callback?)
17
+ * - (options: RequestOptions, callback?)
18
+ *
19
+ * Returns `{ method, url }`. Default method is 'GET'.
20
+ */
21
+ export declare function extractRequestInfo(args: [
22
+ string | URL | http.RequestOptions,
23
+ (http.RequestOptions | ((res: http.IncomingMessage) => void))?,
24
+ ((res: http.IncomingMessage) => void)?
25
+ ]): RequestInfo;
26
+ /**
27
+ * Converts `http.IncomingHttpHeaders` (which has `string | string[] | undefined` values)
28
+ * to `Record<string, string>`. Multi-value headers joined with `', '`. Undefined values skipped.
29
+ */
30
+ export declare function headersToRecord(headers: http.IncomingHttpHeaders): Record<string, string>;
31
+ export declare function buildRequestStep(params: {
32
+ method: string;
33
+ url: string;
34
+ statusCode: number;
35
+ responseBody: string | null;
36
+ responseHeaders: Record<string, string> | null;
37
+ startTime: number;
38
+ endTime: number;
39
+ }): TestStepType;
40
+ /**
41
+ * HttpInterceptor monkey-patches Node.js http/https module functions to capture
42
+ * outgoing requests as REQUEST-type steps in the AsyncLocalStorage store.
43
+ *
44
+ * Pattern:
45
+ * 1. `install()` — saves originals, replaces with wrappers
46
+ * 2. `uninstall()` — restores originals
47
+ */
48
+ export declare class HttpInterceptor {
49
+ private readonly store;
50
+ private readonly profiler;
51
+ private origHttpRequest;
52
+ private origHttpGet;
53
+ private origHttpsRequest;
54
+ private origHttpsGet;
55
+ constructor(store: AsyncLocalStorage<TestStepType[]>, profiler: ProfilerRef);
56
+ install(): void;
57
+ uninstall(): void;
58
+ /**
59
+ * Creates a wrapper around a http.request-like function.
60
+ * The wrapper:
61
+ * 1. Calls the original function to get the ClientRequest
62
+ * 2. Checks if there's an active ALS context — if not, returns unchanged
63
+ * 3. Checks shouldSkip — if true, returns unchanged
64
+ * 4. Intercepts the 'response' event via req.emit override
65
+ * 5. Builds a TestStepType on response end and pushes to accumulator
66
+ */
67
+ private wrapRequestFn;
68
+ }
69
+ export {};