ftmocks-utils 1.3.3 → 1.3.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ftmocks-utils",
3
- "version": "1.3.3",
3
+ "version": "1.3.4",
4
4
  "description": "Util functions for FtMocks",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -0,0 +1,39 @@
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
+ if (fileExtMatch) {
11
+ const fileExt = fileExtMatch[0];
12
+ // Create directory path matching URL structure
13
+ const dirPath = path.join(
14
+ getMockDir(ftmocksConifg),
15
+ `test_${nameToFolder(testName)}`,
16
+ "_files",
17
+ path.dirname(urlObj.pathname)
18
+ );
19
+
20
+ // Create directories if they don't exist
21
+ fs.mkdirSync(dirPath, { recursive: true });
22
+
23
+ // Save file with original name
24
+ const fileName = path.basename(urlObj.pathname);
25
+ const filePath = path.join(dirPath, fileName);
26
+
27
+ const response = await route.fetch();
28
+ const buffer = await response.body();
29
+ fs.writeFileSync(filePath, buffer);
30
+
31
+ await route.continue();
32
+ return true;
33
+ }
34
+ return false;
35
+ };
36
+
37
+ module.exports = {
38
+ saveIfItIsFile,
39
+ };
package/src/index.js CHANGED
@@ -1,681 +1,26 @@
1
- const fs = require("fs");
2
- const path = require("path");
3
- const { v4: uuidv4 } = require("uuid");
4
- const {
5
- charDifference,
6
- nameToFolder,
7
- getMockDir,
8
- getFallbackDir,
9
- clearNulls,
10
- processURL,
11
- getHeaders,
12
- countFilesInDirectory,
13
- getTestByName,
14
- } = require("./common-utils");
1
+ const { nameToFolder, processURL, getTestByName } = require("./common-utils");
15
2
  const {
16
3
  getDefaultMockDataFromConfig,
17
4
  loadMockDataFromConfig,
18
5
  resetAllMockStats,
19
6
  } = require("./mock-utils");
20
- const { createTest } = require("./test-utils");
21
- const { FtJSON } = require("./json-utils");
22
- const { Logger, deleteAllLogs } = require("./log-utils");
7
+ const { deleteAllLogs } = require("./log-utils");
23
8
  const {
24
9
  isSameRequest,
25
10
  compareMockToRequest,
26
11
  compareMockToFetchRequest,
27
- compareMockToMock,
28
12
  } = require("./compare-utils");
