test-proxy-recorder 0.1.4 → 0.1.5
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/README.md +76 -10
- package/dist/{index-De4mgziH.d.cts → index-CBjvm5rb.d.cts} +5 -1
- package/dist/{index-De4mgziH.d.ts → index-CBjvm5rb.d.ts} +5 -1
- package/dist/index.cjs +43 -4
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +43 -4
- package/dist/playwright/index.cjs +39 -3
- package/dist/playwright/index.d.cts +1 -1
- package/dist/playwright/index.d.ts +1 -1
- package/dist/playwright/index.mjs +39 -3
- package/dist/proxy.js +6 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -191,7 +191,7 @@ interface ControlRequest {
|
|
|
191
191
|
### Playwright Integration API
|
|
192
192
|
|
|
193
193
|
```typescript
|
|
194
|
-
import { playwrightProxy } from 'test-proxy-recorder';
|
|
194
|
+
import { playwrightProxy, setProxyMode } from 'test-proxy-recorder';
|
|
195
195
|
|
|
196
196
|
// Main helper object for use with Playwright tests
|
|
197
197
|
const playwrightProxy = {
|
|
@@ -203,14 +203,84 @@ const playwrightProxy = {
|
|
|
203
203
|
};
|
|
204
204
|
```
|
|
205
205
|
|
|
206
|
-
|
|
206
|
+
### Global Teardown and Hooks Setup (Recommended)
|
|
207
|
+
|
|
208
|
+
For robust test setups, it's recommended to configure global teardown and afterEach hooks to ensure the proxy is properly reset even when tests fail. This prevents the proxy from staying in record/replay mode, which could affect subsequent test runs.
|
|
209
|
+
|
|
210
|
+
#### 1. Create Global Teardown File
|
|
211
|
+
|
|
212
|
+
Create `e2e/global-teardown.ts` to reset the proxy mode after all tests complete:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
import { setProxyMode } from 'test-proxy-recorder';
|
|
216
|
+
|
|
217
|
+
async function globalTeardown() {
|
|
218
|
+
await setProxyMode('transparent');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export default globalTeardown;
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
#### 2. Create Global Hooks File
|
|
225
|
+
|
|
226
|
+
Create `e2e/global-hooks.ts` to ensure proxy cleanup happens after each test, even on failure:
|
|
207
227
|
|
|
208
|
-
|
|
228
|
+
```typescript
|
|
229
|
+
import { test } from '@playwright/test';
|
|
230
|
+
import { playwrightProxy } from 'test-proxy-recorder';
|
|
209
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Global afterEach hook to ensure proxy cleanup happens even when tests fail.
|
|
234
|
+
* This will run after every test across all test files.
|
|
235
|
+
*/
|
|
236
|
+
test.afterEach(async ({}, testInfo) => {
|
|
237
|
+
try {
|
|
238
|
+
await playwrightProxy.after(testInfo);
|
|
239
|
+
} catch (error) {
|
|
240
|
+
console.error('Error during proxy cleanup:', error);
|
|
241
|
+
// Don't throw - we want cleanup to continue even if this fails
|
|
242
|
+
}
|
|
243
|
+
});
|
|
210
244
|
```
|
|
245
|
+
|
|
246
|
+
#### 3. Configure Playwright
|
|
247
|
+
|
|
248
|
+
Update your `playwright.config.ts` to include the global teardown:
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
import { defineConfig } from '@playwright/test';
|
|
252
|
+
|
|
253
|
+
export default defineConfig({
|
|
254
|
+
testDir: './e2e',
|
|
255
|
+
globalTeardown: './e2e/global-teardown.ts',
|
|
256
|
+
// ... rest of your config
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
#### 4. Import Global Hooks in Your Base Page or Test Setup
|
|
261
|
+
|
|
262
|
+
Import the global hooks file in your base test file or base page to register the afterEach hook:
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
// In your e2e/basePage.ts or similar base test file
|
|
266
|
+
import { test as base } from '@playwright/test';
|
|
267
|
+
|
|
268
|
+
// Import global hooks to register afterEach for proxy cleanup
|
|
269
|
+
import './global-hooks';
|
|
270
|
+
|
|
271
|
+
export const test = base.extend({
|
|
272
|
+
// your fixtures
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Recording Format
|
|
277
|
+
|
|
278
|
+
Recordings are stored as JSON files with `.mock.json` extension in the recordings directory:
|
|
279
|
+
|
|
280
|
+
```text
|
|
211
281
|
recordings/
|
|
212
|
-
├── test-session-1.json
|
|
213
|
-
├── test-session-2.json
|
|
282
|
+
├── test-session-1.mock.json
|
|
283
|
+
├── test-session-2.mock.json
|
|
214
284
|
└── ...
|
|
215
285
|
```
|
|
216
286
|
|
|
@@ -228,11 +298,7 @@ Each recording contains:
|
|
|
228
298
|
test-proxy-recorder http://localhost:8000 --port 8100
|
|
229
299
|
```
|
|
230
300
|
|
|
231
|
-
2. **Configure your app** to use the proxy
|
|
232
|
-
|
|
233
|
-
```bash
|
|
234
|
-
export EXTERNAL_API_URL=http://localhost:8100 yarn dev
|
|
235
|
-
```
|
|
301
|
+
2. **Configure your app** to use the proxy (point your app to the proxy port, e.g., 8100)
|
|
236
302
|
|
|
237
303
|
3. **Record responses** (first run):
|
|
238
304
|
|
|
@@ -47,7 +47,7 @@ interface RecordingSession {
|
|
|
47
47
|
websocketRecordings: WebSocketRecording[];
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
type PlaywrightTestInfo = Pick<TestInfo, 'title'>;
|
|
50
|
+
type PlaywrightTestInfo = Pick<TestInfo, 'title' | 'titlePath'>;
|
|
51
51
|
/**
|
|
52
52
|
* Set the proxy mode for a given session
|
|
53
53
|
* @param mode - The proxy mode to set (recording, replay, transparent)
|
|
@@ -57,6 +57,10 @@ type PlaywrightTestInfo = Pick<TestInfo, 'title'>;
|
|
|
57
57
|
declare function setProxyMode(mode: Mode, sessionId?: string, timeout?: number): Promise<void>;
|
|
58
58
|
/**
|
|
59
59
|
* Generate a session ID from test info
|
|
60
|
+
* Uses titlePath to create folder structure with test file name
|
|
61
|
+
* Supports both .spec.ts and .test.ts extensions
|
|
62
|
+
* Example: ['jobs/Create.spec.ts', 'create a job'] becomes 'jobs/Create__create-a-job'
|
|
63
|
+
* Example: ['users/Auth.test.ts', 'login test'] becomes 'users/Auth__login-test'
|
|
60
64
|
* @param testInfo - Playwright test info object
|
|
61
65
|
*/
|
|
62
66
|
declare function generateSessionId(testInfo: PlaywrightTestInfo): string;
|
|
@@ -47,7 +47,7 @@ interface RecordingSession {
|
|
|
47
47
|
websocketRecordings: WebSocketRecording[];
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
type PlaywrightTestInfo = Pick<TestInfo, 'title'>;
|
|
50
|
+
type PlaywrightTestInfo = Pick<TestInfo, 'title' | 'titlePath'>;
|
|
51
51
|
/**
|
|
52
52
|
* Set the proxy mode for a given session
|
|
53
53
|
* @param mode - The proxy mode to set (recording, replay, transparent)
|
|
@@ -57,6 +57,10 @@ type PlaywrightTestInfo = Pick<TestInfo, 'title'>;
|
|
|
57
57
|
declare function setProxyMode(mode: Mode, sessionId?: string, timeout?: number): Promise<void>;
|
|
58
58
|
/**
|
|
59
59
|
* Generate a session ID from test info
|
|
60
|
+
* Uses titlePath to create folder structure with test file name
|
|
61
|
+
* Supports both .spec.ts and .test.ts extensions
|
|
62
|
+
* Example: ['jobs/Create.spec.ts', 'create a job'] becomes 'jobs/Create__create-a-job'
|
|
63
|
+
* Example: ['users/Auth.test.ts', 'login test'] becomes 'users/Auth__login-test'
|
|
60
64
|
* @param testInfo - Playwright test info object
|
|
61
65
|
*/
|
|
62
66
|
declare function generateSessionId(testInfo: PlaywrightTestInfo): string;
|
package/dist/index.cjs
CHANGED
|
@@ -35,7 +35,7 @@ var Modes = {
|
|
|
35
35
|
};
|
|
36
36
|
var JSON_INDENT_SPACES = 2;
|
|
37
37
|
function getRecordingPath(recordingsDir, id) {
|
|
38
|
-
return path__default.default.join(recordingsDir, `${id}.json`);
|
|
38
|
+
return path__default.default.join(recordingsDir, `${id}.mock.json`);
|
|
39
39
|
}
|
|
40
40
|
async function loadRecordingSession(filePath) {
|
|
41
41
|
const fileContent = await fs__default.default.readFile(filePath, "utf8");
|
|
@@ -43,6 +43,8 @@ async function loadRecordingSession(filePath) {
|
|
|
43
43
|
}
|
|
44
44
|
async function saveRecordingSession(recordingsDir, session) {
|
|
45
45
|
const filePath = getRecordingPath(recordingsDir, session.id);
|
|
46
|
+
const dirPath = path__default.default.dirname(filePath);
|
|
47
|
+
await fs__default.default.mkdir(dirPath, { recursive: true });
|
|
46
48
|
await fs__default.default.writeFile(
|
|
47
49
|
filePath,
|
|
48
50
|
JSON.stringify(session, null, JSON_INDENT_SPACES)
|
|
@@ -127,6 +129,7 @@ var ProxyServer = class {
|
|
|
127
129
|
this.handleUpgrade(req, socket, head);
|
|
128
130
|
});
|
|
129
131
|
server.listen(port, () => {
|
|
132
|
+
process.env.TEST_PROXY_RECORDER_PORT = String(port);
|
|
130
133
|
this.logServerStartup(port);
|
|
131
134
|
});
|
|
132
135
|
return server;
|
|
@@ -605,15 +608,25 @@ var ProxyServer = class {
|
|
|
605
608
|
};
|
|
606
609
|
|
|
607
610
|
// src/playwright/index.ts
|
|
608
|
-
|
|
611
|
+
function getProxyPort() {
|
|
612
|
+
const envPort = process.env.TEST_PROXY_RECORDER_PORT;
|
|
613
|
+
if (envPort) {
|
|
614
|
+
const parsed = Number.parseInt(envPort, 10);
|
|
615
|
+
if (!Number.isNaN(parsed)) {
|
|
616
|
+
return parsed;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return 8100;
|
|
620
|
+
}
|
|
609
621
|
async function setProxyMode(mode, sessionId, timeout) {
|
|
622
|
+
const proxyPort = getProxyPort();
|
|
610
623
|
try {
|
|
611
624
|
const body = {
|
|
612
625
|
mode,
|
|
613
626
|
id: sessionId,
|
|
614
627
|
...timeout && { timeout }
|
|
615
628
|
};
|
|
616
|
-
const response = await fetch(
|
|
629
|
+
const response = await fetch(`http://127.0.0.1:${proxyPort}/__control`, {
|
|
617
630
|
method: "POST",
|
|
618
631
|
headers: { "Content-Type": "application/json" },
|
|
619
632
|
body: JSON.stringify(body)
|
|
@@ -630,8 +643,34 @@ async function setProxyMode(mode, sessionId, timeout) {
|
|
|
630
643
|
throw error;
|
|
631
644
|
}
|
|
632
645
|
}
|
|
646
|
+
function parseSpecFilePath(specPath) {
|
|
647
|
+
const folderMatch = specPath.match(/^(.+?)\/([^/]+)\.(spec|test)\.ts$/);
|
|
648
|
+
if (folderMatch) {
|
|
649
|
+
return { folder: folderMatch[1], fileName: folderMatch[2] };
|
|
650
|
+
}
|
|
651
|
+
const fileMatch = specPath.match(/^([^/]+)\.(spec|test)\.ts$/);
|
|
652
|
+
if (fileMatch) {
|
|
653
|
+
return { folder: null, fileName: fileMatch[1] };
|
|
654
|
+
}
|
|
655
|
+
return { folder: null, fileName: null };
|
|
656
|
+
}
|
|
657
|
+
function buildSessionPath(folder, fileName, testName) {
|
|
658
|
+
if (folder && fileName) {
|
|
659
|
+
return `${folder}/${fileName}__${testName}`;
|
|
660
|
+
}
|
|
661
|
+
if (fileName) {
|
|
662
|
+
return `${fileName}__${testName}`;
|
|
663
|
+
}
|
|
664
|
+
return testName;
|
|
665
|
+
}
|
|
633
666
|
function generateSessionId(testInfo) {
|
|
634
|
-
|
|
667
|
+
const { titlePath } = testInfo;
|
|
668
|
+
if (!titlePath || titlePath.length === 0) {
|
|
669
|
+
return testInfo.title.toLowerCase().replaceAll(/\s+/g, "-");
|
|
670
|
+
}
|
|
671
|
+
const { folder, fileName } = parseSpecFilePath(titlePath[0]);
|
|
672
|
+
const testName = titlePath.at(-1).toLowerCase().replaceAll(/\s+/g, "-");
|
|
673
|
+
return buildSessionPath(folder, fileName, testName);
|
|
635
674
|
}
|
|
636
675
|
async function startRecording(testInfo) {
|
|
637
676
|
const sessionId = generateSessionId(testInfo);
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import http from 'node:http';
|
|
2
|
-
export { C as ControlRequest, M as Mode, P as PlaywrightTestInfo, R as Recording, a as RecordingSession, W as WebSocketRecording, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from './index-
|
|
2
|
+
export { C as ControlRequest, M as Mode, P as PlaywrightTestInfo, R as Recording, a as RecordingSession, W as WebSocketRecording, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from './index-CBjvm5rb.cjs';
|
|
3
3
|
import '@playwright/test';
|
|
4
4
|
|
|
5
5
|
declare class ProxyServer {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import http from 'node:http';
|
|
2
|
-
export { C as ControlRequest, M as Mode, P as PlaywrightTestInfo, R as Recording, a as RecordingSession, W as WebSocketRecording, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from './index-
|
|
2
|
+
export { C as ControlRequest, M as Mode, P as PlaywrightTestInfo, R as Recording, a as RecordingSession, W as WebSocketRecording, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from './index-CBjvm5rb.js';
|
|
3
3
|
import '@playwright/test';
|
|
4
4
|
|
|
5
5
|
declare class ProxyServer {
|
package/dist/index.mjs
CHANGED
|
@@ -24,7 +24,7 @@ var Modes = {
|
|
|
24
24
|
};
|
|
25
25
|
var JSON_INDENT_SPACES = 2;
|
|
26
26
|
function getRecordingPath(recordingsDir, id) {
|
|
27
|
-
return path.join(recordingsDir, `${id}.json`);
|
|
27
|
+
return path.join(recordingsDir, `${id}.mock.json`);
|
|
28
28
|
}
|
|
29
29
|
async function loadRecordingSession(filePath) {
|
|
30
30
|
const fileContent = await fs.readFile(filePath, "utf8");
|
|
@@ -32,6 +32,8 @@ async function loadRecordingSession(filePath) {
|
|
|
32
32
|
}
|
|
33
33
|
async function saveRecordingSession(recordingsDir, session) {
|
|
34
34
|
const filePath = getRecordingPath(recordingsDir, session.id);
|
|
35
|
+
const dirPath = path.dirname(filePath);
|
|
36
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
35
37
|
await fs.writeFile(
|
|
36
38
|
filePath,
|
|
37
39
|
JSON.stringify(session, null, JSON_INDENT_SPACES)
|
|
@@ -116,6 +118,7 @@ var ProxyServer = class {
|
|
|
116
118
|
this.handleUpgrade(req, socket, head);
|
|
117
119
|
});
|
|
118
120
|
server.listen(port, () => {
|
|
121
|
+
process.env.TEST_PROXY_RECORDER_PORT = String(port);
|
|
119
122
|
this.logServerStartup(port);
|
|
120
123
|
});
|
|
121
124
|
return server;
|
|
@@ -594,15 +597,25 @@ var ProxyServer = class {
|
|
|
594
597
|
};
|
|
595
598
|
|
|
596
599
|
// src/playwright/index.ts
|
|
597
|
-
|
|
600
|
+
function getProxyPort() {
|
|
601
|
+
const envPort = process.env.TEST_PROXY_RECORDER_PORT;
|
|
602
|
+
if (envPort) {
|
|
603
|
+
const parsed = Number.parseInt(envPort, 10);
|
|
604
|
+
if (!Number.isNaN(parsed)) {
|
|
605
|
+
return parsed;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return 8100;
|
|
609
|
+
}
|
|
598
610
|
async function setProxyMode(mode, sessionId, timeout) {
|
|
611
|
+
const proxyPort = getProxyPort();
|
|
599
612
|
try {
|
|
600
613
|
const body = {
|
|
601
614
|
mode,
|
|
602
615
|
id: sessionId,
|
|
603
616
|
...timeout && { timeout }
|
|
604
617
|
};
|
|
605
|
-
const response = await fetch(
|
|
618
|
+
const response = await fetch(`http://127.0.0.1:${proxyPort}/__control`, {
|
|
606
619
|
method: "POST",
|
|
607
620
|
headers: { "Content-Type": "application/json" },
|
|
608
621
|
body: JSON.stringify(body)
|
|
@@ -619,8 +632,34 @@ async function setProxyMode(mode, sessionId, timeout) {
|
|
|
619
632
|
throw error;
|
|
620
633
|
}
|
|
621
634
|
}
|
|
635
|
+
function parseSpecFilePath(specPath) {
|
|
636
|
+
const folderMatch = specPath.match(/^(.+?)\/([^/]+)\.(spec|test)\.ts$/);
|
|
637
|
+
if (folderMatch) {
|
|
638
|
+
return { folder: folderMatch[1], fileName: folderMatch[2] };
|
|
639
|
+
}
|
|
640
|
+
const fileMatch = specPath.match(/^([^/]+)\.(spec|test)\.ts$/);
|
|
641
|
+
if (fileMatch) {
|
|
642
|
+
return { folder: null, fileName: fileMatch[1] };
|
|
643
|
+
}
|
|
644
|
+
return { folder: null, fileName: null };
|
|
645
|
+
}
|
|
646
|
+
function buildSessionPath(folder, fileName, testName) {
|
|
647
|
+
if (folder && fileName) {
|
|
648
|
+
return `${folder}/${fileName}__${testName}`;
|
|
649
|
+
}
|
|
650
|
+
if (fileName) {
|
|
651
|
+
return `${fileName}__${testName}`;
|
|
652
|
+
}
|
|
653
|
+
return testName;
|
|
654
|
+
}
|
|
622
655
|
function generateSessionId(testInfo) {
|
|
623
|
-
|
|
656
|
+
const { titlePath } = testInfo;
|
|
657
|
+
if (!titlePath || titlePath.length === 0) {
|
|
658
|
+
return testInfo.title.toLowerCase().replaceAll(/\s+/g, "-");
|
|
659
|
+
}
|
|
660
|
+
const { folder, fileName } = parseSpecFilePath(titlePath[0]);
|
|
661
|
+
const testName = titlePath.at(-1).toLowerCase().replaceAll(/\s+/g, "-");
|
|
662
|
+
return buildSessionPath(folder, fileName, testName);
|
|
624
663
|
}
|
|
625
664
|
async function startRecording(testInfo) {
|
|
626
665
|
const sessionId = generateSessionId(testInfo);
|
|
@@ -8,15 +8,25 @@ var Modes = {
|
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
// src/playwright/index.ts
|
|
11
|
-
|
|
11
|
+
function getProxyPort() {
|
|
12
|
+
const envPort = process.env.TEST_PROXY_RECORDER_PORT;
|
|
13
|
+
if (envPort) {
|
|
14
|
+
const parsed = Number.parseInt(envPort, 10);
|
|
15
|
+
if (!Number.isNaN(parsed)) {
|
|
16
|
+
return parsed;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return 8100;
|
|
20
|
+
}
|
|
12
21
|
async function setProxyMode(mode, sessionId, timeout) {
|
|
22
|
+
const proxyPort = getProxyPort();
|
|
13
23
|
try {
|
|
14
24
|
const body = {
|
|
15
25
|
mode,
|
|
16
26
|
id: sessionId,
|
|
17
27
|
...timeout && { timeout }
|
|
18
28
|
};
|
|
19
|
-
const response = await fetch(
|
|
29
|
+
const response = await fetch(`http://127.0.0.1:${proxyPort}/__control`, {
|
|
20
30
|
method: "POST",
|
|
21
31
|
headers: { "Content-Type": "application/json" },
|
|
22
32
|
body: JSON.stringify(body)
|
|
@@ -33,8 +43,34 @@ async function setProxyMode(mode, sessionId, timeout) {
|
|
|
33
43
|
throw error;
|
|
34
44
|
}
|
|
35
45
|
}
|
|
46
|
+
function parseSpecFilePath(specPath) {
|
|
47
|
+
const folderMatch = specPath.match(/^(.+?)\/([^/]+)\.(spec|test)\.ts$/);
|
|
48
|
+
if (folderMatch) {
|
|
49
|
+
return { folder: folderMatch[1], fileName: folderMatch[2] };
|
|
50
|
+
}
|
|
51
|
+
const fileMatch = specPath.match(/^([^/]+)\.(spec|test)\.ts$/);
|
|
52
|
+
if (fileMatch) {
|
|
53
|
+
return { folder: null, fileName: fileMatch[1] };
|
|
54
|
+
}
|
|
55
|
+
return { folder: null, fileName: null };
|
|
56
|
+
}
|
|
57
|
+
function buildSessionPath(folder, fileName, testName) {
|
|
58
|
+
if (folder && fileName) {
|
|
59
|
+
return `${folder}/${fileName}__${testName}`;
|
|
60
|
+
}
|
|
61
|
+
if (fileName) {
|
|
62
|
+
return `${fileName}__${testName}`;
|
|
63
|
+
}
|
|
64
|
+
return testName;
|
|
65
|
+
}
|
|
36
66
|
function generateSessionId(testInfo) {
|
|
37
|
-
|
|
67
|
+
const { titlePath } = testInfo;
|
|
68
|
+
if (!titlePath || titlePath.length === 0) {
|
|
69
|
+
return testInfo.title.toLowerCase().replaceAll(/\s+/g, "-");
|
|
70
|
+
}
|
|
71
|
+
const { folder, fileName } = parseSpecFilePath(titlePath[0]);
|
|
72
|
+
const testName = titlePath.at(-1).toLowerCase().replaceAll(/\s+/g, "-");
|
|
73
|
+
return buildSessionPath(folder, fileName, testName);
|
|
38
74
|
}
|
|
39
75
|
async function startRecording(testInfo) {
|
|
40
76
|
const sessionId = generateSessionId(testInfo);
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import '@playwright/test';
|
|
2
|
-
export { P as PlaywrightTestInfo, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from '../index-
|
|
2
|
+
export { P as PlaywrightTestInfo, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from '../index-CBjvm5rb.cjs';
|
|
3
3
|
import 'node:http';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import '@playwright/test';
|
|
2
|
-
export { P as PlaywrightTestInfo, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from '../index-
|
|
2
|
+
export { P as PlaywrightTestInfo, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from '../index-CBjvm5rb.js';
|
|
3
3
|
import 'node:http';
|
|
@@ -6,15 +6,25 @@ var Modes = {
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
// src/playwright/index.ts
|
|
9
|
-
|
|
9
|
+
function getProxyPort() {
|
|
10
|
+
const envPort = process.env.TEST_PROXY_RECORDER_PORT;
|
|
11
|
+
if (envPort) {
|
|
12
|
+
const parsed = Number.parseInt(envPort, 10);
|
|
13
|
+
if (!Number.isNaN(parsed)) {
|
|
14
|
+
return parsed;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return 8100;
|
|
18
|
+
}
|
|
10
19
|
async function setProxyMode(mode, sessionId, timeout) {
|
|
20
|
+
const proxyPort = getProxyPort();
|
|
11
21
|
try {
|
|
12
22
|
const body = {
|
|
13
23
|
mode,
|
|
14
24
|
id: sessionId,
|
|
15
25
|
...timeout && { timeout }
|
|
16
26
|
};
|
|
17
|
-
const response = await fetch(
|
|
27
|
+
const response = await fetch(`http://127.0.0.1:${proxyPort}/__control`, {
|
|
18
28
|
method: "POST",
|
|
19
29
|
headers: { "Content-Type": "application/json" },
|
|
20
30
|
body: JSON.stringify(body)
|
|
@@ -31,8 +41,34 @@ async function setProxyMode(mode, sessionId, timeout) {
|
|
|
31
41
|
throw error;
|
|
32
42
|
}
|
|
33
43
|
}
|
|
44
|
+
function parseSpecFilePath(specPath) {
|
|
45
|
+
const folderMatch = specPath.match(/^(.+?)\/([^/]+)\.(spec|test)\.ts$/);
|
|
46
|
+
if (folderMatch) {
|
|
47
|
+
return { folder: folderMatch[1], fileName: folderMatch[2] };
|
|
48
|
+
}
|
|
49
|
+
const fileMatch = specPath.match(/^([^/]+)\.(spec|test)\.ts$/);
|
|
50
|
+
if (fileMatch) {
|
|
51
|
+
return { folder: null, fileName: fileMatch[1] };
|
|
52
|
+
}
|
|
53
|
+
return { folder: null, fileName: null };
|
|
54
|
+
}
|
|
55
|
+
function buildSessionPath(folder, fileName, testName) {
|
|
56
|
+
if (folder && fileName) {
|
|
57
|
+
return `${folder}/${fileName}__${testName}`;
|
|
58
|
+
}
|
|
59
|
+
if (fileName) {
|
|
60
|
+
return `${fileName}__${testName}`;
|
|
61
|
+
}
|
|
62
|
+
return testName;
|
|
63
|
+
}
|
|
34
64
|
function generateSessionId(testInfo) {
|
|
35
|
-
|
|
65
|
+
const { titlePath } = testInfo;
|
|
66
|
+
if (!titlePath || titlePath.length === 0) {
|
|
67
|
+
return testInfo.title.toLowerCase().replaceAll(/\s+/g, "-");
|
|
68
|
+
}
|
|
69
|
+
const { folder, fileName } = parseSpecFilePath(titlePath[0]);
|
|
70
|
+
const testName = titlePath.at(-1).toLowerCase().replaceAll(/\s+/g, "-");
|
|
71
|
+
return buildSessionPath(folder, fileName, testName);
|
|
36
72
|
}
|
|
37
73
|
async function startRecording(testInfo) {
|
|
38
74
|
const sessionId = generateSessionId(testInfo);
|
package/dist/proxy.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import path2 from 'path';
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import fs from 'fs/promises';
|
|
4
4
|
import http from 'http';
|
|
@@ -38,7 +38,7 @@ function parseCliArgs() {
|
|
|
38
38
|
if (targets2.length === 0) {
|
|
39
39
|
program.help();
|
|
40
40
|
}
|
|
41
|
-
const recordingsDir2 =
|
|
41
|
+
const recordingsDir2 = path2.resolve(process.cwd(), options.recordingsDir);
|
|
42
42
|
return { targets: targets2, port: port2, recordingsDir: recordingsDir2 };
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -58,7 +58,7 @@ var Modes = {
|
|
|
58
58
|
};
|
|
59
59
|
var JSON_INDENT_SPACES = 2;
|
|
60
60
|
function getRecordingPath(recordingsDir2, id) {
|
|
61
|
-
return
|
|
61
|
+
return path2.join(recordingsDir2, `${id}.mock.json`);
|
|
62
62
|
}
|
|
63
63
|
async function loadRecordingSession(filePath) {
|
|
64
64
|
const fileContent = await fs.readFile(filePath, "utf8");
|
|
@@ -66,6 +66,8 @@ async function loadRecordingSession(filePath) {
|
|
|
66
66
|
}
|
|
67
67
|
async function saveRecordingSession(recordingsDir2, session) {
|
|
68
68
|
const filePath = getRecordingPath(recordingsDir2, session.id);
|
|
69
|
+
const dirPath = path2.dirname(filePath);
|
|
70
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
69
71
|
await fs.writeFile(
|
|
70
72
|
filePath,
|
|
71
73
|
JSON.stringify(session, null, JSON_INDENT_SPACES)
|
|
@@ -150,6 +152,7 @@ var ProxyServer = class {
|
|
|
150
152
|
this.handleUpgrade(req, socket, head);
|
|
151
153
|
});
|
|
152
154
|
server.listen(port2, () => {
|
|
155
|
+
process.env.TEST_PROXY_RECORDER_PORT = String(port2);
|
|
153
156
|
this.logServerStartup(port2);
|
|
154
157
|
});
|
|
155
158
|
return server;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "test-proxy-recorder",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "HTTP proxy server for recording and replaying network requests in testing. Works seamlessly with Playwright testing framework.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.mjs",
|