ftmocks-utils 1.3.3 → 1.3.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 +87 -0
- package/package.json +1 -1
- package/src/common-utils.js +79 -0
- package/src/file-utils.js +73 -0
- package/src/index.js +13 -668
- package/src/match-utils.js +195 -0
- package/src/playwright-utils.js +364 -0
- package/src/react-utils.js +189 -0
- package/src/snap-utils.js +45 -0
package/README.md
CHANGED
|
@@ -1,2 +1,89 @@
|
|
|
1
1
|
# ftmocks-utils
|
|
2
|
+
|
|
2
3
|
Util functions for FtMocks
|
|
4
|
+
|
|
5
|
+
## Usage: initiatePlaywrightRoutes
|
|
6
|
+
|
|
7
|
+
`initiatePlaywrightRoutes` sets up Playwright network route mocks for your tests.
|
|
8
|
+
|
|
9
|
+
### Example Test
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
import { test, expect } from "@playwright/test";
|
|
13
|
+
import { initiatePlaywrightRoutes } from "ftmocks-utils";
|
|
14
|
+
|
|
15
|
+
test("Sample test case", async ({ page }) => {
|
|
16
|
+
// Initiate Playwright routes with custom directories and patterns
|
|
17
|
+
await initiatePlaywrightRoutes(
|
|
18
|
+
page,
|
|
19
|
+
{
|
|
20
|
+
MOCK_DIR: "../ftmocks",
|
|
21
|
+
FALLBACK_DIR: "../public",
|
|
22
|
+
},
|
|
23
|
+
"Sample test case",
|
|
24
|
+
"**/*" // Pattern(s) to intercept; you can use a string or array of patterns
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
await page.goto("https://example-test.com/");
|
|
28
|
+
|
|
29
|
+
// Now your requests will be mocked as per your ftmocks setup
|
|
30
|
+
// Add your test steps and assertions here
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Parameters**:
|
|
35
|
+
|
|
36
|
+
- `page`: Playwright page object.
|
|
37
|
+
- `options`: Object with configuration. At minimum, provide `MOCK_DIR` (required). `FALLBACK_DIR` is optional.
|
|
38
|
+
- `testName`: (string) Name of this test, so ftmocks can find the right mock data.
|
|
39
|
+
- `patterns`: (string or array) Glob patterns for requests to intercept.
|
|
40
|
+
|
|
41
|
+
Make sure your `MOCK_DIR` points to the directory where your FtMocks records are saved.
|
|
42
|
+
|
|
43
|
+
See more API documentation at [ftmocks.com](https://ftmocks.com) or in the main FtMocks repository.
|
|
44
|
+
|
|
45
|
+
## Usage: recordPlaywrightRoutes
|
|
46
|
+
|
|
47
|
+
`recordPlaywrightRoutes` allows you to record network requests and responses from a Playwright test session and save them as FtMocks mocks. This is useful for setting up new mocks or updating existing ones with actual traffic.
|
|
48
|
+
|
|
49
|
+
### Example Usage
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
import { test } from "@playwright/test";
|
|
53
|
+
import { recordPlaywrightRoutes } from "ftmocks-utils";
|
|
54
|
+
|
|
55
|
+
test("Record API interactions", async ({ page }) => {
|
|
56
|
+
await recordPlaywrightRoutes(
|
|
57
|
+
page,
|
|
58
|
+
{
|
|
59
|
+
MOCK_DIR: "../ftmocks",
|
|
60
|
+
FALLBACK_DIR: "../public",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
testName: "Recorded test",
|
|
64
|
+
mockPath: "**/*", // Intercept all requests by default
|
|
65
|
+
pattern: "^/api/.*", // Only record requests matching this regex pattern (e.g., API endpoints)
|
|
66
|
+
avoidDuplicatesInTheTest: true, // Skip duplicates within a single test recording
|
|
67
|
+
avoidDuplicatesWithDefaultMocks: true, // Skip duplicates with default mocks
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
await page.goto("https://your-app-under-test.com/");
|
|
72
|
+
// Interact with your page as needed; API requests will be recorded
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Parameters**:
|
|
77
|
+
|
|
78
|
+
- `page`: Playwright page object.
|
|
79
|
+
- `ftmocksConifg`: Object, must contain at minimum `MOCK_DIR`. `FALLBACK_DIR` is optional.
|
|
80
|
+
- `config`: Object containing recording options:
|
|
81
|
+
- `testName`: (string) Name of the test, used for saving the mock data.
|
|
82
|
+
- `mockPath`: (string|array) Glob pattern(s) for requests to intercept.
|
|
83
|
+
- `pattern`: (string) Regex string; only requests matching this will be recorded.
|
|
84
|
+
- `avoidDuplicatesInTheTest`: (boolean) Skip duplicate entries during this run.
|
|
85
|
+
- `avoidDuplicatesWithDefaultMocks`: (boolean) Skip recording if identical default mocks are present.
|
|
86
|
+
|
|
87
|
+
After running the test, FtMocks-compatible mock files will be saved to the specified folder for easy reuse.
|
|
88
|
+
|
|
89
|
+
See more API documentation and advanced usage at [ftmocks.com](https://ftmocks.com).
|
package/package.json
CHANGED
package/src/common-utils.js
CHANGED
|
@@ -134,6 +134,82 @@ const getTestByName = async (ftmocksConifg, testName) => {
|
|
|
134
134
|
}
|
|
135
135
|
};
|
|
136
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Creates a map from an array of objects using `id` property as key.
|
|
139
|
+
* @param {Array<object>} arr - The array of objects to map.
|
|
140
|
+
* @returns {Object} Map of id -> object
|
|
141
|
+
*/
|
|
142
|
+
function createIdMap(arr) {
|
|
143
|
+
if (!Array.isArray(arr)) return {};
|
|
144
|
+
return arr.reduce((acc, obj) => {
|
|
145
|
+
if (obj && obj.id !== undefined && obj.id !== null) {
|
|
146
|
+
acc[obj.id] = obj;
|
|
147
|
+
}
|
|
148
|
+
return acc;
|
|
149
|
+
}, {});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Creates a map where the key is in the format '[method]-[pathname]' and the value is the mock's id.
|
|
154
|
+
* @param {Array<object>} mocks - Array of mock objects. Each should have fileContent with method and url, and an id.
|
|
155
|
+
* @returns {Object} Map of '[method]-[pathname]' -> mock.id
|
|
156
|
+
*/
|
|
157
|
+
function createMethodPathnameIdMap(mocks) {
|
|
158
|
+
if (!Array.isArray(mocks)) return {};
|
|
159
|
+
const map = {};
|
|
160
|
+
for (const mock of mocks) {
|
|
161
|
+
if (
|
|
162
|
+
mock &&
|
|
163
|
+
mock.id !== undefined &&
|
|
164
|
+
mock.fileContent &&
|
|
165
|
+
typeof mock.fileContent.method === "string" &&
|
|
166
|
+
typeof mock.fileContent.url === "string"
|
|
167
|
+
) {
|
|
168
|
+
const urlObj = (() => {
|
|
169
|
+
try {
|
|
170
|
+
return new URL(mock.fileContent.url, "http://localhost");
|
|
171
|
+
} catch (e) {
|
|
172
|
+
// fallback: treat as path only
|
|
173
|
+
return { pathname: mock.fileContent.url };
|
|
174
|
+
}
|
|
175
|
+
})();
|
|
176
|
+
const key = `${mock.fileContent.method.toUpperCase()}-${urlObj.pathname}`;
|
|
177
|
+
if (!map[key]) {
|
|
178
|
+
map[key] = [mock.id];
|
|
179
|
+
} else {
|
|
180
|
+
map[key].push(mock.id);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return map;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Returns a unique key for the given mock in the format '[METHOD]-[pathname]'.
|
|
189
|
+
* If the URL can't be parsed, uses the raw URL as pathname.
|
|
190
|
+
* @param {object} mock - The mock object with at least fileContent.method and fileContent.url
|
|
191
|
+
* @returns {string|null} The key as '[METHOD]-[pathname]', or null if invalid input
|
|
192
|
+
*/
|
|
193
|
+
function getMockKey(options) {
|
|
194
|
+
if (
|
|
195
|
+
!options ||
|
|
196
|
+
!options.method ||
|
|
197
|
+
!options.url ||
|
|
198
|
+
typeof options.method !== "string" ||
|
|
199
|
+
typeof options.url !== "string"
|
|
200
|
+
) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
let pathname;
|
|
204
|
+
try {
|
|
205
|
+
const urlObj = new URL(options.url, "http://localhost");
|
|
206
|
+
pathname = urlObj.pathname;
|
|
207
|
+
} catch (_) {
|
|
208
|
+
pathname = options.url;
|
|
209
|
+
}
|
|
210
|
+
return `${options.method.toUpperCase()}-${pathname}`;
|
|
211
|
+
}
|
|
212
|
+
|
|
137
213
|
module.exports = {
|
|
138
214
|
charDifference,
|
|
139
215
|
nameToFolder,
|
|
@@ -146,4 +222,7 @@ module.exports = {
|
|
|
146
222
|
getHeaders,
|
|
147
223
|
countFilesInDirectory,
|
|
148
224
|
getTestByName,
|
|
225
|
+
createIdMap,
|
|
226
|
+
createMethodPathnameIdMap,
|
|
227
|
+
getMockKey,
|
|
149
228
|
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const { getMockDir, nameToFolder } = require("./common-utils");
|
|
4
|
+
|
|
5
|
+
const saveIfItIsFile = async (route, testName, ftmocksConifg) => {
|
|
6
|
+
const urlObj = new URL(route.request().url());
|
|
7
|
+
|
|
8
|
+
// Check if URL contains file extension like .js, .png, .css etc
|
|
9
|
+
const fileExtMatch = urlObj.pathname.match(/\.[a-zA-Z0-9]+$/);
|
|
10
|
+
// Check mime type if extension is not present
|
|
11
|
+
let fileExt = null;
|
|
12
|
+
if (!fileExtMatch) {
|
|
13
|
+
// Try to get extension from content-type header
|
|
14
|
+
const response = await route.fetch();
|
|
15
|
+
const contentType = response.headers()["content-type"];
|
|
16
|
+
if (contentType) {
|
|
17
|
+
// Map common mime types to extensions
|
|
18
|
+
const mimeToExt = {
|
|
19
|
+
"image/png": ".png",
|
|
20
|
+
"image/jpeg": ".jpg",
|
|
21
|
+
"image/jpg": ".jpg",
|
|
22
|
+
"image/gif": ".gif",
|
|
23
|
+
"image/webp": ".webp",
|
|
24
|
+
"image/svg+xml": ".svg",
|
|
25
|
+
"application/javascript": ".js",
|
|
26
|
+
"application/x-javascript": ".js",
|
|
27
|
+
"text/javascript": ".js",
|
|
28
|
+
"text/css": ".css",
|
|
29
|
+
"font/woff": ".woff",
|
|
30
|
+
"font/woff2": ".woff2",
|
|
31
|
+
"font/ttf": ".ttf",
|
|
32
|
+
"audio/mpeg": ".mp3",
|
|
33
|
+
"audio/wav": ".wav",
|
|
34
|
+
"video/mp4": ".mp4",
|
|
35
|
+
"application/pdf": ".pdf",
|
|
36
|
+
};
|
|
37
|
+
// Remove any charset, etc.
|
|
38
|
+
const mime = contentType.split(";")[0].trim();
|
|
39
|
+
if (mimeToExt[mime]) {
|
|
40
|
+
fileExt = mimeToExt[mime];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
fileExt = fileExtMatch[0];
|
|
45
|
+
}
|
|
46
|
+
if (fileExt) {
|
|
47
|
+
// Create directory path matching URL structure
|
|
48
|
+
const dirPath = path.join(
|
|
49
|
+
getMockDir(ftmocksConifg),
|
|
50
|
+
`test_${nameToFolder(testName)}`,
|
|
51
|
+
"_files",
|
|
52
|
+
path.dirname(urlObj.pathname)
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// Create directories if they don't exist
|
|
56
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
57
|
+
|
|
58
|
+
// Save file with original name
|
|
59
|
+
const fileName = `${id}${fileExt}`;
|
|
60
|
+
const filePath = path.join(dirPath, fileName);
|
|
61
|
+
|
|
62
|
+
const response = await route.fetch();
|
|
63
|
+
const buffer = await response.body();
|
|
64
|
+
fs.writeFileSync(filePath, buffer);
|
|
65
|
+
|
|
66
|
+
return fileName;
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
module.exports = {
|
|
72
|
+
saveIfItIsFile,
|
|
73
|
+
};
|