test-proxy-recorder 0.1.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.
@@ -0,0 +1,464 @@
1
+ import fs from 'node:fs/promises';
2
+ import http from 'node:http';
3
+ import httpProxy from 'http-proxy';
4
+ import { WebSocket, WebSocketServer } from 'ws';
5
+ import { CONTROL_ENDPOINT, DEFAULT_TIMEOUT_MS, HTTP_STATUS_BAD_GATEWAY, HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK, } from './constants.js';
6
+ import { Modes, } from './types.js';
7
+ import { getRecordingPath, loadRecordingSession, saveRecordingSession, } from './utils/fileUtils.js';
8
+ import { readRequestBody, sendJsonResponse } from './utils/httpHelpers.js';
9
+ import { generateRequestKey } from './utils/requestKeyGenerator.js';
10
+ export class ProxyServer {
11
+ targets;
12
+ currentTargetIndex;
13
+ mode;
14
+ recordingId;
15
+ replayId;
16
+ modeTimeout;
17
+ proxy;
18
+ currentSession;
19
+ recordingsDir;
20
+ constructor(targets, recordingsDir) {
21
+ this.targets = targets;
22
+ this.currentTargetIndex = 0;
23
+ this.mode = Modes.transparent;
24
+ this.recordingId = null;
25
+ this.replayId = null;
26
+ this.modeTimeout = null;
27
+ this.currentSession = null;
28
+ this.recordingsDir = recordingsDir;
29
+ this.proxy = httpProxy.createProxyServer({
30
+ secure: false,
31
+ changeOrigin: true,
32
+ });
33
+ this.setupProxyEventHandlers();
34
+ }
35
+ async init() {
36
+ await fs.mkdir(this.recordingsDir, { recursive: true });
37
+ }
38
+ listen(port) {
39
+ const server = http.createServer((req, res) => {
40
+ this.handleRequest(req, res);
41
+ });
42
+ // Handle WebSocket upgrade requests
43
+ server.on('upgrade', (req, socket, head) => {
44
+ this.handleUpgrade(req, socket, head);
45
+ });
46
+ server.listen(port, () => {
47
+ this.logServerStartup(port);
48
+ });
49
+ return server;
50
+ }
51
+ setupProxyEventHandlers() {
52
+ this.proxy.on('error', this.handleProxyError.bind(this));
53
+ this.proxy.on('proxyRes', this.handleProxyResponse.bind(this));
54
+ }
55
+ handleProxyError(err, _req, res) {
56
+ console.error('Proxy error:', err);
57
+ if (!(res instanceof http.ServerResponse)) {
58
+ return;
59
+ }
60
+ if (!res.headersSent) {
61
+ res.writeHead(HTTP_STATUS_BAD_GATEWAY, {
62
+ 'Content-Type': 'application/json',
63
+ });
64
+ }
65
+ res.end(JSON.stringify({ error: 'Proxy error', message: err.message }));
66
+ }
67
+ handleProxyResponse(proxyRes, req) {
68
+ if (this.mode === Modes.record && this.recordingId) {
69
+ this.recordResponse(req, proxyRes);
70
+ }
71
+ }
72
+ getTarget() {
73
+ const target = this.targets[this.currentTargetIndex];
74
+ this.currentTargetIndex =
75
+ (this.currentTargetIndex + 1) % this.targets.length;
76
+ return target;
77
+ }
78
+ async handleControlRequest(req, res) {
79
+ try {
80
+ const body = await readRequestBody(req);
81
+ console.log('MODE CHANGE', body);
82
+ const data = JSON.parse(body);
83
+ const { mode, id, timeout: requestTimeout } = data;
84
+ const timeout = requestTimeout ?? DEFAULT_TIMEOUT_MS;
85
+ this.clearModeTimeout();
86
+ await this.switchMode(mode, id);
87
+ this.setupModeTimeout(timeout);
88
+ sendJsonResponse(res, HTTP_STATUS_OK, {
89
+ success: true,
90
+ mode: this.mode,
91
+ id: this.recordingId || this.replayId,
92
+ timeout,
93
+ });
94
+ }
95
+ catch (error) {
96
+ console.error('Control request error:', error);
97
+ sendJsonResponse(res, HTTP_STATUS_BAD_REQUEST, {
98
+ error: error instanceof Error ? error.message : 'Unknown error',
99
+ });
100
+ }
101
+ }
102
+ clearModeTimeout() {
103
+ if (this.modeTimeout) {
104
+ clearTimeout(this.modeTimeout);
105
+ this.modeTimeout = null;
106
+ }
107
+ }
108
+ async switchMode(mode, id) {
109
+ // Save current session before switching
110
+ if (this.currentSession) {
111
+ console.log('Switching mode, saving current session first');
112
+ await this.saveCurrentSession();
113
+ console.log('Session saved, continuing with mode switch');
114
+ }
115
+ switch (mode) {
116
+ case Modes.transparent: {
117
+ this.switchToTransparentMode();
118
+ break;
119
+ }
120
+ case Modes.record: {
121
+ this.switchToRecordMode(id);
122
+ break;
123
+ }
124
+ case Modes.replay: {
125
+ this.switchToReplayMode(id);
126
+ break;
127
+ }
128
+ default: {
129
+ throw new Error('Invalid mode. Use: transparent, record, or replay');
130
+ }
131
+ }
132
+ }
133
+ switchToTransparentMode() {
134
+ this.mode = Modes.transparent;
135
+ this.recordingId = null;
136
+ this.replayId = null;
137
+ this.currentSession = null;
138
+ console.log('Switched to transparent mode');
139
+ }
140
+ switchToRecordMode(id) {
141
+ if (!id) {
142
+ throw new Error('Record ID is required');
143
+ }
144
+ this.mode = Modes.record;
145
+ this.recordingId = id;
146
+ this.replayId = null;
147
+ this.currentSession = { id, recordings: [], websocketRecordings: [] };
148
+ console.log(`Switched to record mode with ID: ${id}`);
149
+ }
150
+ switchToReplayMode(id) {
151
+ if (!id) {
152
+ throw new Error('Replay ID is required');
153
+ }
154
+ this.mode = Modes.replay;
155
+ this.replayId = id;
156
+ this.recordingId = null;
157
+ this.currentSession = null;
158
+ console.log(`Switched to replay mode with ID: ${id}`);
159
+ }
160
+ setupModeTimeout(timeout) {
161
+ if (timeout && timeout > 0) {
162
+ this.modeTimeout = setTimeout(async () => {
163
+ console.log('Timeout reached, switching back to transparent mode');
164
+ await this.saveCurrentSession();
165
+ this.switchToTransparentMode();
166
+ this.modeTimeout = null;
167
+ }, timeout);
168
+ }
169
+ }
170
+ async saveCurrentSession() {
171
+ if (!this.currentSession) {
172
+ console.log('No current session to save');
173
+ return;
174
+ }
175
+ if (this.currentSession.recordings.length === 0 &&
176
+ this.currentSession.websocketRecordings.length === 0) {
177
+ console.log('Session has no recordings, skipping save');
178
+ return;
179
+ }
180
+ console.log(`Saving session with ${this.currentSession.recordings.length} HTTP and ${this.currentSession.websocketRecordings.length} WebSocket recordings`);
181
+ await saveRecordingSession(this.recordingsDir, this.currentSession);
182
+ }
183
+ async saveRequestRecord(req, body) {
184
+ if (!this.currentSession) {
185
+ return;
186
+ }
187
+ const key = generateRequestKey(req);
188
+ const record = {
189
+ request: {
190
+ method: req.method,
191
+ url: req.url,
192
+ headers: req.headers,
193
+ body: body || null,
194
+ },
195
+ timestamp: new Date().toISOString(),
196
+ key,
197
+ };
198
+ this.currentSession.recordings.push(record);
199
+ }
200
+ async recordResponse(req, proxyRes) {
201
+ if (!this.currentSession) {
202
+ return;
203
+ }
204
+ const key = generateRequestKey(req);
205
+ const record = this.currentSession.recordings.find((r) => r.key === key);
206
+ if (!record) {
207
+ console.error('Request record not found for response:', key);
208
+ return;
209
+ }
210
+ const chunks = [];
211
+ proxyRes.on('data', (chunk) => {
212
+ chunks.push(chunk);
213
+ });
214
+ proxyRes.on('end', async () => {
215
+ const body = Buffer.concat(chunks).toString('utf8');
216
+ record.response = {
217
+ statusCode: proxyRes.statusCode,
218
+ headers: proxyRes.headers,
219
+ body: body || null,
220
+ };
221
+ await this.saveCurrentSession();
222
+ console.log(`Recorded: ${req.method} ${req.url}`);
223
+ });
224
+ }
225
+ async handleReplayRequest(req, res) {
226
+ const key = generateRequestKey(req);
227
+ const filePath = getRecordingPath(this.recordingsDir, this.replayId);
228
+ try {
229
+ const session = await loadRecordingSession(filePath);
230
+ const record = session.recordings.find((r) => r.key === key);
231
+ if (!record) {
232
+ throw new Error(`No recording found for ${key}`);
233
+ }
234
+ if (!record.response) {
235
+ throw new Error('No response recorded for this request');
236
+ }
237
+ const { statusCode, headers, body } = record.response;
238
+ res.writeHead(statusCode, headers);
239
+ res.end(body);
240
+ console.log(`Replayed: ${req.method} ${req.url}`);
241
+ }
242
+ catch (error) {
243
+ this.handleReplayError(res, error, key, filePath);
244
+ }
245
+ }
246
+ handleReplayError(res, err, key, filePath) {
247
+ const isFileNotFound = err instanceof Error && 'code' in err && err.code === 'ENOENT';
248
+ console.error('Replay error:', err);
249
+ sendJsonResponse(res, HTTP_STATUS_NOT_FOUND, {
250
+ error: isFileNotFound
251
+ ? 'Recording file not found'
252
+ : 'Recording not found',
253
+ message: err instanceof Error ? err.message : 'Unknown error',
254
+ key,
255
+ filePath,
256
+ });
257
+ }
258
+ async handleRequest(req, res) {
259
+ if (req.url === CONTROL_ENDPOINT) {
260
+ return this.handleControlRequest(req, res);
261
+ }
262
+ if (this.mode === Modes.replay) {
263
+ return this.handleReplayRequest(req, res);
264
+ }
265
+ await this.handleProxyRequest(req, res);
266
+ }
267
+ async handleProxyRequest(req, res) {
268
+ const target = this.getTarget();
269
+ console.log(`[${this.mode}] ${req.method} ${req.url} -> ${target}`);
270
+ if (this.mode === Modes.record) {
271
+ await this.bufferRequestForRecord(req);
272
+ }
273
+ this.proxy.web(req, res, { target });
274
+ }
275
+ async bufferRequestForRecord(req) {
276
+ const chunks = [];
277
+ req.on('data', (chunk) => {
278
+ chunks.push(chunk);
279
+ });
280
+ req.on('end', async () => {
281
+ const body = Buffer.concat(chunks).toString('utf8');
282
+ await this.saveRequestRecord(req, body);
283
+ });
284
+ }
285
+ handleUpgrade(req, socket, head) {
286
+ if (this.mode === Modes.replay) {
287
+ this.handleReplayWebSocket(req, socket);
288
+ return;
289
+ }
290
+ const target = this.getTarget();
291
+ console.log(`[${this.mode}] WebSocket upgrade ${req.url} -> ${target}`);
292
+ if (this.mode === Modes.record) {
293
+ this.handleRecordWebSocket(req, socket, head, target);
294
+ }
295
+ else {
296
+ // Transparent mode - just proxy through
297
+ this.proxy.ws(req, socket, head, { target });
298
+ }
299
+ }
300
+ handleRecordWebSocket(req, clientSocket, head, target) {
301
+ const url = req.url || '/';
302
+ const key = `WS_${url.replaceAll('/', '_')}`;
303
+ const wsRecording = {
304
+ url,
305
+ messages: [],
306
+ timestamp: new Date().toISOString(),
307
+ key,
308
+ };
309
+ if (this.currentSession) {
310
+ this.currentSession.websocketRecordings.push(wsRecording);
311
+ }
312
+ // Create WebSocket connection to backend
313
+ const backendWsUrl = `${target.replace('http', 'ws')}${url}`;
314
+ const backendWs = new WebSocket(backendWsUrl);
315
+ // Create WebSocket server for client
316
+ const wss = new WebSocketServer({ noServer: true });
317
+ // Wait for backend connection before accepting client
318
+ backendWs.on('open', () => {
319
+ console.log(`WebSocket recording: connected to backend ${backendWsUrl}`);
320
+ wss.handleUpgrade(req, clientSocket, head, (clientWs) => {
321
+ // Forward messages from client to backend
322
+ clientWs.on('message', (data) => {
323
+ const message = data.toString();
324
+ // Record client message
325
+ wsRecording.messages.push({
326
+ direction: 'client-to-server',
327
+ data: message,
328
+ timestamp: new Date().toISOString(),
329
+ });
330
+ // Forward to backend if connected
331
+ if (backendWs.readyState === WebSocket.OPEN) {
332
+ backendWs.send(message);
333
+ }
334
+ this.saveCurrentSession().catch((error) => {
335
+ console.error('Failed to save WebSocket recording:', error);
336
+ });
337
+ });
338
+ // Forward messages from backend to client
339
+ backendWs.on('message', (data) => {
340
+ const message = data.toString();
341
+ // Record server message
342
+ wsRecording.messages.push({
343
+ direction: 'server-to-client',
344
+ data: message,
345
+ timestamp: new Date().toISOString(),
346
+ });
347
+ // Forward to client
348
+ if (clientWs.readyState === WebSocket.OPEN) {
349
+ clientWs.send(message);
350
+ }
351
+ this.saveCurrentSession().catch((error) => {
352
+ console.error('Failed to save WebSocket recording:', error);
353
+ });
354
+ });
355
+ // Handle errors
356
+ clientWs.on('error', (err) => {
357
+ console.error('Client WebSocket error:', err);
358
+ });
359
+ backendWs.on('error', (err) => {
360
+ console.error('Backend WebSocket error:', err);
361
+ });
362
+ // Handle close
363
+ clientWs.on('close', () => {
364
+ backendWs.close();
365
+ console.log('Client WebSocket closed');
366
+ });
367
+ backendWs.on('close', () => {
368
+ clientWs.close();
369
+ console.log('Backend WebSocket closed');
370
+ });
371
+ });
372
+ });
373
+ backendWs.on('error', (err) => {
374
+ console.error('Backend WebSocket connection error:', err);
375
+ clientSocket.write('HTTP/1.1 502 Bad Gateway\r\n\r\n');
376
+ clientSocket.destroy();
377
+ });
378
+ wss.on('error', (err) => {
379
+ console.error('WebSocket server error:', err);
380
+ });
381
+ }
382
+ handleReplayWebSocket(req, socket) {
383
+ const url = req.url || '/';
384
+ const key = `WS_${url.replaceAll('/', '_')}`;
385
+ const filePath = getRecordingPath(this.recordingsDir, this.replayId);
386
+ loadRecordingSession(filePath)
387
+ .then((session) => {
388
+ const wsRecording = session.websocketRecordings.find((r) => r.key === key);
389
+ if (!wsRecording) {
390
+ socket.write('HTTP/1.1 404 Not Found\r\n\r\n');
391
+ socket.destroy();
392
+ console.log(`No WebSocket recording found for ${key}`);
393
+ return;
394
+ }
395
+ // Create WebSocket server for replay
396
+ const wss = new WebSocketServer({ noServer: true });
397
+ // Fake upgrade request with proper headers
398
+ const fakeReq = Object.assign(req, {
399
+ headers: {
400
+ ...req.headers,
401
+ 'sec-websocket-key': req.headers['sec-websocket-key'] || 'replay-key',
402
+ 'sec-websocket-version': '13',
403
+ },
404
+ });
405
+ wss.handleUpgrade(fakeReq, socket, Buffer.alloc(0), (ws) => {
406
+ console.log(`Replaying WebSocket: ${url}`);
407
+ // Replay server-to-client messages
408
+ const serverMessages = wsRecording.messages.filter((m) => m.direction === 'server-to-client');
409
+ let messageIndex = 0;
410
+ // Handle client messages and send corresponding server responses
411
+ ws.on('message', (data) => {
412
+ const clientMessage = data.toString();
413
+ console.log(`Replay: Client sent: ${clientMessage}`);
414
+ // Send next server message if available
415
+ if (messageIndex < serverMessages.length) {
416
+ setTimeout(() => {
417
+ if (ws.readyState === WebSocket.OPEN) {
418
+ ws.send(serverMessages[messageIndex].data);
419
+ console.log(`Replay: Sent server message ${messageIndex}`);
420
+ messageIndex++;
421
+ }
422
+ }, 10);
423
+ }
424
+ });
425
+ // Send initial server messages (those sent before any client message)
426
+ let initialMessagesSent = 0;
427
+ for (let i = 0; i < wsRecording.messages.length; i++) {
428
+ const msg = wsRecording.messages[i];
429
+ if (msg.direction === 'client-to-server') {
430
+ break;
431
+ }
432
+ if (msg.direction === 'server-to-client') {
433
+ setTimeout(() => {
434
+ if (ws.readyState === WebSocket.OPEN) {
435
+ ws.send(msg.data);
436
+ console.log(`Replay: Sent initial server message: ${msg.data}`);
437
+ messageIndex++;
438
+ initialMessagesSent++;
439
+ }
440
+ }, 10 * (initialMessagesSent + 1));
441
+ }
442
+ }
443
+ ws.on('error', (err) => {
444
+ console.error('Replay WebSocket error:', err);
445
+ });
446
+ ws.on('close', () => {
447
+ console.log('Replay WebSocket closed');
448
+ });
449
+ });
450
+ })
451
+ .catch((error) => {
452
+ console.error('Replay error:', error);
453
+ socket.write('HTTP/1.1 404 Not Found\r\n\r\n');
454
+ socket.destroy();
455
+ });
456
+ }
457
+ logServerStartup(port) {
458
+ console.log(`Proxy server running on http://localhost:${port}`);
459
+ console.log(`Mode: ${this.mode}`);
460
+ console.log(`Targets: ${this.targets.join(', ')}`);
461
+ console.log(`Control endpoint: http://localhost:${port}${CONTROL_ENDPOINT}`);
462
+ }
463
+ }
464
+ //# sourceMappingURL=ProxyServer.js.map
package/dist/cli.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ export interface CliOptions {
3
+ targets: string[];
4
+ port: number;
5
+ recordingsDir: string;
6
+ }
7
+ export declare function parseCliArgs(): CliOptions;
8
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AASA,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,YAAY,IAAI,UAAU,CA6CzC"}
package/dist/cli.js ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ import path from 'node:path';
3
+ import { Command } from 'commander';
4
+ const DEFAULT_PORT = 8000;
5
+ const DEFAULT_RECORDINGS_DIR = './recordings';
6
+ export function parseCliArgs() {
7
+ const program = new Command();
8
+ program
9
+ .name('dev-proxy')
10
+ .description('Development proxy server with recording and replay capabilities')
11
+ .argument('<targets...>', 'Target API service URLs (e.g., http://localhost:3000)')
12
+ .option('-p, --port <number>', 'Port number for the proxy server', String(DEFAULT_PORT))
13
+ .option('-r, --recordings-dir <path>', 'Directory to store recordings (relative to CWD)', DEFAULT_RECORDINGS_DIR)
14
+ .action(() => {
15
+ // Action handled after parse
16
+ });
17
+ program.parse();
18
+ const targets = program.args;
19
+ const options = program.opts();
20
+ const port = Number.parseInt(options.port, 10);
21
+ if (Number.isNaN(port) || port < 1025 || port > 65_535) {
22
+ console.error('Error: Invalid port number. Must be between 1 and 65535');
23
+ process.exit(1);
24
+ }
25
+ if (targets.length === 0) {
26
+ program.help();
27
+ }
28
+ // Resolve recordings directory relative to the current working directory (where the command is run)
29
+ const recordingsDir = path.resolve(process.cwd(), options.recordingsDir);
30
+ return { targets, port, recordingsDir };
31
+ }
32
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1,7 @@
1
+ export declare const DEFAULT_TIMEOUT_MS: number;
2
+ export declare const HTTP_STATUS_BAD_GATEWAY = 502;
3
+ export declare const HTTP_STATUS_OK = 200;
4
+ export declare const HTTP_STATUS_BAD_REQUEST = 400;
5
+ export declare const HTTP_STATUS_NOT_FOUND = 404;
6
+ export declare const CONTROL_ENDPOINT = "/__control";
7
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,QAAa,CAAC;AAC7C,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,cAAc,MAAM,CAAC;AAClC,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,qBAAqB,MAAM,CAAC;AACzC,eAAO,MAAM,gBAAgB,eAAe,CAAC"}
@@ -0,0 +1,7 @@
1
+ export const DEFAULT_TIMEOUT_MS = 120 * 1000;
2
+ export const HTTP_STATUS_BAD_GATEWAY = 502;
3
+ export const HTTP_STATUS_OK = 200;
4
+ export const HTTP_STATUS_BAD_REQUEST = 400;
5
+ export const HTTP_STATUS_NOT_FOUND = 404;
6
+ export const CONTROL_ENDPOINT = '/__control';
7
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1,5 @@
1
+ export { ProxyServer } from './ProxyServer';
2
+ export type { ControlRequest, Mode, Recording, RecordingSession, WebSocketRecording, } from './types';
3
+ export type { PlaywrightTestInfo, ProxyMode } from './playwright';
4
+ export { generateSessionId, playwrightProxy, setProxyMode, startRecording, startReplay, stopProxy, } from './playwright';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EACV,cAAc,EACd,IAAI,EACJ,SAAS,EACT,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,SAAS,CAAC;AAGjB,YAAY,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,YAAY,EACZ,cAAc,EACd,WAAW,EACX,SAAS,GACV,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { ProxyServer } from './ProxyServer';
2
+ export { generateSessionId, playwrightProxy, setProxyMode, startRecording, startReplay, stopProxy, } from './playwright';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,48 @@
1
+ import type { TestInfo } from '@playwright/test';
2
+ export type ProxyMode = 'recording' | 'replay' | 'transparent';
3
+ export type PlaywrightTestInfo = Pick<TestInfo, 'title'>;
4
+ /**
5
+ * Set the proxy mode for a given session
6
+ * @param mode - The proxy mode to set (recording, replay, transparent)
7
+ * @param sessionId - Unique identifier for the session
8
+ * @param timeout - Optional timeout in milliseconds
9
+ */
10
+ export declare function setProxyMode(mode: ProxyMode, sessionId: string, timeout?: number): Promise<void>;
11
+ /**
12
+ * Generate a session ID from test info
13
+ * @param testInfo - Playwright test info object
14
+ */
15
+ export declare function generateSessionId(testInfo: PlaywrightTestInfo): string;
16
+ /**
17
+ * Start recording for a test
18
+ * @param testInfo - Playwright test info object
19
+ */
20
+ export declare function startRecording(testInfo: PlaywrightTestInfo): Promise<void>;
21
+ /**
22
+ * Start replay for a test
23
+ * @param testInfo - Playwright test info object
24
+ */
25
+ export declare function startReplay(testInfo: PlaywrightTestInfo): Promise<void>;
26
+ /**
27
+ * Stop recording/replay and return to transparent mode
28
+ * @param testInfo - Playwright test info object
29
+ */
30
+ export declare function stopProxy(testInfo: PlaywrightTestInfo): Promise<void>;
31
+ /**
32
+ * Playwright test fixture helper for managing proxy mode
33
+ * Use this in beforeEach/afterEach hooks
34
+ */
35
+ export declare const playwrightProxy: {
36
+ /**
37
+ * Setup before test - sets the proxy mode
38
+ * @param testInfo - Playwright test info object
39
+ * @param mode - The proxy mode to use for this test
40
+ */
41
+ before(testInfo: PlaywrightTestInfo, mode: ProxyMode): Promise<void>;
42
+ /**
43
+ * Cleanup after test - returns to transparent mode
44
+ * @param testInfo - Playwright test info object
45
+ */
46
+ after(testInfo: PlaywrightTestInfo): Promise<void>;
47
+ };
48
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/playwright/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAKjD,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,aAAa,CAAC;AAE/D,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;AAQzD;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,SAAS,EACf,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CA+Bf;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,MAAM,CAEtE;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,kBAAkB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAGf;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,QAAQ,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAG7E;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAAC,QAAQ,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAG3E;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe;IAC1B;;;;OAIG;qBACoB,kBAAkB,QAAQ,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAM1E;;;OAGG;oBACmB,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;CAIzD,CAAC"}