ftmocks-utils 1.0.2 → 1.0.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/index.js +4 -4
- package/src/recorder.js +280 -0
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -34,7 +34,7 @@ const areJsonEqual = (jsonObj1, jsonObj2) => {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
const getDefaultMockDataFromConfig = (testConfig) => {
|
|
37
|
-
const defaultPath = path.join(testConfig.MOCK_DIR,
|
|
37
|
+
const defaultPath = path.join(testConfig.MOCK_DIR, 'defaultMocks');
|
|
38
38
|
|
|
39
39
|
try {
|
|
40
40
|
const defaultData = fs.readFileSync(defaultPath, 'utf8');
|
|
@@ -42,7 +42,7 @@ try {
|
|
|
42
42
|
|
|
43
43
|
// Read and attach mock data for each entry in parsedData
|
|
44
44
|
parsedData.forEach(entry => {
|
|
45
|
-
const mockFilePath = path.join(testConfig.MOCK_DIR,
|
|
45
|
+
const mockFilePath = path.join(testConfig.MOCK_DIR, 'defaultMocks', `mock_${entry.id}.json`);;
|
|
46
46
|
try {
|
|
47
47
|
const mockData = fs.readFileSync(mockFilePath, 'utf8');
|
|
48
48
|
entry.fileContent = JSON.parse(mockData);
|
|
@@ -53,7 +53,7 @@ try {
|
|
|
53
53
|
});
|
|
54
54
|
return parsedData;
|
|
55
55
|
} catch (error) {
|
|
56
|
-
console.error(`Error reading or parsing
|
|
56
|
+
console.error(`Error reading or parsing default.json:`, error);
|
|
57
57
|
return [];
|
|
58
58
|
}
|
|
59
59
|
}
|
|
@@ -69,7 +69,7 @@ const loadMockDataFromConfig = (testConfig, _testName) => {
|
|
|
69
69
|
const config = JSON.parse(configData);
|
|
70
70
|
testName = config.testName;
|
|
71
71
|
}
|
|
72
|
-
// Read the tests from testConfig
|
|
72
|
+
// Read the tests from testConfig
|
|
73
73
|
const mocksPath = path.join(testConfig.MOCK_DIR, `test_${nameToFolder(testName)}`, '_mock_list.json');
|
|
74
74
|
const mocksData = fs.readFileSync(mocksPath, 'utf8');
|
|
75
75
|
const mocks = JSON.parse(mocksData);
|
package/src/recorder.js
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
// Intercept Fetch API
|
|
3
|
+
const originalFetch = window.fetch;
|
|
4
|
+
const recordedTracks = [];
|
|
5
|
+
|
|
6
|
+
const addTrack = track => {
|
|
7
|
+
track.id = recordedTracks.length ? recordedTracks[recordedTracks.length - 1].id + 1 : 1;
|
|
8
|
+
track.time = new Date();
|
|
9
|
+
fetch(window.FTMOCKS_CONFIG.record_events_url, {
|
|
10
|
+
method: 'POST',
|
|
11
|
+
headers: {
|
|
12
|
+
'Content-Type': 'application/json',
|
|
13
|
+
},
|
|
14
|
+
body: JSON.stringify(track),
|
|
15
|
+
}).then(response => response.json());
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
window.fetch = async function (url, options = {}) {
|
|
20
|
+
const method = options.method || 'GET';
|
|
21
|
+
const body = options.body;
|
|
22
|
+
const headers = options.headers || {};
|
|
23
|
+
const queryString = url.includes('?') ? url.split('?')[1] : null;
|
|
24
|
+
const response = await originalFetch(url, options);
|
|
25
|
+
const ftMocksURL = new URL(window.FTMOCKS_CONFIG.record_mocks_url);
|
|
26
|
+
const currentURL = new URL(url.startsWith('http') ? url : `http://something/${url}`);
|
|
27
|
+
const clonedResponse = response.clone();
|
|
28
|
+
clonedResponse.text().then((text) => {
|
|
29
|
+
if (ftMocksURL.hostname !== currentURL.hostname) {
|
|
30
|
+
const mockResponse = {
|
|
31
|
+
url: url,
|
|
32
|
+
time: new Date().toString(),
|
|
33
|
+
method: method,
|
|
34
|
+
request: {
|
|
35
|
+
headers: headers,
|
|
36
|
+
queryString: queryString,
|
|
37
|
+
postData: {
|
|
38
|
+
mimeType: headers['Content-Type'] || null,
|
|
39
|
+
text: body
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
response: {
|
|
43
|
+
status: response.status,
|
|
44
|
+
headers: Array.from(clonedResponse.headers.entries()),
|
|
45
|
+
content: text
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
fetch(window.FTMOCKS_CONFIG.record_mocks_url, {
|
|
49
|
+
method: 'POST',
|
|
50
|
+
headers: {
|
|
51
|
+
'Content-Type': 'application/json',
|
|
52
|
+
},
|
|
53
|
+
body: JSON.stringify(mockResponse),
|
|
54
|
+
}).then(response => response.json());
|
|
55
|
+
addTrack({
|
|
56
|
+
type: mockResponse.method,
|
|
57
|
+
target: mockResponse.url,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
return response;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Intercept XMLHttpRequest
|
|
65
|
+
const originalXHR = window.XMLHttpRequest;
|
|
66
|
+
|
|
67
|
+
function MockXHR() {
|
|
68
|
+
const xhr = new originalXHR();
|
|
69
|
+
const originalOpen = xhr.open;
|
|
70
|
+
const originalSend = xhr.send;
|
|
71
|
+
const originalSetRequestHeader = xhr.setRequestHeader;
|
|
72
|
+
let requestDetails = {
|
|
73
|
+
headers: {},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Override 'open' method
|
|
77
|
+
xhr.open = function (method, url, async, user, password) {
|
|
78
|
+
requestDetails.method = method;
|
|
79
|
+
requestDetails.url = url;
|
|
80
|
+
requestDetails.async = async;
|
|
81
|
+
requestDetails.user = user;
|
|
82
|
+
requestDetails.password = password;
|
|
83
|
+
requestDetails.queryString = url.includes('?') ? url.split('?')[1] : null;
|
|
84
|
+
originalOpen.apply(xhr, arguments);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Override 'setRequestHeader' to log headers
|
|
88
|
+
xhr.setRequestHeader = function (header, value) {
|
|
89
|
+
requestDetails.headers[header] = value;
|
|
90
|
+
originalSetRequestHeader.apply(xhr, arguments);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Override 'send' method
|
|
94
|
+
xhr.send = function (body) {
|
|
95
|
+
requestDetails.body = body;
|
|
96
|
+
const originalOnReadyStateChange = xhr.onreadystatechange;
|
|
97
|
+
xhr.onreadystatechange = function () {
|
|
98
|
+
if (xhr.readyState === 4) { // Complete
|
|
99
|
+
const ftMocksURL = new URL(window.FTMOCKS_CONFIG.record_mocks_url);
|
|
100
|
+
const currentURL = new URL(requestDetails.url.startsWith('http') ? requestDetails.url : `http://something/${requestDetails.url}`);
|
|
101
|
+
if (ftMocksURL.hostname !== currentURL.hostname) {
|
|
102
|
+
const mockResponse = {
|
|
103
|
+
url: requestDetails.url,
|
|
104
|
+
time: new Date().toString(),
|
|
105
|
+
method: requestDetails.method,
|
|
106
|
+
request: {
|
|
107
|
+
headers: requestDetails.headers,
|
|
108
|
+
queryString: requestDetails.queryString,
|
|
109
|
+
postData: {
|
|
110
|
+
mimeType: requestDetails.headers['Content-Type'] || null,
|
|
111
|
+
text: requestDetails.body
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
response: {
|
|
115
|
+
status: xhr.status,
|
|
116
|
+
headers: xhr.getAllResponseHeaders(),
|
|
117
|
+
content: xhr.responseText
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
fetch(window.FTMOCKS_CONFIG.record_mocks_url, {
|
|
121
|
+
method: 'POST',
|
|
122
|
+
headers: {
|
|
123
|
+
'Content-Type': 'application/json',
|
|
124
|
+
},
|
|
125
|
+
body: JSON.stringify(mockResponse),
|
|
126
|
+
}).then(response => response.json());
|
|
127
|
+
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (originalOnReadyStateChange) originalOnReadyStateChange.apply(xhr, arguments);
|
|
131
|
+
};
|
|
132
|
+
originalSend.apply(xhr, arguments);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
return xhr;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
window.XMLHttpRequest = MockXHR;
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
const generateXPathWithNearestParentId = (element) => {
|
|
142
|
+
let path = '';
|
|
143
|
+
let nearestParentId = null;
|
|
144
|
+
|
|
145
|
+
// Check if the current element's has an ID
|
|
146
|
+
if (element.id) {
|
|
147
|
+
nearestParentId = element.id;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
while (!nearestParentId && element !== document.body && element) {
|
|
151
|
+
const tagName = element.tagName.toLowerCase();
|
|
152
|
+
let index = 1;
|
|
153
|
+
let sibling = element.previousElementSibling;
|
|
154
|
+
|
|
155
|
+
while (sibling) {
|
|
156
|
+
if (sibling.tagName.toLowerCase() === tagName) {
|
|
157
|
+
index += 1;
|
|
158
|
+
}
|
|
159
|
+
sibling = sibling.previousElementSibling;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (index === 1) {
|
|
163
|
+
path = `/${tagName}${path}`;
|
|
164
|
+
} else {
|
|
165
|
+
path = `/${tagName}[${index}]${path}`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Check if the current element's parent has an ID
|
|
169
|
+
if (element.parentElement && element.parentElement.id) {
|
|
170
|
+
nearestParentId = element.parentElement.id;
|
|
171
|
+
break; // Stop searching when we find the nearest parent with an ID
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
element = element.parentElement;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (nearestParentId) {
|
|
178
|
+
path = `//*[@id='${nearestParentId}']${path}`;
|
|
179
|
+
return path;
|
|
180
|
+
}
|
|
181
|
+
return null; // No parent with an ID found
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const handleMouseEvent = (type, limit) => event => {
|
|
185
|
+
const target = generateXPathWithNearestParentId(event.target);
|
|
186
|
+
const track = {
|
|
187
|
+
id: recordedTracks.length ? recordedTracks[recordedTracks.length - 1].id + 1 : 1,
|
|
188
|
+
type,
|
|
189
|
+
target,
|
|
190
|
+
time: new Date(),
|
|
191
|
+
};
|
|
192
|
+
if(recordedTracks.length > limit + 1) {
|
|
193
|
+
recordedTracks.shift();
|
|
194
|
+
}
|
|
195
|
+
recordedTracks.push(track);
|
|
196
|
+
addTrack(track);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const handleChange = limit => event => {
|
|
200
|
+
const prevCommand =
|
|
201
|
+
recordedTracks && recordedTracks.length ? recordedTracks[recordedTracks.length - 1] : null;
|
|
202
|
+
const target = generateXPathWithNearestParentId(event.target);
|
|
203
|
+
const track = {
|
|
204
|
+
id: recordedTracks.length ? recordedTracks[recordedTracks.length - 1].id + 1 : 1,
|
|
205
|
+
type: 'change',
|
|
206
|
+
target,
|
|
207
|
+
value: event.target.value,
|
|
208
|
+
time: new Date(),
|
|
209
|
+
};
|
|
210
|
+
if(recordedTracks.length > limit + 1) {
|
|
211
|
+
recordedTracks.shift();
|
|
212
|
+
}
|
|
213
|
+
if (
|
|
214
|
+
prevCommand &&
|
|
215
|
+
prevCommand.type === 'change' &&
|
|
216
|
+
prevCommand.target === target
|
|
217
|
+
) {
|
|
218
|
+
recordedTracks.pop();
|
|
219
|
+
}
|
|
220
|
+
recordedTracks.push(track);
|
|
221
|
+
addTrack(track);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const handleDocumentLoad = limit => () => {
|
|
225
|
+
let oldHref = document.location.href;
|
|
226
|
+
const body = document.querySelector('body');
|
|
227
|
+
const observer = new MutationObserver(mutations => {
|
|
228
|
+
if (oldHref !== document.location.href) {
|
|
229
|
+
oldHref = document.location.href;
|
|
230
|
+
const track = {
|
|
231
|
+
id: recordedTracks.length ? recordedTracks[recordedTracks.length - 1].id + 1 : 1,
|
|
232
|
+
type: 'url',
|
|
233
|
+
value: oldHref,
|
|
234
|
+
time: new Date(),
|
|
235
|
+
};
|
|
236
|
+
if(recordedTracks.length > limit + 1) {
|
|
237
|
+
recordedTracks.shift();
|
|
238
|
+
}
|
|
239
|
+
recordedTracks.push(track);
|
|
240
|
+
addTrack(track);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
observer.observe(body, { childList: true, subtree: true });
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const clearTracks = () => {
|
|
247
|
+
recordedTracks = [];
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const getAllTracks = () => {
|
|
251
|
+
return recordedTracks;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const initTracks = (initInfo = {events: ['click', 'change', 'url', 'dblclick', 'contextmenu'], limit: 100}) => {
|
|
255
|
+
const {events, limit} = initInfo;
|
|
256
|
+
const mouseEvents = {
|
|
257
|
+
click: handleMouseEvent('click', limit),
|
|
258
|
+
contextmenu: handleMouseEvent('contextmenu', limit),
|
|
259
|
+
dblclick: handleMouseEvent('dblclick', limit),
|
|
260
|
+
mousedown: handleMouseEvent('mousedown', limit),
|
|
261
|
+
mouseenter: handleMouseEvent('mouseenter', limit),
|
|
262
|
+
mouseleave: handleMouseEvent('mouseleave', limit),
|
|
263
|
+
mousemove: handleMouseEvent('mousemove', limit),
|
|
264
|
+
mouseout: handleMouseEvent('mouseout', limit),
|
|
265
|
+
mouseover: handleMouseEvent('mouseover', limit),
|
|
266
|
+
mouseup: handleMouseEvent('mouseup', limit),
|
|
267
|
+
};
|
|
268
|
+
events.forEach(e => {
|
|
269
|
+
if(e === 'url') {
|
|
270
|
+
window.onload = handleDocumentLoad(limit);
|
|
271
|
+
} else if (e === 'change') {
|
|
272
|
+
document.addEventListener('input', handleChange(limit));
|
|
273
|
+
} else {
|
|
274
|
+
document.addEventListener(e, mouseEvents[e]);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
};
|
|
278
|
+
initTracks();
|
|
279
|
+
|
|
280
|
+
})();
|