29
- const { getCompareRankMockToFetchRequest } = require("./rank-compare-utils");
30
-
31
- let logger = null;
32
-
33
- // src/index.js
34
- function getMatchingMockData({
35
- testMockData,
36
- defaultMockData,
37
- url,
38
- options,
39
- testConfig,
40
- testName,
41
- mode,
42
- }) {
43
- let served = false;
44
- let matchedMocks =
45
- testMockData?.filter((mock) => {
46
- if (mock.fileContent.waitForPrevious && !served) {
47
- return false;
48
- }
49
- served = mock.fileContent.served;
50
- return compareMockToFetchRequest(mock, { url, options });
51
- }) || [];
52
- let foundMock = matchedMocks.find((mock) => !mock.fileContent.served)
53
- ? matchedMocks.find((mock) => !mock.fileContent.served)
54
- : matchedMocks[matchedMocks.length - 1];
55
-
56
- if (!foundMock) {
57
- foundMock = defaultMockData.find((tm) =>
58
- compareMockToFetchRequest(tm, {
59
- url,
60
- options,
61
- })
62
- );
63
- }
64
-
65
- if (!foundMock && mode !== "strict") {
66
- const mockRanks = {};
67
- testMockData.forEach((tm) => {
68
- const rank = getCompareRankMockToFetchRequest(tm, {
69
- url,
70
- options,
71
- });
72
- if (rank > 0) {
73
- mockRanks[tm.id] = rank;
74
- }
75
- });
76
- defaultMockData.forEach((tm) => {
77
- const rank = getCompareRankMockToFetchRequest(tm, {
78
- url,
79
- options,
80
- });
81
- if (rank > 0) {
82
- mockRanks[tm.id] = rank;
83
- }
84
- });
85
- // Sort by rank to find the best match
86
- const sortedRanks = Object.entries(mockRanks).sort((a, b) => a[1] - b[1]);
87
- if (sortedRanks.length > 0) {
88
- const bestMockId = sortedRanks?.[0]?.[0];
89
- if (bestMockId) {
90
- foundMock = [...testMockData, ...defaultMockData].find(
91
- (mock) => mock.id === bestMockId
92
- );
93
- }
94
- }
95
- }
96
- // updating stats to mock file
97
- if (foundMock) {
98
- let mockFilePath = path.join(
99
- getMockDir(testConfig),
100
- `test_${nameToFolder(testName)}`,
101
- `mock_${foundMock.id}.json`
102
- );
103
- if (!fs.existsSync(mockFilePath)) {
104
- mockFilePath = path.join(
105
- getMockDir(testConfig),
106
- "defaultMocks",
107
- `mock_${foundMock.id}.json`
108
- );
109
- }
110
- foundMock.fileContent.served = true;
111
- fs.writeFileSync(
112
- mockFilePath,
113
- JSON.stringify(foundMock.fileContent, null, 2)
114
- );
115
- }
116
- return foundMock ? foundMock.fileContent : null;
117
- }
118
-
119
- async function initiatePlaywrightRoutes(
120
- page,
121
- ftmocksConifg,
122
- testName,
123
- mockPath = "**/*",
124
- excludeMockPath = null
125
- ) {
126
- logger = new Logger(
127
- { disableLogs: ftmocksConifg.DISABLE_LOGS },
128
- ftmocksConifg,
129
- testName
130
- );
131
- const testMockData = testName
132
- ? loadMockDataFromConfig(ftmocksConifg, testName)
133
- : [];
134
- resetAllMockStats({ testMockData, testConfig: ftmocksConifg, testName });
135
- const test = await getTestByName(ftmocksConifg, testName);
136
- const defaultMockData = getDefaultMockDataFromConfig(ftmocksConifg);
137
- logger.debug("\x1b[32mcalling initiatePlaywrightRoutes fetch\x1b[0m");
138
- let firstUrl = null;
139
- await page.route(mockPath, async (route, request) => {
140
- const url = request.url();
141
- if (!firstUrl) {
142
- firstUrl = url;
143
- }
144
- const options = {
145
- url,
146
- method: request.method(),
147
- body: request.postData(),
148
- };
149
- if (excludeMockPath && new RegExp(excludeMockPath).test(url)) {
150
- await route.fallback();
151
- return;
152
- }
153
- let mockData = getMatchingMockData({
154
- testMockData,
155
- defaultMockData,
156
- url,
157
- options,
158
- testConfig: ftmocksConifg,
159
- testName,
160
- mode: test.mode || "loose",
161
- });
162
- try {
163
- if (mockData) {
164
- const { content, headers, status, file } = mockData.response;
165
-
166
- const json = {
167
- status,
168
- headers: getHeaders(headers),
169
- body: content,
170
- };
171
-
172
- if (file) {
173
- let filePath = path.join(
174
- getMockDir(ftmocksConifg),
175
- `test_${nameToFolder(testName)}`,
176
- "_files",
177
- file
178
- );
179
- if (!fs.existsSync(filePath)) {
180
- filePath = path.join(
181
- getMockDir(ftmocksConifg),
182
- "defaultMocks",
183
- "_files",
184
- file
185
- );
186
- }
187
- if (fs.existsSync(filePath) && fs.lstatSync(filePath).isFile()) {
188
- const fileContent = fs.readFileSync(filePath);
189
- json.body = fileContent;
190
-
191
- console.debug(
192
- "\x1b[32mresponse is a file, serving file\x1b[0m",
193
- filePath,
194
- url
195
- );
196
- await route.fulfill(json);
197
- }
198
- } else {
199
- await route.fulfill(json);
200
- }
201
- } else {
202
- const fallbackDir = getFallbackDir(ftmocksConifg);
203
- if (!fallbackDir) {
204
- await route.fallback();
205
- return;
206
- }
207
- const urlObj = new URL(route.request().url());
208
- let filePath = path.join(
209
- fallbackDir,
210
- urlObj.pathname === "/" || urlObj.pathname === ""
211
- ? ftmocksConifg.FALLBACK_DIR_INDEX_FILE || "index.html"
212
- : urlObj.pathname
213
- );
214
-
215
- if (
216
- !fs.existsSync(filePath) &&
217
- !path.extname(filePath) &&
218
- url === firstUrl
219
- ) {
220
- filePath = path.join(
221
- fallbackDir,
222
- ftmocksConifg.FALLBACK_DIR_INDEX_FILE_FOR_STATUS_404 || "index.html"
223
- );
224
- logger.debug(
225
- "\x1b[32mserving file for status 404\x1b[0m",
226
- filePath,
227
- url
228
- );
229
- }
230
- if (fs.existsSync(filePath) && fs.lstatSync(filePath).isFile()) {
231
- const fileContent = fs.readFileSync(filePath);
232
- const ext = path.extname(filePath);
233
- const contentType =
234
- {
235
- ".html": "text/html",
236
- ".htm": "text/html",
237
- ".xhtml": "application/xhtml+xml",
238
- ".css": "text/css",
239
- ".js": "application/javascript",
240
- ".json": "application/json",
241
- ".png": "image/png",
242
- ".jpg": "image/jpeg",
243
- ".svg": "image/svg+xml",
244
- ".ico": "image/x-icon",
245
- ".webp": "image/webp",
246
- ".mp4": "video/mp4",
247
- ".mp3": "audio/mpeg",
248
- ".wav": "audio/wav",
249
- ".ogg": "audio/ogg",
250
- ".pdf": "application/pdf",
251
- ".doc": "application/msword",
252
- ".docx":
253
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
254
- ".xls": "application/vnd.ms-excel",
255
- ".xlsx":
256
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
257
- ".ppt": "application/vnd.ms-powerpoint",
258
- ".pptx":
259
- "application/vnd.openxmlformats-officedocument.presentationml.presentation",
260
- ".zip": "application/zip",
261
- ".rar": "application/x-rar-compressed",
262
- ".7z": "application/x-7z-compressed",
263
- ".tar": "application/x-tar",
264
- ".gz": "application/gzip",
265
- ".bz2": "application/x-bzip2",
266
- ".xz": "application/x-xz",
267
- ".exe": "application/x-msdownload",
268
- ".dll": "application/x-msdownload",
269
- ".so": "application/x-sharedlib",
270
- ".dylib": "application/x-dynamiclib",
271
- ".bin": "application/octet-stream",
272
- ".txt": "text/plain",
273
- ".csv": "text/csv",
274
- ".tsv": "text/tab-separated-values",
275
- ".xml": "application/xml",
276
- ".xsl": "application/xml",
277
- ".xslt": "application/xml",
278
- ".xlt": "application/xml",
279
- ".xltx": "application/xml",
280
- ".xltm": "application/xml",
281
- ".yaml": "text/yaml",
282
- ".yml": "text/yaml",
283
- ".toml": "text/toml",
284
- ".php": "application/x-httpd-php",
285
- }[ext] || "application/octet-stream";
286
-
287
- logger.info("\x1b[32mserving file\x1b[0m", filePath);
288
- await route.fulfill({
289
- body: fileContent,
290
- headers: { "Content-Type": contentType },
291
- });
292
- } else {
293
- logger.debug("\x1b[31mmissing mock data, falling back\x1b[0m", url);
294
- await route.fallback();
295
- }
296
- }
297
- } catch (e) {
298
- logger.error(e);
299
- logger.error(
300
- "\x1b[31merror at initiatePlaywrightRoutes\x1b[0m",
301
- url,
302
- options
303
- );
304
- }
305
- });
306
- }
307
-
308
- async function initiateJestFetch(jest, ftmocksConifg, testName) {
309
- const testMockData = testName
310
- ? loadMockDataFromConfig(ftmocksConifg, testName)
311
- : [];
312
- resetAllMockStats({ testMockData, testConfig: ftmocksConifg, testName });
313
- const defaultMockData = getDefaultMockDataFromConfig(ftmocksConifg);
314
- console.debug("calling initiateJestFetch fetch");
315
- global.fetch = jest.fn((url, options = {}) => {
316
- console.debug("got fetch request", url, options);
317
- let mockData = getMatchingMockData({
318
- testMockData,
319
- defaultMockData,
320
- url,
321
- options,
322
- testConfig: ftmocksConifg,
323
- testName,
324
- });
325
- if (mockData) {
326
- console.debug("mocked", url, options);
327
- } else {
328
- console.debug("missing mock data", url, options);
329
- return Promise.resolve({
330
- status: 404,
331
- headers: new Map([["Content-Type", "application/json"]]),
332
- json: () => Promise.resolve({ error: "Mock data not found" }),
333
- });
334
- }
335
-
336
- const { content, headers, status } = mockData.response;
337
-
338
- return Promise.resolve({
339
- status,
340
- headers: new Map(Object.entries(headers)),
341
- json: () => Promise.resolve(FtJSON.parse(content)),
342
- });
343
- });
344
-
345
- console.debug("calling XMLHttpRequest fetch");
346
- global.XMLHttpRequest = jest.fn(function () {
347
- const xhrMock = {
348
- open: jest.fn(),
349
- send: jest.fn(),
350
- setRequestHeader: jest.fn(),
351
- getAllResponseHeaders: jest.fn(() => {
352
- return "";
353
- }),
354
- getResponseHeader: jest.fn((header) => {
355
- return null;
356
- }),
357
- readyState: 4,
358
- status: 0,
359
- response: null,
360
- responseText: "",
361
- headers: new Map(Object.entries(headers)),
362
- onreadystatechange: null,
363
- onload: null,
364
- onerror: null,
365
- };
366
-
367
- xhrMock.send.mockImplementation(function () {
368
- const mockData = getMatchingMockData({
369
- testMockData,
370
- defaultMockData,
371
- url: xhrMock._url,
372
- options: xhrMock._options,
373
- testConfig: ftmocksConifg,
374
- testName,
375
- });
376
-
377
- if (mockData) {
378
- console.debug("mocked", xhrMock._url, xhrMock._options);
379
- const { content, headers, status } = mockData.response;
380
-
381
- xhrMock.status = status;
382
- xhrMock.responseText = content;
383
- xhrMock.response = content;
384
- xhrMock.headers = new Map(Object.entries(headers));
385
-
386
- if (xhrMock.onreadystatechange) {
387
- xhrMock.onreadystatechange();
388
- }
389
- if (xhrMock.onload) {
390
- xhrMock.onload();
391
- }
392
- } else {
393
- console.debug("missing mock data", xhrMock._url, xhrMock._options);
394
-
395
- xhrMock.status = 404;
396
- xhrMock.responseText = JSON.stringify({ error: "Mock data not found" });
397
- xhrMock.response = xhrMock.responseText;
398
-
399
- if (xhrMock.onreadystatechange) {
400
- xhrMock.onreadystatechange();
401
- }
402
- if (xhrMock.onerror) {
403
- xhrMock.onerror();
404
- }
405
- }
406
- });
407
-
408
- xhrMock.open.mockImplementation(function (method, url) {
409
- xhrMock._options = { method };
410
- xhrMock._url = url;
411
- });
412
-
413
- return xhrMock;
414
- });
415
-
416
- return;
417
- }
418
-
419
- function initiateConsoleLogs(jest, ftmocksConifg, testName) {
420
- const logsFile = path.join(
421
- getMockDir(ftmocksConifg),
422
- `test_${nameToFolder(testName)}`,
423
- "_logs.json"
424
- );
425
- let logs = [];
426
- if (!fs.existsSync(logsFile)) {
427
- fs.appendFileSync(logsFile, "[]", "utf8");
428
- } else {
429
- fs.writeFileSync(logsFile, "[]", "utf8");
430
- }
431
-
432
- const writeToFile = (type, params) => {
433
- const logMessage = params.join(" ") + "\n"; // Combine params into a string with spaces
434
- logs.push({
435
- type,
436
- message: logMessage,
437
- time: Date.now(),
438
- });
439
- fs.writeFileSync(logsFile, JSON.stringify(logs, null, 2), "utf8"); // Append the log message to the file
440
- };
441
-
442
- global.console = {
443
- ...console,
444
- // uncomment to ignore a specific log level
445
- log: jest.fn((...params) => {
446
- writeToFile("log", params);
447
- }),
448
- debug: jest.fn((...params) => {
449
- writeToFile("debug", params);
450
- }),
451
- info: jest.fn((...params) => {
452
- writeToFile("info", params);
453
- }),
454
- warn: jest.fn((...params) => {
455
- writeToFile("warn", params);
456
- }),
457
- error: jest.fn((...params) => {
458
- writeToFile("error", params);
459
- }),
460
- };
461
- }
462
-
463
- const saveSnap = async (html, ftmocksConifg, testName) => {
464
- const snapFolder = path.join(
465
- getMockDir(ftmocksConifg),
466
- `test_${nameToFolder(testName)}`,
467
- "_snaps"
468
- );
469
- const snapTemplate = path.join(
470
- getMockDir(ftmocksConifg),
471
- "snap_template.html"
472
- );
473
-
474
- if (!fs.existsSync(snapFolder)) {
475
- fs.mkdirSync(snapFolder);
476
- }
477
- const fileCount = await countFilesInDirectory(snapFolder);
478
- const snapFilePath = path.join(snapFolder, `snap_${fileCount + 1}.html`);
479
- let resHtml = html;
480
- if (fs.existsSync(snapFolder)) {
481
- const templateHtml = fs.readFileSync(snapTemplate, "utf8");
482
- resHtml = templateHtml.replace(
483
- "<!--FtMocks-Snap-Template-To-Be-Replaced-->",
484
- html
485
- );
486
- }
487
- fs.writeFileSync(snapFilePath, resHtml);
488
- };
489
-
490
- const deleteAllSnaps = async (ftmocksConifg, testName) => {
491
- const snapFolder = path.join(
492
- getMockDir(ftmocksConifg),
493
- `test_${nameToFolder(testName)}`,
494
- "_snaps"
495
- );
496
- fs.rmSync(snapFolder, { recursive: true, force: true });
497
- };
498
-
499
- function initiateJestEventSnaps(jest, ftmocksConifg, testName) {
500
- const mouseEvents = ftmocksConifg.snapEvents || [
501
- "click",
502
- "change",
503
- "url",
504
- "dblclick",
505
- "contextmenu",
506
- ];
507
- mouseEvents.forEach((event) => {
508
- jest
509
- .spyOn(document, "addEventListener")
510
- .mockImplementation((e, callback) => {
511
- if (mouseEvents.includes(e)) {
512
- saveSnap(document.outerHTML, ftmocksConifg, testName);
513
- }
514
- });
515
- });
516
- }
517
-
518
- const saveIfItIsFile = async (route, testName, ftmocksConifg) => {
519
- const urlObj = new URL(route.request().url());
520
-
521
- // Check if URL contains file extension like .js, .png, .css etc
522
- const fileExtMatch = urlObj.pathname.match(/\.[a-zA-Z0-9]+$/);
523
- if (fileExtMatch) {
524
- const fileExt = fileExtMatch[0];
525
- // Create directory path matching URL structure
526
- const dirPath = path.join(
527
- getMockDir(ftmocksConifg),
528
- `test_${nameToFolder(testName)}`,
529
- "_files",
530
- path.dirname(urlObj.pathname)
531
- );
532
-
533
- // Create directories if they don't exist
534
- fs.mkdirSync(dirPath, { recursive: true });
535
-
536
- // Save file with original name
537
- const fileName = path.basename(urlObj.pathname);
538
- const filePath = path.join(dirPath, fileName);
539
-
540
- const response = await route.fetch();
541
- const buffer = await response.body();
542
- fs.writeFileSync(filePath, buffer);
543
-
544
- await route.continue();
545
- return true;
546
- }
547
- return false;
548
- };
549
-
550
- async function recordPlaywrightRoutes(
551
- page,
552
- ftmocksConifg,
553
- config = {
554
- testName,
555
- mockPath: "**/*",
556
- pattern: "^/api/.*",
557
- avoidDuplicatesInTheTest: false,
558
- avoidDuplicatesWithDefaultMocks: false,
559
- }
560
- ) {
561
- await page.route(config.mockPath, async (route) => {
562
- try {
563
- const urlObj = new URL(route.request().url());
564
- if (config.pattern && config.pattern.length > 0) {
565
- const patternRegex = new RegExp(config.pattern);
566
- if (!patternRegex.test(urlObj.pathname)) {
567
- await route.continue();
568
- return;
569
- }
570
- }
571
-
572
- const test = await getTestByName(ftmocksConifg, config.testName);
573
- if (!test) {
574
- await createTest(ftmocksConifg, config.testName);
575
- }
576
-
577
- if (await saveIfItIsFile(route, config.testName, ftmocksConifg)) {
578
- return;
579
- }
580
-
581
- const mockData = {
582
- url: urlObj.pathname + urlObj.search,
583
- time: new Date().toString(),
584
- method: route.request().method(),
585
- request: {
586
- headers: await route.request().headers(),
587
- queryString: Array.from(urlObj.searchParams.entries()).map(
588
- ([name, value]) => ({
589
- name,
590
- value,
591
- })
592
- ),
593
- postData: route.request().postData()
594
- ? {
595
- mimeType: "application/json",
596
- text: route.request().postData(),
597
- }
598
- : null,
599
- },
600
- response: {
601
- status: (await route.fetch()).status(),
602
- headers: (await route.fetch()).headers(),
603
- content: await (await route.fetch()).text(),
604
- },
605
- id: crypto.randomUUID(),
606
- served: false,
607
- ignoreParams: ftmocksConifg.ignoreParams || [],
608
- };
609
-
610
- await createTest(ftmocksConifg, config.testName);
611
- if (config.avoidDuplicatesInTheTest) {
612
- // Check if the mock data is a duplicate of a mock data in the test
613
- const testMockList = loadMockDataFromConfig(
614
- ftmocksConifg,
615
- config.testName
616
- );
617
- const matchResponse = testMockList.find((mock) =>
618
- compareMockToMock(mock.fileContent, mockData, true)
619
- );
620
- if (matchResponse) {
621
- console.log("Aborting duplicate mock data in the test");
622
- await route.continue();
623
- return;
624
- }
625
- }
626
-
627
- if (config.avoidDuplicatesWithDefaultMocks) {
628
- // Check if the mock data is a duplicate of a mock data in the test
629
- const defaultMockList = getDefaultMockDataFromConfig(ftmocksConifg);
630
- const matchResponse = defaultMockList.find((mock) =>
631
- compareMockToMock(mock.fileContent, mockData, true)
632
- );
633
- if (matchResponse) {
634
- console.log("Aborting duplicate mock data with default mocks");
635
- await route.continue();
636
- return;
637
- }
638
- }
639
-
640
- // Save the mock data to the test
641
- const mockListPath = path.join(
642
- getMockDir(ftmocksConifg),
643
- `test_${nameToFolder(config.testName)}`,
644
- "_mock_list.json"
645
- );
646
- let mockList = [];
647
- if (fs.existsSync(mockListPath)) {
648
- mockList = JSON.parse(fs.readFileSync(mockListPath, "utf8"));
649
- }
650
- mockList.push({
651
- id: mockData.id,
652
- url: mockData.url,
653
- method: mockData.method,
654
- time: mockData.time,
655
- });
656
-
657
- // Create test directory if it doesn't exist
658
- const testDir = path.join(
659
- getMockDir(ftmocksConifg),
660
- `test_${nameToFolder(config.testName)}`
661
- );
662
- if (!fs.existsSync(testDir)) {
663
- fs.mkdirSync(testDir, { recursive: true });
664
- }
665
- fs.writeFileSync(mockListPath, JSON.stringify(mockList, null, 2));
666
- const mocDataPath = path.join(
667
- getMockDir(ftmocksConifg),
668
- `test_${nameToFolder(config.testName)}`,
669
- `mock_${mockData.id}.json`
670
- );
671
- fs.writeFileSync(mocDataPath, JSON.stringify(mockData, null, 2));
672
- await route.continue();
673
- } catch (error) {
674
- console.error(error);
675
- await route.continue();
676
- }
677
- });
678
- }
13
+ const { getMatchingMockData } = require("./match-utils");
14
+ const {
15
+ initiateJestFetch,
16
+ initiateConsoleLogs,
17
+ initiateJestEventSnaps,
18
+ } = require("./react-utils");
19
+ const {
20
+ initiatePlaywrightRoutes,
21
+ recordPlaywrightRoutes,
22
+ } = require("./playwright-utils");
23
+ const { saveSnap, deleteAllSnaps } = require("./snap-utils");
679
24
 
