ftmocks-utils 1.3.2 → 1.3.3
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 +1 -1
- package/src/compare-utils.js +151 -0
- package/src/index.js +19 -313
- package/src/log-utils.js +15 -2
- package/src/mock-utils.js +101 -0
- package/src/rank-compare-utils.js +62 -0
- package/src/recorder.js +289 -271
- package/src/test-utils.js +49 -0
package/package.json
CHANGED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
const { clearNulls, processURL } = require("./common-utils");
|
|
2
|
+
const { FtJSON } = require("./json-utils");
|
|
3
|
+
|
|
4
|
+
const isUrlAndMethodSame = (req1, req2) => {
|
|
5
|
+
const url1 = new URL(`http://domain.com${req1.url}`);
|
|
6
|
+
const url2 = new URL(`http://domain.com${req2.url}`);
|
|
7
|
+
return (
|
|
8
|
+
url1.pathname === url2.pathname &&
|
|
9
|
+
url1.method?.toLowerCase() === url2.method?.toLowerCase()
|
|
10
|
+
);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const isSameRequest = (req1, req2) => {
|
|
14
|
+
clearNulls(req1.postData);
|
|
15
|
+
clearNulls(req2.postData);
|
|
16
|
+
let matched = true;
|
|
17
|
+
if (req1.url !== req2.url) {
|
|
18
|
+
matched = false;
|
|
19
|
+
} else if (req1.method?.toLowerCase() !== req2.method?.toLowerCase()) {
|
|
20
|
+
matched = false;
|
|
21
|
+
} else if (
|
|
22
|
+
(!req1.postData && req2.postData) ||
|
|
23
|
+
(req1.postData && !req2.postData)
|
|
24
|
+
) {
|
|
25
|
+
matched = FtJSON.areJsonEqual(req1.postData || {}, req2.postData || {});
|
|
26
|
+
} else if (
|
|
27
|
+
req1.postData &&
|
|
28
|
+
req2.postData &&
|
|
29
|
+
!FtJSON.areJsonEqual(req1.postData, req2.postData)
|
|
30
|
+
) {
|
|
31
|
+
matched = false;
|
|
32
|
+
}
|
|
33
|
+
return matched;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const isSameResponse = (req1, req2) => {
|
|
37
|
+
try {
|
|
38
|
+
let matched = true;
|
|
39
|
+
if (req1.response.status !== req2.response.status) {
|
|
40
|
+
matched = false;
|
|
41
|
+
// console.log('not matched at url', req1.method, req2.method);
|
|
42
|
+
} else if (
|
|
43
|
+
(!req1.response.content && req2.response.content) ||
|
|
44
|
+
(req1.response.content && !req2.response.content)
|
|
45
|
+
) {
|
|
46
|
+
matched = FtJSON.areJsonEqual(
|
|
47
|
+
FtJSON.parse(req1.response.content) || {},
|
|
48
|
+
FtJSON.parse(req2.response.content) || {}
|
|
49
|
+
);
|
|
50
|
+
// console.log('not matched at post Data 0', req1.postData, req2.postData);
|
|
51
|
+
} else if (
|
|
52
|
+
req1.response.content &&
|
|
53
|
+
req2.response.content &&
|
|
54
|
+
!FtJSON.areJsonEqual(
|
|
55
|
+
FtJSON.parse(req1.response.content) || {},
|
|
56
|
+
FtJSON.parse(req2.response.content) || {}
|
|
57
|
+
)
|
|
58
|
+
) {
|
|
59
|
+
matched = false;
|
|
60
|
+
}
|
|
61
|
+
// if (matched) {
|
|
62
|
+
// console.log('matched responses', req1, req2);
|
|
63
|
+
// }
|
|
64
|
+
return matched;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error(error);
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
function compareMockToRequest(mock, req) {
|
|
72
|
+
const mockURL = processURL(
|
|
73
|
+
mock.fileContent.url,
|
|
74
|
+
mock.fileContent.ignoreParams
|
|
75
|
+
);
|
|
76
|
+
const reqURL = processURL(req.originalUrl, mock.fileContent.ignoreParams);
|
|
77
|
+
const isSameUrlAndMethod = isUrlAndMethodSame(
|
|
78
|
+
{ url: mockURL, method: mock.fileContent.method },
|
|
79
|
+
{ url: reqURL, method: req.method }
|
|
80
|
+
);
|
|
81
|
+
if (!isSameUrlAndMethod) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
const postData = mock.fileContent.request?.postData?.text
|
|
85
|
+
? FtJSON.parse(mock.fileContent.request?.postData?.text)
|
|
86
|
+
: mock.fileContent.request?.postData;
|
|
87
|
+
return isSameRequest(
|
|
88
|
+
{ url: mockURL, method: mock.fileContent.method, postData },
|
|
89
|
+
{
|
|
90
|
+
method: req.method,
|
|
91
|
+
postData: req.body,
|
|
92
|
+
url: reqURL,
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function compareMockToFetchRequest(mock, fetchReq) {
|
|
98
|
+
try {
|
|
99
|
+
const mockURL = processURL(
|
|
100
|
+
mock.fileContent.url,
|
|
101
|
+
mock.fileContent.ignoreParams
|
|
102
|
+
);
|
|
103
|
+
const reqURL = processURL(fetchReq.url, mock.fileContent.ignoreParams);
|
|
104
|
+
const isSameUrlAndMethod = isUrlAndMethodSame(
|
|
105
|
+
{ url: mockURL, method: mock.fileContent.method },
|
|
106
|
+
{ url: reqURL, method: fetchReq.options.method || "GET" }
|
|
107
|
+
);
|
|
108
|
+
if (!isSameUrlAndMethod) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
const postData = mock.fileContent.request?.postData?.text
|
|
112
|
+
? FtJSON.parse(mock.fileContent.request?.postData?.text)
|
|
113
|
+
: mock.fileContent.request?.postData;
|
|
114
|
+
return isSameRequest(
|
|
115
|
+
{ url: mockURL, method: mock.fileContent.method, postData },
|
|
116
|
+
{
|
|
117
|
+
method: fetchReq.options.method || "GET",
|
|
118
|
+
postData: fetchReq.options.body?.length
|
|
119
|
+
? FtJSON.parse(fetchReq.options.body)
|
|
120
|
+
: fetchReq.options.body,
|
|
121
|
+
url: reqURL,
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
} catch (e) {
|
|
125
|
+
console.error("error at compareMockToFetchRequest", mock, fetchReq);
|
|
126
|
+
console.error(e);
|
|
127
|
+
}
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const compareMockToMock = (mock1, mock2, matchResponse) => {
|
|
132
|
+
try {
|
|
133
|
+
if (matchResponse) {
|
|
134
|
+
return isSameRequest(mock1, mock2) && isSameResponse(mock1, mock2);
|
|
135
|
+
} else {
|
|
136
|
+
return isSameRequest(mock1, mock2);
|
|
137
|
+
}
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error(error);
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
module.exports = {
|
|
145
|
+
isUrlAndMethodSame,
|
|
146
|
+
isSameRequest,
|
|
147
|
+
isSameResponse,
|
|
148
|
+
compareMockToRequest,
|
|
149
|
+
compareMockToFetchRequest,
|
|
150
|
+
compareMockToMock,
|
|
151
|
+
};
|
package/src/index.js
CHANGED
|
@@ -12,209 +12,25 @@ const {
|
|
|
12
12
|
countFilesInDirectory,
|
|
13
13
|
getTestByName,
|
|
14
14
|
} = require("./common-utils");
|
|
15
|
+
const {
|
|
16
|
+
getDefaultMockDataFromConfig,
|
|
17
|
+
loadMockDataFromConfig,
|
|
18
|
+
resetAllMockStats,
|
|
19
|
+
} = require("./mock-utils");
|
|
20
|
+
const { createTest } = require("./test-utils");
|
|
15
21
|
const { FtJSON } = require("./json-utils");
|
|
16
|
-
const Logger = require("./log-utils");
|
|
22
|
+
const { Logger, deleteAllLogs } = require("./log-utils");
|
|
23
|
+
const {
|
|
24
|
+
isSameRequest,
|
|
25
|
+
compareMockToRequest,
|
|
26
|
+
compareMockToFetchRequest,
|
|
27
|
+
compareMockToMock,
|
|
28
|
+
} = require("./compare-utils");
|
|
29
|
+
const { getCompareRankMockToFetchRequest } = require("./rank-compare-utils");
|
|
17
30
|
|
|
18
31
|
let logger = null;
|
|
19
32
|
|
|
20
|
-
const getDefaultMockDataFromConfig = (testConfig) => {
|
|
21
|
-
const defaultPath = path.join(
|
|
22
|
-
getMockDir(testConfig),
|
|
23
|
-
"defaultMocks",
|
|
24
|
-
"_mock_list.json"
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
const defaultData = fs.readFileSync(defaultPath, "utf8");
|
|
29
|
-
let parsedData = JSON.parse(defaultData);
|
|
30
|
-
|
|
31
|
-
// Read and attach mock data for each entry in parsedData
|
|
32
|
-
parsedData.forEach((entry) => {
|
|
33
|
-
const mockFilePath = path.join(
|
|
34
|
-
getMockDir(testConfig),
|
|
35
|
-
"defaultMocks",
|
|
36
|
-
`mock_${entry.id}.json`
|
|
37
|
-
);
|
|
38
|
-
try {
|
|
39
|
-
const mockData = fs.readFileSync(mockFilePath, "utf8");
|
|
40
|
-
entry.fileContent = JSON.parse(mockData);
|
|
41
|
-
} catch (error) {
|
|
42
|
-
console.error(`Error reading mock data for ${entry.id}:`, error);
|
|
43
|
-
return entry; // Return the original entry if there's an error
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
return parsedData;
|
|
47
|
-
} catch (error) {
|
|
48
|
-
console.error(`Error reading or parsing default mocks:`, error);
|
|
49
|
-
return [];
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
|
|
53
33
|
// src/index.js
|
|
54
|
-
const loadMockDataFromConfig = (testConfig, _testName) => {
|
|
55
|
-
try {
|
|
56
|
-
let testName = _testName;
|
|
57
|
-
if (!testName) {
|
|
58
|
-
// Read the test ID from mockServer.config.json
|
|
59
|
-
const configPath = path.join(
|
|
60
|
-
getMockDir(testConfig),
|
|
61
|
-
"mockServer.config.json"
|
|
62
|
-
);
|
|
63
|
-
const configData = fs.readFileSync(configPath, "utf8");
|
|
64
|
-
const config = JSON.parse(configData);
|
|
65
|
-
testName = config.testName;
|
|
66
|
-
}
|
|
67
|
-
// Read the tests from testConfig
|
|
68
|
-
const mocksPath = path.join(
|
|
69
|
-
getMockDir(testConfig),
|
|
70
|
-
`test_${nameToFolder(testName)}`,
|
|
71
|
-
"_mock_list.json"
|
|
72
|
-
);
|
|
73
|
-
const mocksData = fs.readFileSync(mocksPath, "utf8");
|
|
74
|
-
const mocks = JSON.parse(mocksData);
|
|
75
|
-
|
|
76
|
-
mocks.forEach((mock) => {
|
|
77
|
-
const fileContent = JSON.parse(
|
|
78
|
-
fs.readFileSync(
|
|
79
|
-
path.join(
|
|
80
|
-
getMockDir(testConfig),
|
|
81
|
-
`test_${nameToFolder(testName)}`,
|
|
82
|
-
`mock_${mock.id}.json`
|
|
83
|
-
),
|
|
84
|
-
"utf8"
|
|
85
|
-
)
|
|
86
|
-
);
|
|
87
|
-
mock.fileContent = fileContent;
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
return mocks;
|
|
91
|
-
} catch (error) {
|
|
92
|
-
console.error("Error loading test data:", error.message);
|
|
93
|
-
return [];
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const isSameRequest = (req1, req2) => {
|
|
98
|
-
clearNulls(req1.postData);
|
|
99
|
-
clearNulls(req2.postData);
|
|
100
|
-
let matched = true;
|
|
101
|
-
if (req1.url !== req2.url) {
|
|
102
|
-
matched = false;
|
|
103
|
-
} else if (req1.method?.toLowerCase() !== req2.method?.toLowerCase()) {
|
|
104
|
-
matched = false;
|
|
105
|
-
} else if (
|
|
106
|
-
(!req1.postData && req2.postData) ||
|
|
107
|
-
(req1.postData && !req2.postData)
|
|
108
|
-
) {
|
|
109
|
-
matched = FtJSON.areJsonEqual(req1.postData || {}, req2.postData || {});
|
|
110
|
-
} else if (
|
|
111
|
-
req1.postData &&
|
|
112
|
-
req2.postData &&
|
|
113
|
-
!FtJSON.areJsonEqual(req1.postData, req2.postData)
|
|
114
|
-
) {
|
|
115
|
-
matched = false;
|
|
116
|
-
}
|
|
117
|
-
return matched;
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
const getSameRequestRank = (req1, req2) => {
|
|
121
|
-
let rank = 1;
|
|
122
|
-
clearNulls(req1.postData);
|
|
123
|
-
clearNulls(req2.postData);
|
|
124
|
-
// Compare path names
|
|
125
|
-
const url1 = new URL(`http://domain.com${req1.url}`);
|
|
126
|
-
const url2 = new URL(`http://domain.com${req2.url}`);
|
|
127
|
-
if (url1.pathname !== url2.pathname) {
|
|
128
|
-
rank = 0;
|
|
129
|
-
} else if (url1.method?.toLowerCase() !== url2.method?.toLowerCase()) {
|
|
130
|
-
rank = 0;
|
|
131
|
-
} else {
|
|
132
|
-
// Compare query strings
|
|
133
|
-
const queryDiff = charDifference(url1.search || "", url2.search || "");
|
|
134
|
-
rank = rank + queryDiff;
|
|
135
|
-
// Compare post data
|
|
136
|
-
const charDiff = charDifference(
|
|
137
|
-
FtJSON.stringify(req1.postData || {}),
|
|
138
|
-
FtJSON.stringify(req2.postData || {})
|
|
139
|
-
);
|
|
140
|
-
rank = rank + charDiff;
|
|
141
|
-
}
|
|
142
|
-
return rank;
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
function compareMockToRequest(mock, req) {
|
|
146
|
-
const mockURL = processURL(
|
|
147
|
-
mock.fileContent.url,
|
|
148
|
-
mock.fileContent.ignoreParams
|
|
149
|
-
);
|
|
150
|
-
const reqURL = processURL(req.originalUrl, mock.fileContent.ignoreParams);
|
|
151
|
-
const postData = mock.fileContent.request?.postData?.text
|
|
152
|
-
? FtJSON.parse(mock.fileContent.request?.postData?.text)
|
|
153
|
-
: mock.fileContent.request?.postData;
|
|
154
|
-
return isSameRequest(
|
|
155
|
-
{ url: mockURL, method: mock.fileContent.method, postData },
|
|
156
|
-
{
|
|
157
|
-
method: req.method,
|
|
158
|
-
postData: req.body,
|
|
159
|
-
url: reqURL,
|
|
160
|
-
}
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function compareMockToFetchRequest(mock, fetchReq) {
|
|
165
|
-
try {
|
|
166
|
-
const mockURL = processURL(
|
|
167
|
-
mock.fileContent.url,
|
|
168
|
-
mock.fileContent.ignoreParams
|
|
169
|
-
);
|
|
170
|
-
const reqURL = processURL(fetchReq.url, mock.fileContent.ignoreParams);
|
|
171
|
-
const postData = mock.fileContent.request?.postData?.text
|
|
172
|
-
? FtJSON.parse(mock.fileContent.request?.postData?.text)
|
|
173
|
-
: mock.fileContent.request?.postData;
|
|
174
|
-
return isSameRequest(
|
|
175
|
-
{ url: mockURL, method: mock.fileContent.method, postData },
|
|
176
|
-
{
|
|
177
|
-
method: fetchReq.options.method || "GET",
|
|
178
|
-
postData: fetchReq.options.body?.length
|
|
179
|
-
? FtJSON.parse(fetchReq.options.body)
|
|
180
|
-
: fetchReq.options.body,
|
|
181
|
-
url: reqURL,
|
|
182
|
-
}
|
|
183
|
-
);
|
|
184
|
-
} catch (e) {
|
|
185
|
-
console.error("error at compareMockToFetchRequest", mock, fetchReq);
|
|
186
|
-
console.error(e);
|
|
187
|
-
}
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function getCompareRankMockToFetchRequest(mock, fetchReq) {
|
|
192
|
-
try {
|
|
193
|
-
const mockURL = processURL(
|
|
194
|
-
mock.fileContent.url,
|
|
195
|
-
mock.fileContent.ignoreParams
|
|
196
|
-
);
|
|
197
|
-
const reqURL = processURL(fetchReq.url, mock.fileContent.ignoreParams);
|
|
198
|
-
const postData = mock.fileContent.request?.postData?.text
|
|
199
|
-
? FtJSON.parse(mock.fileContent.request?.postData?.text)
|
|
200
|
-
: mock.fileContent.request?.postData;
|
|
201
|
-
return getSameRequestRank(
|
|
202
|
-
{ url: mockURL, method: mock.fileContent.method, postData },
|
|
203
|
-
{
|
|
204
|
-
method: fetchReq.options.method || "GET",
|
|
205
|
-
postData: fetchReq.options.body?.length
|
|
206
|
-
? FtJSON.parse(fetchReq.options.body)
|
|
207
|
-
: fetchReq.options.body,
|
|
208
|
-
url: reqURL,
|
|
209
|
-
}
|
|
210
|
-
);
|
|
211
|
-
} catch (e) {
|
|
212
|
-
console.error("error at getCompareRankMockToFetchRequest", mock, fetchReq);
|
|
213
|
-
console.error(e);
|
|
214
|
-
}
|
|
215
|
-
return false;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
34
|
function getMatchingMockData({
|
|
219
35
|
testMockData,
|
|
220
36
|
defaultMockData,
|
|
@@ -300,22 +116,6 @@ function getMatchingMockData({
|
|
|
300
116
|
return foundMock ? foundMock.fileContent : null;
|
|
301
117
|
}
|
|
302
118
|
|
|
303
|
-
async function resetAllMockStats({ testMockData, testConfig, testName }) {
|
|
304
|
-
for (let i = 0; i < testMockData.length; i++) {
|
|
305
|
-
const tmd = testMockData[i];
|
|
306
|
-
const mockFilePath = path.join(
|
|
307
|
-
getMockDir(testConfig),
|
|
308
|
-
`test_${nameToFolder(testName)}`,
|
|
309
|
-
`mock_${tmd.id}.json`
|
|
310
|
-
);
|
|
311
|
-
tmd.fileContent.served = false;
|
|
312
|
-
await fs.writeFileSync(
|
|
313
|
-
mockFilePath,
|
|
314
|
-
JSON.stringify(tmd.fileContent, null, 2)
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
119
|
async function initiatePlaywrightRoutes(
|
|
320
120
|
page,
|
|
321
121
|
ftmocksConifg,
|
|
@@ -323,7 +123,11 @@ async function initiatePlaywrightRoutes(
|
|
|
323
123
|
mockPath = "**/*",
|
|
324
124
|
excludeMockPath = null
|
|
325
125
|
) {
|
|
326
|
-
logger = new Logger(
|
|
126
|
+
logger = new Logger(
|
|
127
|
+
{ disableLogs: ftmocksConifg.DISABLE_LOGS },
|
|
128
|
+
ftmocksConifg,
|
|
129
|
+
testName
|
|
130
|
+
);
|
|
327
131
|
const testMockData = testName
|
|
328
132
|
? loadMockDataFromConfig(ftmocksConifg, testName)
|
|
329
133
|
: [];
|
|
@@ -692,15 +496,6 @@ const deleteAllSnaps = async (ftmocksConifg, testName) => {
|
|
|
692
496
|
fs.rmSync(snapFolder, { recursive: true, force: true });
|
|
693
497
|
};
|
|
694
498
|
|
|
695
|
-
const deleteAllLogs = async (ftmocksConifg, testName) => {
|
|
696
|
-
const mockDir = path.join(
|
|
697
|
-
getMockDir(ftmocksConifg),
|
|
698
|
-
`test_${nameToFolder(testName)}`
|
|
699
|
-
);
|
|
700
|
-
const logFilePath = path.join(mockDir, `_logs.json`);
|
|
701
|
-
fs.rmSync(logFilePath, { recursive: true, force: true });
|
|
702
|
-
};
|
|
703
|
-
|
|
704
499
|
function initiateJestEventSnaps(jest, ftmocksConifg, testName) {
|
|
705
500
|
const mouseEvents = ftmocksConifg.snapEvents || [
|
|
706
501
|
"click",
|
|
@@ -720,95 +515,6 @@ function initiateJestEventSnaps(jest, ftmocksConifg, testName) {
|
|
|
720
515
|
});
|
|
721
516
|
}
|
|
722
517
|
|
|
723
|
-
const createTest = async (ftmocksConifg, testName) => {
|
|
724
|
-
const testsPath = path.join(getMockDir(ftmocksConifg), "tests.json");
|
|
725
|
-
let tests = [];
|
|
726
|
-
try {
|
|
727
|
-
// Read existing tests
|
|
728
|
-
const testsData = fs.readFileSync(testsPath, "utf8");
|
|
729
|
-
tests = JSON.parse(testsData);
|
|
730
|
-
const etest = tests.find((tst) => tst.name === testName);
|
|
731
|
-
if (!etest) {
|
|
732
|
-
const newTest = {
|
|
733
|
-
id: uuidv4(),
|
|
734
|
-
name: testName,
|
|
735
|
-
};
|
|
736
|
-
tests.push(newTest);
|
|
737
|
-
fs.writeFileSync(testsPath, JSON.stringify(tests, null, 2));
|
|
738
|
-
const folderPath = path.join(
|
|
739
|
-
getMockDir(ftmocksConifg),
|
|
740
|
-
`test_${nameToFolder(testName)}`
|
|
741
|
-
);
|
|
742
|
-
const mockListFilePath = path.join(folderPath, "_mock_list.json");
|
|
743
|
-
fs.mkdir(folderPath, { recursive: true }, (err) => {
|
|
744
|
-
if (err) {
|
|
745
|
-
console.error("\x1b[31mError creating directory:\x1b[0m", err);
|
|
746
|
-
} else {
|
|
747
|
-
console.log("\x1b[32mDirectory created successfully!\x1b[0m");
|
|
748
|
-
}
|
|
749
|
-
});
|
|
750
|
-
await fs.appendFile(mockListFilePath, "[]", () => {
|
|
751
|
-
console.log("\x1b[32mmock list file created successfully\x1b[0m");
|
|
752
|
-
});
|
|
753
|
-
|
|
754
|
-
return newTest;
|
|
755
|
-
} else {
|
|
756
|
-
throw "Test already exists";
|
|
757
|
-
}
|
|
758
|
-
} catch (error) {
|
|
759
|
-
console.error(`\x1b[31mError reading tests.json:\x1b[0m`, error);
|
|
760
|
-
return null;
|
|
761
|
-
}
|
|
762
|
-
};
|
|
763
|
-
|
|
764
|
-
const isSameResponse = (req1, req2) => {
|
|
765
|
-
try {
|
|
766
|
-
let matched = true;
|
|
767
|
-
if (req1.response.status !== req2.response.status) {
|
|
768
|
-
matched = false;
|
|
769
|
-
// console.log('not matched at url', req1.method, req2.method);
|
|
770
|
-
} else if (
|
|
771
|
-
(!req1.response.content && req2.response.content) ||
|
|
772
|
-
(req1.response.content && !req2.response.content)
|
|
773
|
-
) {
|
|
774
|
-
matched = FtJSON.areJsonEqual(
|
|
775
|
-
FtJSON.parse(req1.response.content) || {},
|
|
776
|
-
FtJSON.parse(req2.response.content) || {}
|
|
777
|
-
);
|
|
778
|
-
// console.log('not matched at post Data 0', req1.postData, req2.postData);
|
|
779
|
-
} else if (
|
|
780
|
-
req1.response.content &&
|
|
781
|
-
req2.response.content &&
|
|
782
|
-
!FtJSON.areJsonEqual(
|
|
783
|
-
FtJSON.parse(req1.response.content) || {},
|
|
784
|
-
FtJSON.parse(req2.response.content) || {}
|
|
785
|
-
)
|
|
786
|
-
) {
|
|
787
|
-
matched = false;
|
|
788
|
-
}
|
|
789
|
-
// if (matched) {
|
|
790
|
-
// console.log('matched responses', req1, req2);
|
|
791
|
-
// }
|
|
792
|
-
return matched;
|
|
793
|
-
} catch (error) {
|
|
794
|
-
console.error(error);
|
|
795
|
-
return false;
|
|
796
|
-
}
|
|
797
|
-
};
|
|
798
|
-
|
|
799
|
-
const compareMockToMock = (mock1, mock2, matchResponse) => {
|
|
800
|
-
try {
|
|
801
|
-
if (matchResponse) {
|
|
802
|
-
return isSameRequest(mock1, mock2) && isSameResponse(mock1, mock2);
|
|
803
|
-
} else {
|
|
804
|
-
return isSameRequest(mock1, mock2);
|
|
805
|
-
}
|
|
806
|
-
} catch (error) {
|
|
807
|
-
console.error(error);
|
|
808
|
-
return false;
|
|
809
|
-
}
|
|
810
|
-
};
|
|
811
|
-
|
|
812
518
|
const saveIfItIsFile = async (route, testName, ftmocksConifg) => {
|
|
813
519
|
const urlObj = new URL(route.request().url());
|
|
814
520
|
|
package/src/log-utils.js
CHANGED
|
@@ -30,7 +30,11 @@ class Logger {
|
|
|
30
30
|
time: Date.now(),
|
|
31
31
|
source: "ftmocks-utils",
|
|
32
32
|
});
|
|
33
|
-
fs.writeFileSync(
|
|
33
|
+
fs.writeFileSync(
|
|
34
|
+
this.logsFile,
|
|
35
|
+
JSON.stringify(this.logs, null, 2),
|
|
36
|
+
"utf8"
|
|
37
|
+
); // Append the log message to the file
|
|
34
38
|
} catch (error) {
|
|
35
39
|
// Ignore error
|
|
36
40
|
}
|
|
@@ -87,4 +91,13 @@ class Logger {
|
|
|
87
91
|
}
|
|
88
92
|
}
|
|
89
93
|
|
|
90
|
-
|
|
94
|
+
const deleteAllLogs = async (ftmocksConifg, testName) => {
|
|
95
|
+
const mockDir = path.join(
|
|
96
|
+
getMockDir(ftmocksConifg),
|
|
97
|
+
`test_${nameToFolder(testName)}`
|
|
98
|
+
);
|
|
99
|
+
const logFilePath = path.join(mockDir, `_logs.json`);
|
|
100
|
+
fs.rmSync(logFilePath, { recursive: true, force: true });
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
module.exports = { Logger, deleteAllLogs };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { getMockDir, nameToFolder } = require("./common-utils");
|
|
4
|
+
|
|
5
|
+
const getDefaultMockDataFromConfig = (testConfig) => {
|
|
6
|
+
const defaultPath = path.join(
|
|
7
|
+
getMockDir(testConfig),
|
|
8
|
+
"defaultMocks",
|
|
9
|
+
"_mock_list.json"
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const defaultData = fs.readFileSync(defaultPath, "utf8");
|
|
14
|
+
let parsedData = JSON.parse(defaultData);
|
|
15
|
+
|
|
16
|
+
// Read and attach mock data for each entry in parsedData
|
|
17
|
+
parsedData.forEach((entry) => {
|
|
18
|
+
const mockFilePath = path.join(
|
|
19
|
+
getMockDir(testConfig),
|
|
20
|
+
"defaultMocks",
|
|
21
|
+
`mock_${entry.id}.json`
|
|
22
|
+
);
|
|
23
|
+
try {
|
|
24
|
+
const mockData = fs.readFileSync(mockFilePath, "utf8");
|
|
25
|
+
entry.fileContent = JSON.parse(mockData);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error(`Error reading mock data for ${entry.id}:`, error);
|
|
28
|
+
return entry; // Return the original entry if there's an error
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
return parsedData;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error(`Error reading or parsing default mocks:`, error);
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const loadMockDataFromConfig = (testConfig, _testName) => {
|
|
39
|
+
try {
|
|
40
|
+
let testName = _testName;
|
|
41
|
+
if (!testName) {
|
|
42
|
+
// Read the test ID from mockServer.config.json
|
|
43
|
+
const configPath = path.join(
|
|
44
|
+
getMockDir(testConfig),
|
|
45
|
+
"mockServer.config.json"
|
|
46
|
+
);
|
|
47
|
+
const configData = fs.readFileSync(configPath, "utf8");
|
|
48
|
+
const config = JSON.parse(configData);
|
|
49
|
+
testName = config.testName;
|
|
50
|
+
}
|
|
51
|
+
// Read the tests from testConfig
|
|
52
|
+
const mocksPath = path.join(
|
|
53
|
+
getMockDir(testConfig),
|
|
54
|
+
`test_${nameToFolder(testName)}`,
|
|
55
|
+
"_mock_list.json"
|
|
56
|
+
);
|
|
57
|
+
const mocksData = fs.readFileSync(mocksPath, "utf8");
|
|
58
|
+
const mocks = JSON.parse(mocksData);
|
|
59
|
+
|
|
60
|
+
mocks.forEach((mock) => {
|
|
61
|
+
const fileContent = JSON.parse(
|
|
62
|
+
fs.readFileSync(
|
|
63
|
+
path.join(
|
|
64
|
+
getMockDir(testConfig),
|
|
65
|
+
`test_${nameToFolder(testName)}`,
|
|
66
|
+
`mock_${mock.id}.json`
|
|
67
|
+
),
|
|
68
|
+
"utf8"
|
|
69
|
+
)
|
|
70
|
+
);
|
|
71
|
+
mock.fileContent = fileContent;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return mocks;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error("Error loading test data:", error.message);
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
async function resetAllMockStats({ testMockData, testConfig, testName }) {
|
|
82
|
+
for (let i = 0; i < testMockData.length; i++) {
|
|
83
|
+
const tmd = testMockData[i];
|
|
84
|
+
const mockFilePath = path.join(
|
|
85
|
+
getMockDir(testConfig),
|
|
86
|
+
`test_${nameToFolder(testName)}`,
|
|
87
|
+
`mock_${tmd.id}.json`
|
|
88
|
+
);
|
|
89
|
+
tmd.fileContent.served = false;
|
|
90
|
+
await fs.writeFileSync(
|
|
91
|
+
mockFilePath,
|
|
92
|
+
JSON.stringify(tmd.fileContent, null, 2)
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
getDefaultMockDataFromConfig,
|
|
99
|
+
loadMockDataFromConfig,
|
|
100
|
+
resetAllMockStats,
|
|
101
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const { clearNulls, charDifference, processURL } = require("./common-utils");
|
|
2
|
+
const { FtJSON } = require("./json-utils");
|
|
3
|
+
const { isUrlAndMethodSame } = require("./compare-utils");
|
|
4
|
+
|
|
5
|
+
const getSameRequestRank = (req1, req2) => {
|
|
6
|
+
let rank = 1;
|
|
7
|
+
clearNulls(req1.postData);
|
|
8
|
+
clearNulls(req2.postData);
|
|
9
|
+
// Compare query strings
|
|
10
|
+
const queryDiff = charDifference(
|
|
11
|
+
req1.url.split("?")[1] || "",
|
|
12
|
+
req2.url.split("?")[1] || ""
|
|
13
|
+
);
|
|
14
|
+
rank = rank + queryDiff;
|
|
15
|
+
// Compare post data
|
|
16
|
+
const postDataDiff = charDifference(
|
|
17
|
+
FtJSON.stringify(req1.postData || {}),
|
|
18
|
+
FtJSON.stringify(req2.postData || {})
|
|
19
|
+
);
|
|
20
|
+
rank = rank + postDataDiff;
|
|
21
|
+
return rank;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function getCompareRankMockToFetchRequest(mock, fetchReq) {
|
|
25
|
+
try {
|
|
26
|
+
const mockURL = processURL(
|
|
27
|
+
mock.fileContent.url,
|
|
28
|
+
mock.fileContent.ignoreParams
|
|
29
|
+
);
|
|
30
|
+
const reqURL = processURL(fetchReq.url, mock.fileContent.ignoreParams);
|
|
31
|
+
if (
|
|
32
|
+
!isUrlAndMethodSame(
|
|
33
|
+
{ url: mockURL, method: mock.fileContent.method },
|
|
34
|
+
{ url: reqURL, method: fetchReq.options.method || "GET" }
|
|
35
|
+
)
|
|
36
|
+
) {
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
const postData = mock.fileContent.request?.postData?.text
|
|
40
|
+
? FtJSON.parse(mock.fileContent.request?.postData?.text)
|
|
41
|
+
: mock.fileContent.request?.postData;
|
|
42
|
+
return getSameRequestRank(
|
|
43
|
+
{ url: mockURL, method: mock.fileContent.method, postData },
|
|
44
|
+
{
|
|
45
|
+
method: fetchReq.options.method || "GET",
|
|
46
|
+
postData: fetchReq.options.body?.length
|
|
47
|
+
? FtJSON.parse(fetchReq.options.body)
|
|
48
|
+
: fetchReq.options.body,
|
|
49
|
+
url: reqURL,
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.error("error at getCompareRankMockToFetchRequest", mock, fetchReq);
|
|
54
|
+
console.error(e);
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = {
|
|
60
|
+
getSameRequestRank,
|
|
61
|
+
getCompareRankMockToFetchRequest,
|
|
62
|
+
};
|
package/src/recorder.js
CHANGED
|
@@ -1,288 +1,306 @@
|
|
|
1
1
|
window.FTMOCKS_CONFIG = {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
record_mocks_url: "http://localhost:5000/api/v1/recordMockdata",
|
|
3
|
+
record_events_url: "http://localhost:5000/api/v1/recordedEvents",
|
|
4
4
|
};
|
|
5
5
|
|
|
6
|
-
|
|
7
6
|
(function () {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
7
|
+
// Intercept Fetch API
|
|
8
|
+
const originalFetch = window.fetch;
|
|
9
|
+
const recordedTracks = [];
|
|
10
|
+
|
|
11
|
+
const addTrack = (track) => {
|
|
12
|
+
track.id = recordedTracks.length
|
|
13
|
+
? recordedTracks[recordedTracks.length - 1].id + 1
|
|
14
|
+
: 1;
|
|
15
|
+
track.time = new Date();
|
|
16
|
+
// track.bodyHtml = document.documentElement.outerHTML.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');;
|
|
17
|
+
|
|
18
|
+
fetch(window.FTMOCKS_CONFIG.record_events_url, {
|
|
19
|
+
method: "POST",
|
|
20
|
+
headers: {
|
|
21
|
+
"Content-Type": "application/json",
|
|
22
|
+
},
|
|
23
|
+
body: JSON.stringify(track),
|
|
24
|
+
}).then((response) => response.json());
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
window.fetch = async function (url, options = {}) {
|
|
28
|
+
const method = options.method || "GET";
|
|
29
|
+
const body = options.body;
|
|
30
|
+
const headers = options.headers || {};
|
|
31
|
+
const queryString = url.includes("?") ? url.split("?")[1] : null;
|
|
32
|
+
const response = await originalFetch(url, options);
|
|
33
|
+
const ftMocksURL = new URL(window.FTMOCKS_CONFIG.record_mocks_url);
|
|
34
|
+
const currentURL = new URL(
|
|
35
|
+
url.startsWith("http") ? url : `http://something/${url}`
|
|
36
|
+
);
|
|
37
|
+
const clonedResponse = response.clone();
|
|
38
|
+
clonedResponse.text().then((text) => {
|
|
39
|
+
if (ftMocksURL.hostname !== currentURL.hostname) {
|
|
40
|
+
const mockResponse = {
|
|
41
|
+
url: url,
|
|
42
|
+
time: new Date().toString(),
|
|
43
|
+
method: method,
|
|
44
|
+
request: {
|
|
45
|
+
headers: headers,
|
|
46
|
+
queryString: queryString,
|
|
47
|
+
postData: {
|
|
48
|
+
mimeType: headers["Content-Type"] || null,
|
|
49
|
+
text: body,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
response: {
|
|
53
|
+
status: response.status,
|
|
54
|
+
headers: Array.from(clonedResponse.headers.entries()),
|
|
55
|
+
content: text,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
fetch(window.FTMOCKS_CONFIG.record_mocks_url, {
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers: {
|
|
61
|
+
"Content-Type": "application/json",
|
|
62
|
+
},
|
|
63
|
+
body: JSON.stringify(mockResponse),
|
|
64
|
+
}).then((response) => response.json());
|
|
65
|
+
addTrack({
|
|
66
|
+
type: mockResponse.method,
|
|
67
|
+
target: mockResponse.url,
|
|
68
68
|
});
|
|
69
|
-
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
return response;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Intercept XMLHttpRequest
|
|
75
|
+
const originalXHR = window.XMLHttpRequest;
|
|
76
|
+
|
|
77
|
+
function MockXHR() {
|
|
78
|
+
const xhr = new originalXHR();
|
|
79
|
+
const originalOpen = xhr.open;
|
|
80
|
+
const originalSend = xhr.send;
|
|
81
|
+
const originalSetRequestHeader = xhr.setRequestHeader;
|
|
82
|
+
let requestDetails = {
|
|
83
|
+
headers: {},
|
|
70
84
|
};
|
|
71
|
-
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
},
|
|
122
|
-
response: {
|
|
123
|
-
status: xhr.status,
|
|
124
|
-
headers: xhr.getAllResponseHeaders(),
|
|
125
|
-
content: xhr.responseText
|
|
126
|
-
},
|
|
127
|
-
};
|
|
128
|
-
fetch(window.FTMOCKS_CONFIG.record_mocks_url, {
|
|
129
|
-
method: 'POST',
|
|
130
|
-
headers: {
|
|
131
|
-
'Content-Type': 'application/json',
|
|
132
|
-
},
|
|
133
|
-
body: JSON.stringify(mockResponse),
|
|
134
|
-
}).then(response => response.json());
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (originalOnReadyStateChange) originalOnReadyStateChange.apply(xhr, arguments);
|
|
85
|
+
|
|
86
|
+
// Override 'open' method
|
|
87
|
+
xhr.open = function (method, url, async, user, password) {
|
|
88
|
+
requestDetails.method = method;
|
|
89
|
+
requestDetails.url = url;
|
|
90
|
+
requestDetails.async = async;
|
|
91
|
+
requestDetails.user = user;
|
|
92
|
+
requestDetails.password = password;
|
|
93
|
+
requestDetails.queryString = url.includes("?") ? url.split("?")[1] : null;
|
|
94
|
+
originalOpen.apply(xhr, arguments);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Override 'setRequestHeader' to log headers
|
|
98
|
+
xhr.setRequestHeader = function (header, value) {
|
|
99
|
+
requestDetails.headers[header] = value;
|
|
100
|
+
originalSetRequestHeader.apply(xhr, arguments);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Override 'send' method
|
|
104
|
+
xhr.send = function (body) {
|
|
105
|
+
requestDetails.body = body;
|
|
106
|
+
const originalOnReadyStateChange = xhr.onreadystatechange;
|
|
107
|
+
xhr.onreadystatechange = function () {
|
|
108
|
+
if (xhr.readyState === 4) {
|
|
109
|
+
// Complete
|
|
110
|
+
const ftMocksURL = new URL(window.FTMOCKS_CONFIG.record_mocks_url);
|
|
111
|
+
const currentURL = new URL(
|
|
112
|
+
requestDetails.url.startsWith("http")
|
|
113
|
+
? requestDetails.url
|
|
114
|
+
: `http://something/${requestDetails.url}`
|
|
115
|
+
);
|
|
116
|
+
if (ftMocksURL.hostname !== currentURL.hostname) {
|
|
117
|
+
const mockResponse = {
|
|
118
|
+
url: requestDetails.url,
|
|
119
|
+
time: new Date().toString(),
|
|
120
|
+
method: requestDetails.method,
|
|
121
|
+
request: {
|
|
122
|
+
headers: requestDetails.headers,
|
|
123
|
+
queryString: requestDetails.queryString,
|
|
124
|
+
postData: {
|
|
125
|
+
mimeType: requestDetails.headers["Content-Type"] || null,
|
|
126
|
+
text: requestDetails.body,
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
response: {
|
|
130
|
+
status: xhr.status,
|
|
131
|
+
headers: xhr.getAllResponseHeaders(),
|
|
132
|
+
content: xhr.responseText,
|
|
133
|
+
},
|
|
139
134
|
};
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const generateXPathWithNearestParentId = (element) => {
|
|
150
|
-
let path = '';
|
|
151
|
-
let nearestParentId = null;
|
|
152
|
-
|
|
153
|
-
// Check if the current element's has an ID
|
|
154
|
-
if (element.id) {
|
|
155
|
-
nearestParentId = element.id;
|
|
135
|
+
fetch(window.FTMOCKS_CONFIG.record_mocks_url, {
|
|
136
|
+
method: "POST",
|
|
137
|
+
headers: {
|
|
138
|
+
"Content-Type": "application/json",
|
|
139
|
+
},
|
|
140
|
+
body: JSON.stringify(mockResponse),
|
|
141
|
+
}).then((response) => response.json());
|
|
142
|
+
}
|
|
156
143
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
let sibling = element.previousElementSibling;
|
|
162
|
-
|
|
163
|
-
while (sibling) {
|
|
164
|
-
if (sibling.tagName.toLowerCase() === tagName) {
|
|
165
|
-
index += 1;
|
|
166
|
-
}
|
|
167
|
-
sibling = sibling.previousElementSibling;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (index === 1) {
|
|
171
|
-
path = `/${tagName}${path}`;
|
|
172
|
-
} else {
|
|
173
|
-
path = `/${tagName}[${index}]${path}`;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Check if the current element's parent has an ID
|
|
177
|
-
if (element.parentElement && element.parentElement.id) {
|
|
178
|
-
nearestParentId = element.parentElement.id;
|
|
179
|
-
break; // Stop searching when we find the nearest parent with an ID
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
element = element.parentElement;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (nearestParentId) {
|
|
186
|
-
path = `//*[@id='${nearestParentId}']${path}`;
|
|
187
|
-
return path;
|
|
188
|
-
}
|
|
189
|
-
return null; // No parent with an ID found
|
|
144
|
+
if (originalOnReadyStateChange)
|
|
145
|
+
originalOnReadyStateChange.apply(xhr, arguments);
|
|
146
|
+
};
|
|
147
|
+
originalSend.apply(xhr, arguments);
|
|
190
148
|
};
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
149
|
+
|
|
150
|
+
return xhr;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
window.XMLHttpRequest = MockXHR;
|
|
154
|
+
|
|
155
|
+
const generateXPathWithNearestParentId = (element) => {
|
|
156
|
+
let path = "";
|
|
157
|
+
let nearestParentId = null;
|
|
158
|
+
|
|
159
|
+
// Check if the current element's has an ID
|
|
160
|
+
if (element.id) {
|
|
161
|
+
nearestParentId = element.id;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
while (!nearestParentId && element !== document.body && element) {
|
|
165
|
+
const tagName = element.tagName.toLowerCase();
|
|
166
|
+
let index = 1;
|
|
167
|
+
let sibling = element.previousElementSibling;
|
|
168
|
+
|
|
169
|
+
while (sibling) {
|
|
170
|
+
if (sibling.tagName.toLowerCase() === tagName) {
|
|
171
|
+
index += 1;
|
|
202
172
|
}
|
|
203
|
-
|
|
204
|
-
|
|
173
|
+
sibling = sibling.previousElementSibling;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (index === 1) {
|
|
177
|
+
path = `/${tagName}${path}`;
|
|
178
|
+
} else {
|
|
179
|
+
path = `/${tagName}[${index}]${path}`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Check if the current element's parent has an ID
|
|
183
|
+
if (element.parentElement && element.parentElement.id) {
|
|
184
|
+
nearestParentId = element.parentElement.id;
|
|
185
|
+
break; // Stop searching when we find the nearest parent with an ID
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
element = element.parentElement;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (nearestParentId) {
|
|
192
|
+
path = `//*[@id='${nearestParentId}']${path}`;
|
|
193
|
+
return path;
|
|
194
|
+
}
|
|
195
|
+
return null; // No parent with an ID found
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const handleMouseEvent = (type, limit) => (event) => {
|
|
199
|
+
const target = generateXPathWithNearestParentId(event.target);
|
|
200
|
+
const track = {
|
|
201
|
+
id: recordedTracks.length
|
|
202
|
+
? recordedTracks[recordedTracks.length - 1].id + 1
|
|
203
|
+
: 1,
|
|
204
|
+
type,
|
|
205
|
+
target,
|
|
206
|
+
time: new Date(),
|
|
205
207
|
};
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
208
|
+
if (recordedTracks.length > limit + 1) {
|
|
209
|
+
recordedTracks.shift();
|
|
210
|
+
}
|
|
211
|
+
recordedTracks.push(track);
|
|
212
|
+
addTrack(track);
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const handleChange = (limit) => (event) => {
|
|
216
|
+
const prevCommand =
|
|
217
|
+
recordedTracks && recordedTracks.length
|
|
218
|
+
? recordedTracks[recordedTracks.length - 1]
|
|
219
|
+
: null;
|
|
220
|
+
const target = generateXPathWithNearestParentId(event.target);
|
|
221
|
+
const track = {
|
|
222
|
+
id: recordedTracks.length
|
|
223
|
+
? recordedTracks[recordedTracks.length - 1].id + 1
|
|
224
|
+
: 1,
|
|
225
|
+
type: "change",
|
|
226
|
+
target,
|
|
227
|
+
value: event.target.value,
|
|
228
|
+
time: new Date(),
|
|
229
|
+
};
|
|
230
|
+
if (recordedTracks.length > limit + 1) {
|
|
231
|
+
recordedTracks.shift();
|
|
232
|
+
}
|
|
233
|
+
if (
|
|
234
|
+
prevCommand &&
|
|
235
|
+
prevCommand.type === "change" &&
|
|
236
|
+
prevCommand.target === target
|
|
237
|
+
) {
|
|
238
|
+
recordedTracks.pop();
|
|
239
|
+
}
|
|
240
|
+
recordedTracks.push(track);
|
|
241
|
+
addTrack(track);
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const handleDocumentLoad = (limit) => () => {
|
|
245
|
+
let oldHref = document.location.href;
|
|
246
|
+
const body = document.querySelector("body");
|
|
247
|
+
const observer = new MutationObserver((mutations) => {
|
|
248
|
+
if (oldHref !== document.location.href) {
|
|
249
|
+
oldHref = document.location.href;
|
|
211
250
|
const track = {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
251
|
+
id: recordedTracks.length
|
|
252
|
+
? recordedTracks[recordedTracks.length - 1].id + 1
|
|
253
|
+
: 1,
|
|
254
|
+
type: "url",
|
|
255
|
+
value: oldHref,
|
|
256
|
+
time: new Date(),
|
|
217
257
|
};
|
|
218
|
-
if(recordedTracks.length > limit + 1) {
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
if (
|
|
222
|
-
prevCommand &&
|
|
223
|
-
prevCommand.type === 'change' &&
|
|
224
|
-
prevCommand.target === target
|
|
225
|
-
) {
|
|
226
|
-
recordedTracks.pop();
|
|
258
|
+
if (recordedTracks.length > limit + 1) {
|
|
259
|
+
recordedTracks.shift();
|
|
227
260
|
}
|
|
228
261
|
recordedTracks.push(track);
|
|
229
262
|
addTrack(track);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
observer.observe(body, { childList: true, subtree: true });
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const clearTracks = () => {
|
|
269
|
+
recordedTracks = [];
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const getAllTracks = () => {
|
|
273
|
+
return recordedTracks;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const initTracks = (
|
|
277
|
+
initInfo = {
|
|
278
|
+
events: ["click", "change", "url", "dblclick", "contextmenu"],
|
|
279
|
+
limit: 100,
|
|
280
|
+
}
|
|
281
|
+
) => {
|
|
282
|
+
const { events, limit } = initInfo;
|
|
283
|
+
const mouseEvents = {
|
|
284
|
+
click: handleMouseEvent("click", limit),
|
|
285
|
+
contextmenu: handleMouseEvent("contextmenu", limit),
|
|
286
|
+
dblclick: handleMouseEvent("dblclick", limit),
|
|
287
|
+
mousedown: handleMouseEvent("mousedown", limit),
|
|
288
|
+
mouseenter: handleMouseEvent("mouseenter", limit),
|
|
289
|
+
mouseleave: handleMouseEvent("mouseleave", limit),
|
|
290
|
+
mousemove: handleMouseEvent("mousemove", limit),
|
|
291
|
+
mouseout: handleMouseEvent("mouseout", limit),
|
|
292
|
+
mouseover: handleMouseEvent("mouseover", limit),
|
|
293
|
+
mouseup: handleMouseEvent("mouseup", limit),
|
|
230
294
|
};
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
};
|
|
244
|
-
if(recordedTracks.length > limit + 1) {
|
|
245
|
-
recordedTracks.shift();
|
|
246
|
-
}
|
|
247
|
-
recordedTracks.push(track);
|
|
248
|
-
addTrack(track);
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
observer.observe(body, { childList: true, subtree: true });
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
const clearTracks = () => {
|
|
255
|
-
recordedTracks = [];
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
const getAllTracks = () => {
|
|
259
|
-
return recordedTracks;
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
const initTracks = (initInfo = {events: ['click', 'change', 'url', 'dblclick', 'contextmenu'], limit: 100}) => {
|
|
263
|
-
const {events, limit} = initInfo;
|
|
264
|
-
const mouseEvents = {
|
|
265
|
-
click: handleMouseEvent('click', limit),
|
|
266
|
-
contextmenu: handleMouseEvent('contextmenu', limit),
|
|
267
|
-
dblclick: handleMouseEvent('dblclick', limit),
|
|
268
|
-
mousedown: handleMouseEvent('mousedown', limit),
|
|
269
|
-
mouseenter: handleMouseEvent('mouseenter', limit),
|
|
270
|
-
mouseleave: handleMouseEvent('mouseleave', limit),
|
|
271
|
-
mousemove: handleMouseEvent('mousemove', limit),
|
|
272
|
-
mouseout: handleMouseEvent('mouseout', limit),
|
|
273
|
-
mouseover: handleMouseEvent('mouseover', limit),
|
|
274
|
-
mouseup: handleMouseEvent('mouseup', limit),
|
|
275
|
-
};
|
|
276
|
-
events.forEach(e => {
|
|
277
|
-
if(e === 'url') {
|
|
278
|
-
window.onload = handleDocumentLoad(limit);
|
|
279
|
-
} else if (e === 'change') {
|
|
280
|
-
document.addEventListener('input', handleChange(limit));
|
|
281
|
-
} else {
|
|
282
|
-
document.addEventListener(e, mouseEvents[e]);
|
|
283
|
-
}
|
|
284
|
-
});
|
|
285
|
-
};
|
|
286
|
-
initTracks();
|
|
287
|
-
|
|
288
|
-
})();
|
|
295
|
+
events.forEach((e) => {
|
|
296
|
+
if (e === "url") {
|
|
297
|
+
window.onload = handleDocumentLoad(limit);
|
|
298
|
+
} else if (e === "change") {
|
|
299
|
+
document.addEventListener("input", handleChange(limit));
|
|
300
|
+
} else {
|
|
301
|
+
document.addEventListener(e, mouseEvents[e]);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
};
|
|
305
|
+
initTracks();
|
|
306
|
+
})();
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { getMockDir, nameToFolder } = require("./common-utils");
|
|
4
|
+
const { v4: uuidv4 } = require("uuid");
|
|
5
|
+
|
|
6
|
+
const createTest = async (ftmocksConifg, testName) => {
|
|
7
|
+
const testsPath = path.join(getMockDir(ftmocksConifg), "tests.json");
|
|
8
|
+
let tests = [];
|
|
9
|
+
try {
|
|
10
|
+
// Read existing tests
|
|
11
|
+
const testsData = fs.readFileSync(testsPath, "utf8");
|
|
12
|
+
tests = JSON.parse(testsData);
|
|
13
|
+
const etest = tests.find((tst) => tst.name === testName);
|
|
14
|
+
if (!etest) {
|
|
15
|
+
const newTest = {
|
|
16
|
+
id: uuidv4(),
|
|
17
|
+
name: testName,
|
|
18
|
+
};
|
|
19
|
+
tests.push(newTest);
|
|
20
|
+
fs.writeFileSync(testsPath, JSON.stringify(tests, null, 2));
|
|
21
|
+
const folderPath = path.join(
|
|
22
|
+
getMockDir(ftmocksConifg),
|
|
23
|
+
`test_${nameToFolder(testName)}`
|
|
24
|
+
);
|
|
25
|
+
const mockListFilePath = path.join(folderPath, "_mock_list.json");
|
|
26
|
+
fs.mkdir(folderPath, { recursive: true }, (err) => {
|
|
27
|
+
if (err) {
|
|
28
|
+
console.error("\x1b[31mError creating directory:\x1b[0m", err);
|
|
29
|
+
} else {
|
|
30
|
+
console.log("\x1b[32mDirectory created successfully!\x1b[0m");
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
await fs.appendFile(mockListFilePath, "[]", () => {
|
|
34
|
+
console.log("\x1b[32mmock list file created successfully\x1b[0m");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return newTest;
|
|
38
|
+
} else {
|
|
39
|
+
throw "Test already exists";
|
|
40
|
+
}
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error(`\x1b[31mError reading tests.json:\x1b[0m`, error);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
module.exports = {
|
|
48
|
+
createTest,
|
|
49
|
+
};
|