680
25
  // Export functions as a module
681
26
  module.exports = {
@@ -0,0 +1,94 @@
1
+ const { compareMockToFetchRequest } = require("./compare-utils");
2
+ const { getCompareRankMockToFetchRequest } = require("./rank-compare-utils");
3
+ const { getMockDir, nameToFolder } = require("./common-utils");
4
+ const path = require("path");
5
+ const fs = require("fs");
6
+
7
+ function getMatchingMockData({
8
+ testMockData,
9
+ defaultMockData,
10
+ url,
11
+ options,
12
+ testConfig,
13
+ testName,
14
+ mode,
15
+ }) {
16
+ let served = false;
17
+ let matchedMocks =
18
+ testMockData?.filter((mock) => {
19
+ if (mock.fileContent.waitForPrevious && !served) {
20
+ return false;
21
+ }
22
+ served = mock.fileContent.served;
23
+ return compareMockToFetchRequest(mock, { url, options });
24
+ }) || [];
25
+ let foundMock = matchedMocks.find((mock) => !mock.fileContent.served)
26
+ ? matchedMocks.find((mock) => !mock.fileContent.served)
27
+ : matchedMocks[matchedMocks.length - 1];
28
+
29
+ if (!foundMock) {
30
+ foundMock = defaultMockData.find((tm) =>
31
+ compareMockToFetchRequest(tm, {
32
+ url,
33
+ options,
34
+ })
35
+ );
36
+ }
37
+
38
+ if (!foundMock && mode !== "strict") {
39
+ const mockRanks = {};
40
+ testMockData.forEach((tm) => {
41
+ const rank = getCompareRankMockToFetchRequest(tm, {
42
+ url,
43
+ options,
44
+ });
45
+ if (rank > 0) {
46
+ mockRanks[tm.id] = rank;
47
+ }
48
+ });
49
+ defaultMockData.forEach((tm) => {
50
+ const rank = getCompareRankMockToFetchRequest(tm, {
51
+ url,
52
+ options,
53
+ });
54
+ if (rank > 0) {
55
+ mockRanks[tm.id] = rank;
56
+ }
57
+ });
58
+ // Sort by rank to find the best match
59
+ const sortedRanks = Object.entries(mockRanks).sort((a, b) => a[1] - b[1]);
60
+ if (sortedRanks.length > 0) {
61
+ const bestMockId = sortedRanks?.[0]?.[0];
62
+ if (bestMockId) {
63
+ foundMock = [...testMockData, ...defaultMockData].find(
64
+ (mock) => mock.id === bestMockId
65
+ );
66
+ }
67
+ }
68
+ }
69
+ // updating stats to mock file
70
+ if (foundMock) {
71
+ let mockFilePath = path.join(
72
+ getMockDir(testConfig),
73
+ `test_${nameToFolder(testName)}`,
74
+ `mock_${foundMock.id}.json`
75
+ );
76
+ if (!fs.existsSync(mockFilePath)) {
77
+ mockFilePath = path.join(
78
+ getMockDir(testConfig),
79
+ "defaultMocks",
80
+ `mock_${foundMock.id}.json`
81
+ );
82
+ }
83
+ foundMock.fileContent.served = true;
84
+ fs.writeFileSync(
85
+ mockFilePath,
86
+ JSON.stringify(foundMock.fileContent, null, 2)
87
+ );
88
+ }
89
+ return foundMock ? foundMock.fileContent : null;
90
+ }
91
+
92
+ module.exports = {
93
+ getMatchingMockData,
94
+ };
@@ -0,0 +1,340 @@
1
+ const { getMatchingMockData } = require("./match-utils");
2
+ const { getMockDir, nameToFolder, getHeaders } = require("./common-utils");
3
+ const { getFallbackDir } = require("./common-utils");
4
+ const { getTestByName } = require("./common-utils");
5
+ const { compareMockToMock } = require("./compare-utils");
6
+ const { loadMockDataFromConfig } = require("./mock-utils");
7
+ const { resetAllMockStats } = require("./mock-utils");
8
+ const { getDefaultMockDataFromConfig } = require("./mock-utils");
9
+ const { Logger } = require("./log-utils");
10
+ const { saveIfItIsFile } = require("./file-utils");
11
+ const { createTest } = require("./test-utils");
12
+ const path = require("path");
13
+ const crypto = require("crypto");
14
+ const fs = require("fs");
15
+
16
+ let logger = null;
17
+
18
+ async function initiatePlaywrightRoutes(
19
+ page,
20
+ ftmocksConifg,
21
+ testName,
22
+ mockPath = "**/*",
23
+ excludeMockPath = null
24
+ ) {
25
+ logger = new Logger(
26
+ { disableLogs: ftmocksConifg.DISABLE_LOGS },
27
+ ftmocksConifg,
28
+ testName
29
+ );
30
+ const testMockData = testName
31
+ ? loadMockDataFromConfig(ftmocksConifg, testName)
32
+ : [];
33
+ resetAllMockStats({ testMockData, testConfig: ftmocksConifg, testName });
34
+ const test = await getTestByName(ftmocksConifg, testName);
35
+ const defaultMockData = getDefaultMockDataFromConfig(ftmocksConifg);
36
+ logger.debug("\x1b[32mcalling initiatePlaywrightRoutes fetch\x1b[0m");
37
+ let firstUrl = null;
38
+ await page.route(mockPath, async (route, request) => {
39
+ const url = request.url();
40
+ if (!firstUrl) {
41
+ firstUrl = url;
42
+ }
43
+ const options = {
44
+ url,
45
+ method: request.method(),
46
+ body: request.postData(),
47
+ };
48
+ if (excludeMockPath && new RegExp(excludeMockPath).test(url)) {
49
+ await route.fallback();
50
+ return;
51
+ }
52
+ let mockData = getMatchingMockData({
53
+ testMockData,
54
+ defaultMockData,
55
+ url,
56
+ options,
57
+ testConfig: ftmocksConifg,
58
+ testName,
59
+ mode: test.mode || "loose",
60
+ });
61
+ try {
62
+ if (mockData) {
63
+ const { content, headers, status, file } = mockData.response;
64
+
65
+ const json = {
66
+ status,
67
+ headers: getHeaders(headers),
68
+ body: content,
69
+ };
70
+
71
+ if (file) {
72
+ let filePath = path.join(
73
+ getMockDir(ftmocksConifg),
74
+ `test_${nameToFolder(testName)}`,
75
+ "_files",
76
+ file
77
+ );
78
+ if (!fs.existsSync(filePath)) {
79
+ filePath = path.join(
80
+ getMockDir(ftmocksConifg),
81
+ "defaultMocks",
82
+ "_files",
83
+ file
84
+ );
85
+ }
86
+ if (fs.existsSync(filePath) && fs.lstatSync(filePath).isFile()) {
87
+ const fileContent = fs.readFileSync(filePath);
88
+ json.body = fileContent;
89
+
90
+ console.debug(
91
+ "\x1b[32mresponse is a file, serving file\x1b[0m",
92
+ filePath,
93
+ url
94
+ );
95
+ await route.fulfill(json);
96
+ }
97
+ } else {
98
+ await route.fulfill(json);
99
+ }
100
+ } else {
101
+ const fallbackDir = getFallbackDir(ftmocksConifg);
102
+ if (!fallbackDir) {
103
+ await route.fallback();
104
+ return;
105
+ }
106
+ const urlObj = new URL(route.request().url());
107
+ let filePath = path.join(
108
+ fallbackDir,
109
+ urlObj.pathname === "/" || urlObj.pathname === ""
110
+ ? ftmocksConifg.FALLBACK_DIR_INDEX_FILE || "index.html"
111
+ : urlObj.pathname
112
+ );
113
+
114
+ if (
115
+ !fs.existsSync(filePath) &&
116
+ !path.extname(filePath) &&
117
+ url === firstUrl
118
+ ) {
119
+ filePath = path.join(
120
+ fallbackDir,
121
+ ftmocksConifg.FALLBACK_DIR_INDEX_FILE_FOR_STATUS_404 || "index.html"
122
+ );
123
+ logger.debug(
124
+ "\x1b[32mserving file for status 404\x1b[0m",
125
+ filePath,
126
+ url
127
+ );
128
+ }
129
+ if (fs.existsSync(filePath) && fs.lstatSync(filePath).isFile()) {
130
+ const fileContent = fs.readFileSync(filePath);
131
+ const ext = path.extname(filePath);
132
+ const contentType =
133
+ {
134
+ ".html": "text/html",
135
+ ".htm": "text/html",
136
+ ".xhtml": "application/xhtml+xml",
137
+ ".css": "text/css",
138
+ ".js": "application/javascript",
139
+ ".json": "application/json",
140
+ ".png": "image/png",
141
+ ".jpg": "image/jpeg",
142
+ ".svg": "image/svg+xml",
143
+ ".ico": "image/x-icon",
144
+ ".webp": "image/webp",
145
+ ".mp4": "video/mp4",
146
+ ".mp3": "audio/mpeg",
147
+ ".wav": "audio/wav",
148
+ ".ogg": "audio/ogg",
149
+ ".pdf": "application/pdf",
150
+ ".doc": "application/msword",
151
+ ".docx":
152
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
153
+ ".xls": "application/vnd.ms-excel",
154
+ ".xlsx":
155
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
156
+ ".ppt": "application/vnd.ms-powerpoint",
157
+ ".pptx":
158
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
159
+ ".zip": "application/zip",
160
+ ".rar": "application/x-rar-compressed",
161
+ ".7z": "application/x-7z-compressed",
162
+ ".tar": "application/x-tar",
163
+ ".gz": "application/gzip",
164
+ ".bz2": "application/x-bzip2",
165
+ ".xz": "application/x-xz",
166
+ ".exe": "application/x-msdownload",
167
+ ".dll": "application/x-msdownload",
168
+ ".so": "application/x-sharedlib",
169
+ ".dylib": "application/x-dynamiclib",
170
+ ".bin": "application/octet-stream",
171
+ ".txt": "text/plain",
172
+ ".csv": "text/csv",
173
+ ".tsv": "text/tab-separated-values",
174
+ ".xml": "application/xml",
175
+ ".xsl": "application/xml",
176
+ ".xslt": "application/xml",
177
+ ".xlt": "application/xml",
178
+ ".xltx": "application/xml",
179
+ ".xltm": "application/xml",
180
+ ".yaml": "text/yaml",
181
+ ".yml": "text/yaml",
182
+ ".toml": "text/toml",
183
+ ".php": "application/x-httpd-php",
184
+ }[ext] || "application/octet-stream";
185
+
186
+ logger.info("\x1b[32mserving file\x1b[0m", filePath);
187
+ await route.fulfill({
188
+ body: fileContent,
189
+ headers: { "Content-Type": contentType },
190
+ });
191
+ } else {
192
+ logger.debug("\x1b[31mmissing mock data, falling back\x1b[0m", url);
193
+ await route.fallback();
194
+ }
195
+ }
196
+ } catch (e) {
197
+ logger.error(e);
198
+ logger.error(
199
+ "\x1b[31merror at initiatePlaywrightRoutes\x1b[0m",
200
+ url,
201
+ options
202
+ );
203
+ }
204
+ });
205
+ }
206
+
207
+ async function recordPlaywrightRoutes(
208
+ page,
209
+ ftmocksConifg,
210
+ config = {
211
+ testName,
212
+ mockPath: "**/*",
213
+ pattern: "^/api/.*",
214
+ avoidDuplicatesInTheTest: false,
215
+ avoidDuplicatesWithDefaultMocks: false,
216
+ }
217
+ ) {
218
+ await page.route(config.mockPath, async (route) => {
219
+ try {
220
+ const urlObj = new URL(route.request().url());
221
+ if (config.pattern && config.pattern.length > 0) {
222
+ const patternRegex = new RegExp(config.pattern);
223
+ if (!patternRegex.test(urlObj.pathname)) {
224
+ await route.continue();
225
+ return;
226
+ }
227
+ }
228
+
229
+ const test = await getTestByName(ftmocksConifg, config.testName);
230
+ if (!test) {
231
+ await createTest(ftmocksConifg, config.testName);
232
+ }
233
+
234
+ if (await saveIfItIsFile(route, config.testName, ftmocksConifg)) {
235
+ return;
236
+ }
237
+
238
+ const mockData = {
239
+ url: urlObj.pathname + urlObj.search,
240
+ time: new Date().toString(),
241
+ method: route.request().method(),
242
+ request: {
243
+ headers: await route.request().headers(),
244
+ queryString: Array.from(urlObj.searchParams.entries()).map(
245
+ ([name, value]) => ({
246
+ name,
247
+ value,
248
+ })
249
+ ),
250
+ postData: route.request().postData()
251
+ ? {
252
+ mimeType: "application/json",
253
+ text: route.request().postData(),
254
+ }
255
+ : null,
256
+ },
257
+ response: {
258
+ status: (await route.fetch()).status(),
259
+ headers: (await route.fetch()).headers(),
260
+ content: await (await route.fetch()).text(),
261
+ },
262
+ id: crypto.randomUUID(),
263
+ served: false,
264
+ ignoreParams: ftmocksConifg.ignoreParams || [],
265
+ };
266
+
267
+ await createTest(ftmocksConifg, config.testName);
268
+ if (config.avoidDuplicatesInTheTest) {
269
+ // Check if the mock data is a duplicate of a mock data in the test
270
+ const testMockList = loadMockDataFromConfig(
271
+ ftmocksConifg,
272
+ config.testName
273
+ );
274
+ const matchResponse = testMockList.find((mock) =>
275
+ compareMockToMock(mock.fileContent, mockData, true)
276
+ );
277
+ if (matchResponse) {
278
+ console.log("Aborting duplicate mock data in the test");
279
+ await route.continue();
280
+ return;
281
+ }
282
+ }
283
+
284
+ if (config.avoidDuplicatesWithDefaultMocks) {
285
+ // Check if the mock data is a duplicate of a mock data in the test
286
+ const defaultMockList = getDefaultMockDataFromConfig(ftmocksConifg);
287
+ const matchResponse = defaultMockList.find((mock) =>
288
+ compareMockToMock(mock.fileContent, mockData, true)
289
+ );
290
+ if (matchResponse) {
291
+ console.log("Aborting duplicate mock data with default mocks");
292
+ await route.continue();
293
+ return;
294
+ }
295
+ }
296
+
297
+ // Save the mock data to the test
298
+ const mockListPath = path.join(
299
+ getMockDir(ftmocksConifg),
300
+ `test_${nameToFolder(config.testName)}`,
301
+ "_mock_list.json"
302
+ );
303
+ let mockList = [];
304
+ if (fs.existsSync(mockListPath)) {
305
+ mockList = JSON.parse(fs.readFileSync(mockListPath, "utf8"));
306
+ }
307
+ mockList.push({
308
+ id: mockData.id,
309
+ url: mockData.url,
310
+ method: mockData.method,
311
+ time: mockData.time,
312
+ });
313
+
314
+ // Create test directory if it doesn't exist
315
+ const testDir = path.join(
316
+ getMockDir(ftmocksConifg),
317
+ `test_${nameToFolder(config.testName)}`
318
+ );
319
+ if (!fs.existsSync(testDir)) {
320
+ fs.mkdirSync(testDir, { recursive: true });
321
+ }
322
+ fs.writeFileSync(mockListPath, JSON.stringify(mockList, null, 2));
323
+ const mocDataPath = path.join(
324
+ getMockDir(ftmocksConifg),
325
+ `test_${nameToFolder(config.testName)}`,
326
+ `mock_${mockData.id}.json`
327
+ );
328
+ fs.writeFileSync(mocDataPath, JSON.stringify(mockData, null, 2));
329
+ await route.continue();
330
+ } catch (error) {
331
+ console.error(error);
332
+ await route.continue();
333
+ }
334
+ });
335
+ }
336
+
337
+ module.exports = {
338
+ initiatePlaywrightRoutes,
339
+ recordPlaywrightRoutes,
340
+ };
@@ -0,0 +1,189 @@
1
+ const { getMatchingMockData } = require("./match-utils");
2
+ const { loadMockDataFromConfig } = require("./mock-utils");
3
+ const { resetAllMockStats } = require("./mock-utils");
4
+ const { getDefaultMockDataFromConfig } = require("./mock-utils");
5
+ const { FtJSON } = require("./json-utils");
6
+ const { getMockDir, nameToFolder } = require("./common-utils");
7
+ const { saveSnap } = require("./snap-utils");
8
+ const path = require("path");
9
+ const fs = require("fs");
10
+
11
+ async function initiateJestFetch(jest, ftmocksConifg, testName) {
12
+ const testMockData = testName
13
+ ? loadMockDataFromConfig(ftmocksConifg, testName)
14
+ : [];
15
+ resetAllMockStats({ testMockData, testConfig: ftmocksConifg, testName });
16
+ const defaultMockData = getDefaultMockDataFromConfig(ftmocksConifg);
17
+ console.debug("calling initiateJestFetch fetch");
18
+ global.fetch = jest.fn((url, options = {}) => {
19
+ console.debug("got fetch request", url, options);
20
+ let mockData = getMatchingMockData({
21
+ testMockData,
22
+ defaultMockData,
23
+ url,
24
+ options,
25
+ testConfig: ftmocksConifg,
26
+ testName,
27
+ });
28
+ if (mockData) {
29
+ console.debug("mocked", url, options);
30
+ } else {
31
+ console.debug("missing mock data", url, options);
32
+ return Promise.resolve({
33
+ status: 404,
34
+ headers: new Map([["Content-Type", "application/json"]]),
35
+ json: () => Promise.resolve({ error: "Mock data not found" }),
36
+ });
37
+ }
38
+
39
+ const { content, headers, status } = mockData.response;
40
+
41
+ return Promise.resolve({
42
+ status,
43
+ headers: new Map(Object.entries(headers)),
44
+ json: () => Promise.resolve(FtJSON.parse(content)),
45
+ });
46
+ });
47
+
48
+ console.debug("calling XMLHttpRequest fetch");
49
+ global.XMLHttpRequest = jest.fn(function () {
50
+ const xhrMock = {
51
+ open: jest.fn(),
52
+ send: jest.fn(),
53
+ setRequestHeader: jest.fn(),
54
+ getAllResponseHeaders: jest.fn(() => {
55
+ return "";
56
+ }),
57
+ getResponseHeader: jest.fn((header) => {
58
+ return null;
59
+ }),
60
+ readyState: 4,
61
+ status: 0,
62
+ response: null,
63
+ responseText: "",
64
+ headers: new Map(Object.entries(headers)),
65
+ onreadystatechange: null,
66
+ onload: null,
67
+ onerror: null,
68
+ };
69
+
70
+ xhrMock.send.mockImplementation(function () {
71
+ const mockData = getMatchingMockData({
72
+ testMockData,
73
+ defaultMockData,
74
+ url: xhrMock._url,
75
+ options: xhrMock._options,
76
+ testConfig: ftmocksConifg,
77
+ testName,
78
+ });
79
+
80
+ if (mockData) {
81
+ console.debug("mocked", xhrMock._url, xhrMock._options);
82
+ const { content, headers, status } = mockData.response;
83
+
84
+ xhrMock.status = status;
85
+ xhrMock.responseText = content;
86
+ xhrMock.response = content;
87
+ xhrMock.headers = new Map(Object.entries(headers));
88
+
89
+ if (xhrMock.onreadystatechange) {
90
+ xhrMock.onreadystatechange();
91
+ }
92
+ if (xhrMock.onload) {
93
+ xhrMock.onload();
94
+ }
95
+ } else {
96
+ console.debug("missing mock data", xhrMock._url, xhrMock._options);
97
+
98
+ xhrMock.status = 404;
99
+ xhrMock.responseText = JSON.stringify({ error: "Mock data not found" });
100
+ xhrMock.response = xhrMock.responseText;
101
+
102
+ if (xhrMock.onreadystatechange) {
103
+ xhrMock.onreadystatechange();
104
+ }
105
+ if (xhrMock.onerror) {
106
+ xhrMock.onerror();
107
+ }
108
+ }
109
+ });
110
+
111
+ xhrMock.open.mockImplementation(function (method, url) {
112
+ xhrMock._options = { method };
113
+ xhrMock._url = url;
114
+ });
115
+
116
+ return xhrMock;
117
+ });
118
+
119
+ return;
120
+ }
121
+
122
+ function initiateConsoleLogs(jest, ftmocksConifg, testName) {
123
+ const logsFile = path.join(
124
+ getMockDir(ftmocksConifg),
125
+ `test_${nameToFolder(testName)}`,
126
+ "_logs.json"
127
+ );
128
+ let logs = [];
129
+ if (!fs.existsSync(logsFile)) {
130
+ fs.appendFileSync(logsFile, "[]", "utf8");
131
+ } else {
132
+ fs.writeFileSync(logsFile, "[]", "utf8");
133
+ }
134
+
135
+ const writeToFile = (type, params) => {
136
+ const logMessage = params.join(" ") + "\n"; // Combine params into a string with spaces
137
+ logs.push({
138
+ type,
139
+ message: logMessage,
140
+ time: Date.now(),
141
+ });
142
+ fs.writeFileSync(logsFile, JSON.stringify(logs, null, 2), "utf8"); // Append the log message to the file
143
+ };
144
+
145
+ global.console = {
146
+ ...console,
147
+ // uncomment to ignore a specific log level
148
+ log: jest.fn((...params) => {
149
+ writeToFile("log", params);
150
+ }),
151
+ debug: jest.fn((...params) => {
152
+ writeToFile("debug", params);
153
+ }),
154
+ info: jest.fn((...params) => {
155
+ writeToFile("info", params);
156
+ }),
157
+ warn: jest.fn((...params) => {
158
+ writeToFile("warn", params);
159
+ }),
160
+ error: jest.fn((...params) => {
161
+ writeToFile("error", params);
162
+ }),
163
+ };
164
+ }
165
+
166
+ function initiateJestEventSnaps(jest, ftmocksConifg, testName) {
167
+ const mouseEvents = ftmocksConifg.snapEvents || [
168
+ "click",
169
+ "change",
170
+ "url",
171
+ "dblclick",
172
+ "contextmenu",
173
+ ];
174
+ mouseEvents.forEach((event) => {
175
+ jest
176
+ .spyOn(document, "addEventListener")
177
+ .mockImplementation((e, callback) => {
178
+ if (mouseEvents.includes(e)) {
179
+ saveSnap(document.outerHTML, ftmocksConifg, testName);
180
+ }
181
+ });
182
+ });
183
+ }
184
+
185
+ module.exports = {
186
+ initiateJestFetch,
187
+ initiateConsoleLogs,
188
+ initiateJestEventSnaps,
189
+ };
@@ -0,0 +1,45 @@
1
+ const path = require("path");
2
+ const fs = require("fs");
3
+ const { getMockDir, nameToFolder } = require("./common-utils");
4
+ const { countFilesInDirectory } = require("./common-utils");
5
+
6
+ const saveSnap = async (html, ftmocksConifg, testName) => {
7
+ const snapFolder = path.join(
8
+ getMockDir(ftmocksConifg),
9
+ `test_${nameToFolder(testName)}`,
10
+ "_snaps"
11
+ );
12
+ const snapTemplate = path.join(
13
+ getMockDir(ftmocksConifg),
14
+ "snap_template.html"
15
+ );
16
+
17
+ if (!fs.existsSync(snapFolder)) {
18
+ fs.mkdirSync(snapFolder);
19
+ }
20
+ const fileCount = await countFilesInDirectory(snapFolder);
21
+ const snapFilePath = path.join(snapFolder, `snap_${fileCount + 1}.html`);
22
+ let resHtml = html;
23
+ if (fs.existsSync(snapFolder)) {
24
+ const templateHtml = fs.readFileSync(snapTemplate, "utf8");
25
+ resHtml = templateHtml.replace(
26
+ "<!--FtMocks-Snap-Template-To-Be-Replaced-->",
27
+ html
28
+ );
29
+ }
30
+ fs.writeFileSync(snapFilePath, resHtml);
31
+ };
32
+
33
+ const deleteAllSnaps = async (ftmocksConifg, testName) => {
34
+ const snapFolder = path.join(
35
+ getMockDir(ftmocksConifg),
36
+ `test_${nameToFolder(testName)}`,
37
+ "_snaps"
38
+ );
39
+ fs.rmSync(snapFolder, { recursive: true, force: true });
40
+ };
41
+
42
+ module.exports = {
43
+ saveSnap,
44
+ deleteAllSnaps,
45
+ };