@dev-blinq/cucumber_client 1.0.1311-dev → 1.0.1313-dev
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/bin/assets/bundled_scripts/recorder.js +57 -57
- package/bin/assets/scripts/unique_locators.js +195 -169
- package/bin/client/code_gen/playwright_codeget.js +23 -4
- package/bin/client/recorderv3/bvt_recorder.js +36 -7
- package/bin/client/recorderv3/index.js +37 -1
- package/bin/client/recorderv3/network.js +299 -0
- package/bin/client/recorderv3/step_runner.js +116 -2
- package/bin/client/recorderv3/step_utils.js +70 -2
- package/package.json +2 -2
|
@@ -34,6 +34,11 @@ function cssEscapeCharacter(s, i) {
|
|
|
34
34
|
return s.charAt(i);
|
|
35
35
|
return "\\" + s.charAt(i);
|
|
36
36
|
}
|
|
37
|
+
function escapeRegexForSelector(re) {
|
|
38
|
+
if (re.unicode || re.unicodeSets)
|
|
39
|
+
return String(re);
|
|
40
|
+
return String(re).replace(/(^|[^\\])(\\\\)*(["'`])/g, "$1$2\\$3").replace(/>>/g, "\\>\\>");
|
|
41
|
+
}
|
|
37
42
|
class LocatorGenerator {
|
|
38
43
|
constructor(injectedScript, options = {}) {
|
|
39
44
|
this.locatorStrategies = {
|
|
@@ -213,102 +218,107 @@ class LocatorGenerator {
|
|
|
213
218
|
return [];
|
|
214
219
|
}
|
|
215
220
|
const result = [];
|
|
216
|
-
|
|
217
|
-
for (const locator of locators) {
|
|
218
|
-
const selector = locator.css;
|
|
219
|
-
if (!selector || typeof selector !== "string") {
|
|
220
|
-
console.error("Locator must have a valid css selector");
|
|
221
|
-
continue;
|
|
222
|
-
}
|
|
223
|
-
const parseResult = this.injectedScript.parseSelector(selector);
|
|
224
|
-
const parts = parseResult.parts;
|
|
225
|
-
if (!parts || !Array.isArray(parts) || parts.length === 0) {
|
|
226
|
-
console.error("Locator must have a valid css selector");
|
|
227
|
-
continue;
|
|
228
|
-
}
|
|
229
|
-
// ignore parts.length < 3
|
|
230
|
-
if (parts.length < 3) {
|
|
231
|
-
// console.warn("Locator must have at least 3 parts to be a context locator");
|
|
232
|
-
continue;
|
|
233
|
-
}
|
|
234
|
-
const firstPart = parts[0];
|
|
235
|
-
if (firstPart.name !== "internal:text") {
|
|
236
|
-
// console.warn("Locator must have internal:text as the first part to be a context locator");
|
|
237
|
-
continue;
|
|
238
|
-
}
|
|
239
|
-
const textBody = firstPart.body;
|
|
240
|
-
if (!textBody || typeof textBody !== "string" || textBody.length === 0) {
|
|
241
|
-
console.error("Locator must have a valid text in the first part to be a context locator");
|
|
242
|
-
continue;
|
|
243
|
-
}
|
|
244
|
-
const secondPart = parts[1];
|
|
245
|
-
if (secondPart.name !== "xpath") {
|
|
246
|
-
continue;
|
|
247
|
-
}
|
|
248
|
-
const xpath = secondPart.body;
|
|
249
|
-
if (!xpath || typeof xpath !== "string" || xpath.length === 0) {
|
|
250
|
-
// console.error("Locator must have a valid xpath in the second part to be a context locator");
|
|
251
|
-
continue;
|
|
252
|
-
}
|
|
253
|
-
const climbString = secondPart.body;
|
|
254
|
-
if (!climbString || typeof climbString !== "string" || climbString.length === 0) {
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
257
|
-
const climbStringRegex = /(\.\.)(\/\.\.)*/;
|
|
258
|
-
try {
|
|
259
|
-
const match = climbStringRegex.test(climbString);
|
|
260
|
-
if (match) {
|
|
261
|
-
const climbCount = climbString.split("..").length - 1;
|
|
262
|
-
const lastIndex = selector.indexOf(climbString);
|
|
263
|
-
const restOfSelector = selector.substring(lastIndex + climbString.length + 3).trim();
|
|
264
|
-
if (restOfSelector.length === 0) {
|
|
265
|
-
// console.warn("Locator must have a valid rest of selector after the xpath part to
|
|
266
|
-
// be a context locator");
|
|
267
|
-
continue;
|
|
268
|
-
}
|
|
221
|
+
try {
|
|
269
222
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
223
|
+
const textSet = new Set();
|
|
224
|
+
for (const locator of locators) {
|
|
225
|
+
const selector = locator.css;
|
|
226
|
+
if (!selector || typeof selector !== "string") {
|
|
227
|
+
console.error("Locator must have a valid css selector");
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
const parseResult = this.injectedScript.parseSelector(selector);
|
|
231
|
+
const parts = parseResult.parts;
|
|
232
|
+
if (!parts || !Array.isArray(parts) || parts.length === 0) {
|
|
233
|
+
console.error("Locator must have a valid css selector");
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
// ignore parts.length < 3
|
|
237
|
+
if (parts.length < 3) {
|
|
238
|
+
// console.warn("Locator must have at least 3 parts to be a context locator");
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
const firstPart = parts[0];
|
|
242
|
+
if (firstPart.name !== "internal:text") {
|
|
243
|
+
// console.warn("Locator must have internal:text as the first part to be a context locator");
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const textBody = firstPart.body;
|
|
247
|
+
if (!textBody || typeof textBody !== "string" || textBody.length === 0) {
|
|
248
|
+
console.error("Locator must have a valid text in the first part to be a context locator");
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
const secondPart = parts[1];
|
|
252
|
+
if (secondPart.name !== "xpath") {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
const xpath = secondPart.body;
|
|
256
|
+
if (!xpath || typeof xpath !== "string" || xpath.length === 0) {
|
|
257
|
+
// console.error("Locator must have a valid xpath in the second part to be a context locator");
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
const climbString = secondPart.body;
|
|
261
|
+
if (!climbString || typeof climbString !== "string" || climbString.length === 0) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
const climbStringRegex = /(\.\.)(\/\.\.)*/;
|
|
265
|
+
try {
|
|
266
|
+
const match = climbStringRegex.test(climbString);
|
|
267
|
+
if (match) {
|
|
268
|
+
const climbCount = climbString.split("..").length - 1;
|
|
269
|
+
const lastIndex = selector.indexOf(climbString);
|
|
270
|
+
const restOfSelector = selector.substring(lastIndex + climbString.length + 3).trim();
|
|
271
|
+
if (restOfSelector.length === 0) {
|
|
272
|
+
// console.warn("Locator must have a valid rest of selector after the xpath part to
|
|
273
|
+
// be a context locator");
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const textLocator = `internal:text=${textBody}`;
|
|
278
|
+
const elements = this.getMatchingElements(textLocator, {});
|
|
279
|
+
if (elements.length !== 1) {
|
|
280
|
+
// throw new Error("Context locator must have exactly one element matching the text part");
|
|
281
|
+
console.error("Context locator must have exactly one element matching the text part");
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
const textElement = elements[0];
|
|
285
|
+
// const text = this.PW.selectorUtils.elementText(textElement);
|
|
286
|
+
const text = this.injectedScript.utils.elementText(new Map(), textElement).full;
|
|
287
|
+
|
|
288
|
+
const fullSelector = `${textLocator} >> xpath=${xpath} >> ${restOfSelector}`;
|
|
289
|
+
const fullElements = this.getMatchingElements(fullSelector, {});
|
|
290
|
+
if (fullElements.length !== 1) {
|
|
291
|
+
// throw new Error("Context locator must have exactly one element matching the full selector");
|
|
292
|
+
console.error("Context locator must have exactly one element matching the full selector");
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
const fullElement = fullElements[0];
|
|
296
|
+
if (fullElement !== element) {
|
|
297
|
+
// throw new Error("Context locator must have the text element as the full element");
|
|
298
|
+
console.error("Context locator must have the text element as the full element");
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (!textSet.has(text)) {
|
|
302
|
+
textSet.add(text);
|
|
303
|
+
const loc = {
|
|
304
|
+
css: restOfSelector,
|
|
305
|
+
climb: climbCount,
|
|
306
|
+
text,
|
|
307
|
+
priority: 1,
|
|
308
|
+
};
|
|
309
|
+
if (locator.index !== undefined) {
|
|
310
|
+
loc.index = locator.index;
|
|
311
|
+
}
|
|
312
|
+
result.push(loc);
|
|
304
313
|
}
|
|
305
|
-
result.push(loc);
|
|
306
314
|
}
|
|
315
|
+
} catch (error) {
|
|
316
|
+
console.error("Error parsing climb string:", error);
|
|
317
|
+
continue;
|
|
307
318
|
}
|
|
308
|
-
} catch (error) {
|
|
309
|
-
console.error("Error parsing climb string:", error);
|
|
310
|
-
continue;
|
|
311
319
|
}
|
|
320
|
+
} catch (error) {
|
|
321
|
+
console.error("Error generating context locators:", error);
|
|
312
322
|
}
|
|
313
323
|
// Sort by text length to prioritize shorter texts
|
|
314
324
|
result.sort((a, b) => a.text.length - b.text.length);
|
|
@@ -316,65 +326,71 @@ class LocatorGenerator {
|
|
|
316
326
|
}
|
|
317
327
|
getDigitIgnoreLocators(element, locators) {
|
|
318
328
|
const result = [];
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
for (const locator of locators) {
|
|
325
|
-
const selector = locator.css;
|
|
326
|
-
if (!selector || typeof selector !== "string") {
|
|
327
|
-
console.error("Locator must have a valid css selector");
|
|
328
|
-
continue;
|
|
329
|
-
}
|
|
330
|
-
const parseresult = this.injectedScript.parseSelector(selector);
|
|
331
|
-
const parts = parseresult.parts;
|
|
332
|
-
if (!parts || !Array.isArray(parts) || parts.length === 0) {
|
|
333
|
-
console.error("Locator must have a valid css selector");
|
|
334
|
-
continue;
|
|
329
|
+
try {
|
|
330
|
+
if (!locators || !Array.isArray(locators)) {
|
|
331
|
+
console.error("Locators must be an array");
|
|
332
|
+
return [];
|
|
335
333
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
if (
|
|
340
|
-
|
|
334
|
+
|
|
335
|
+
for (const locator of locators) {
|
|
336
|
+
const selector = locator.css;
|
|
337
|
+
if (!selector || typeof selector !== "string") {
|
|
338
|
+
console.error("Locator must have a valid css selector");
|
|
341
339
|
continue;
|
|
342
340
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
341
|
+
const parseresult = this.injectedScript.parseSelector(selector);
|
|
342
|
+
const parts = parseresult.parts;
|
|
343
|
+
if (!parts || !Array.isArray(parts) || parts.length === 0) {
|
|
344
|
+
console.error("Locator must have a valid css selector");
|
|
346
345
|
continue;
|
|
347
346
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
347
|
+
let finalSelector = "";
|
|
348
|
+
let hasDigitsInText = false;
|
|
349
|
+
for (const part of parts) {
|
|
350
|
+
if (part.name !== "internal:text") {
|
|
351
|
+
finalSelector += `${part.name === "css" ? "" : part.name + "="}${part.source} >> `;
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
if (typeof part.body !== "string" || part.body.length === 0) {
|
|
355
|
+
// console.error("Locator must have a valid text in the first part to be a digit ignore locator");
|
|
356
|
+
finalSelector += `${part.name === "css" ? "" : part.name + "="}${part.source} >> `;
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
const text = part.body;
|
|
360
|
+
const digitsRegex = /\d+/g;
|
|
361
|
+
hasDigitsInText = digitsRegex.test(text);
|
|
351
362
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if (!hasDigitsInText) {
|
|
357
|
-
continue;
|
|
358
|
-
}
|
|
359
|
-
if (finalSelector.endsWith(` >> `)) {
|
|
360
|
-
finalSelector = finalSelector.slice(0, -4);
|
|
361
|
-
}
|
|
362
|
-
if (finalSelector) {
|
|
363
|
-
const elements = this.getMatchingElements(finalSelector, {});
|
|
364
|
-
if (elements.length !== 1) {
|
|
365
|
-
console.error("Digit ignore locator must have exactly one element matching the final selector");
|
|
366
|
-
continue;
|
|
363
|
+
let pattern = this.PW.stringUtils.escapeRegExp(text.substring(1, text.length - 2));
|
|
364
|
+
const re = new RegExp("^" + pattern + "$");
|
|
365
|
+
|
|
366
|
+
finalSelector += `internal:text=${escapeRegexForSelector(re)} >> `;
|
|
367
367
|
}
|
|
368
|
-
if (
|
|
369
|
-
console.error("Digit ignore locator must match the original element");
|
|
368
|
+
if (!hasDigitsInText) {
|
|
370
369
|
continue;
|
|
371
370
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
371
|
+
if (finalSelector.endsWith(` >> `)) {
|
|
372
|
+
finalSelector = finalSelector.slice(0, -4);
|
|
373
|
+
}
|
|
374
|
+
if (finalSelector) {
|
|
375
|
+
const elements = this.getMatchingElements(finalSelector, {});
|
|
376
|
+
if (elements.length !== 1) {
|
|
377
|
+
console.error("Digit ignore locator must have exactly one element matching the final selector");
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if (elements[0] !== element) {
|
|
381
|
+
console.error("Digit ignore locator must match the original element");
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
result.push({
|
|
385
|
+
css: finalSelector,
|
|
386
|
+
priority: locator.priority || 1,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
376
389
|
}
|
|
390
|
+
} catch (error) {
|
|
391
|
+
console.error("Error generating digit ignore locators:", error);
|
|
377
392
|
}
|
|
393
|
+
|
|
378
394
|
return result;
|
|
379
395
|
}
|
|
380
396
|
getTextwithIndexLocators(locators) {
|
|
@@ -383,17 +399,22 @@ class LocatorGenerator {
|
|
|
383
399
|
return [];
|
|
384
400
|
}
|
|
385
401
|
const result = [];
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
402
|
+
try {
|
|
403
|
+
for (const locator of locators) {
|
|
404
|
+
if (!locator || !locator.css || typeof locator.css !== "string") {
|
|
405
|
+
console.error("Locator must have a valid css selector");
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
const index = locator.index;
|
|
409
|
+
if (typeof index !== "number" || index < 0) {
|
|
410
|
+
// console.error("Locator must have a valid index");
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
result.push(locator);
|
|
395
414
|
}
|
|
396
|
-
|
|
415
|
+
|
|
416
|
+
} catch (error) {
|
|
417
|
+
console.error("Error getting text with index locators:", error);
|
|
397
418
|
}
|
|
398
419
|
return result;
|
|
399
420
|
}
|
|
@@ -417,28 +438,33 @@ class LocatorGenerator {
|
|
|
417
438
|
categorizeLocators(element, locators, options) {
|
|
418
439
|
const unique = [];
|
|
419
440
|
const nonUnique = [];
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
441
|
+
try {
|
|
442
|
+
|
|
443
|
+
for (const locator of locators) {
|
|
444
|
+
const elements = this.getMatchingElements(locator.css, options);
|
|
445
|
+
if (elements.length === 0) {
|
|
446
|
+
console.warn(`No elements found for locator: ${locator.css}`);
|
|
447
|
+
continue;
|
|
448
|
+
} else if (elements.length === 1) {
|
|
449
|
+
if (element === elements[0]) {
|
|
450
|
+
locator.priority = 1;
|
|
451
|
+
unique.push(locator);
|
|
452
|
+
} else if (element.contains(elements[0])) {
|
|
453
|
+
locator.priority = 1;
|
|
454
|
+
const climb = this.dom_Parent.getClimbCountToParent(elements[0], element);
|
|
455
|
+
const climbSelector = this.getXPathSelector(climb);
|
|
456
|
+
const newSelector = `${locator.css} >> ${climbSelector}`;
|
|
457
|
+
locator.css = newSelector;
|
|
458
|
+
unique.push(locator);
|
|
459
|
+
}
|
|
460
|
+
} else {
|
|
461
|
+
locator.priority = 2;
|
|
462
|
+
locator.elements = elements;
|
|
463
|
+
nonUnique.push(locator);
|
|
436
464
|
}
|
|
437
|
-
} else {
|
|
438
|
-
locator.priority = 2;
|
|
439
|
-
locator.elements = elements;
|
|
440
|
-
nonUnique.push(locator);
|
|
441
465
|
}
|
|
466
|
+
} catch (error) {
|
|
467
|
+
console.error("Error categorizing locators:", error);
|
|
442
468
|
}
|
|
443
469
|
return { unique, nonUnique };
|
|
444
470
|
}
|
|
@@ -674,6 +674,22 @@ const _generateCodeFromCommand = (step, elements, userData) => {
|
|
|
674
674
|
return { codeLines, elements: elementsChanged ? elements : null, elementIdentifier, allStrategyLocators };
|
|
675
675
|
};
|
|
676
676
|
|
|
677
|
+
/**
|
|
678
|
+
* Generates a report command based on the given position.
|
|
679
|
+
* @param {"start"|"end"} position
|
|
680
|
+
*/
|
|
681
|
+
const generateReportCommand = (position, cmdId) => {
|
|
682
|
+
const codeLines = [];
|
|
683
|
+
if (position === "start") {
|
|
684
|
+
const line = `await context.web.addCommandToReport("${cmdId}", "PASSED", '{"status":"start","cmdId":"${cmdId}"}', {type:"cmdReport"},this);`;
|
|
685
|
+
codeLines.push(line);
|
|
686
|
+
} else if (position === "end") {
|
|
687
|
+
const line = `await context.web.addCommandToReport("${cmdId}", "PASSED", '{"status":"end","cmdId":"${cmdId}"}', {type:"cmdReport"},this);`;
|
|
688
|
+
codeLines.push(line);
|
|
689
|
+
}
|
|
690
|
+
return codeLines;
|
|
691
|
+
};
|
|
692
|
+
|
|
677
693
|
const generateCode = (recording, codePage, userData, projectDir, methodName) => {
|
|
678
694
|
const stepsDefinitions = new StepsDefinitions(projectDir);
|
|
679
695
|
stepsDefinitions.load(false);
|
|
@@ -714,12 +730,15 @@ const generateCode = (recording, codePage, userData, projectDir, methodName) =>
|
|
|
714
730
|
if (recordingStep.type === Types.COMPLETE) {
|
|
715
731
|
return;
|
|
716
732
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
733
|
+
|
|
734
|
+
if (process.env.TEMP_RUN === "true") {
|
|
735
|
+
codeLines.push(...generateReportCommand("start", recordingStep.cmdId));
|
|
736
|
+
}
|
|
721
737
|
const result = _generateCodeFromCommand(recordingStep, elements, userData);
|
|
722
738
|
codeLines.push(...result.codeLines);
|
|
739
|
+
if (process.env.TEMP_RUN === "true") {
|
|
740
|
+
codeLines.push(...generateReportCommand("end", recordingStep.cmdId));
|
|
741
|
+
}
|
|
723
742
|
if (result.elements) {
|
|
724
743
|
elements = result.elements;
|
|
725
744
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// define the jsdoc type for the input
|
|
2
2
|
import { closeContext, initContext, _getDataFile, resetTestData } from "automation_model";
|
|
3
|
-
import { existsSync, readdirSync, readFileSync, rmSync } from "fs";
|
|
3
|
+
import { existsSync, mkdir, mkdirSync, readdirSync, readFileSync, rmSync } from "fs";
|
|
4
4
|
import path from "path";
|
|
5
5
|
import url from "url";
|
|
6
6
|
import { getImplementedSteps, getStepsAndCommandsForScenario } from "./implemented_steps.js";
|
|
@@ -15,6 +15,7 @@ import chokidar from "chokidar";
|
|
|
15
15
|
import logger from "../../logger.js";
|
|
16
16
|
import { unEscapeNonPrintables } from "../cucumber/utils.js";
|
|
17
17
|
import { findAvailablePort } from "../utils/index.js";
|
|
18
|
+
import NetworkMonitor from "./network.js";
|
|
18
19
|
|
|
19
20
|
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
20
21
|
|
|
@@ -185,12 +186,33 @@ export class BVTRecorder {
|
|
|
185
186
|
});
|
|
186
187
|
this.stepRunner = new BVTStepRunner({
|
|
187
188
|
projectDir: this.projectDir,
|
|
189
|
+
sendExecutionStatus: (data) => {
|
|
190
|
+
if (data && data.type) {
|
|
191
|
+
switch (data.type) {
|
|
192
|
+
case "cmdExecutionStart":
|
|
193
|
+
console.log("Sending cmdExecutionStart event for cmdId:", data.cmdId);
|
|
194
|
+
this.sendEvent(this.events.cmdExecutionStart, data.cmdId);
|
|
195
|
+
break;
|
|
196
|
+
case "cmdExecutionSuccess":
|
|
197
|
+
console.log("Sending cmdExecutionSuccess event for cmdId:", data.cmdId);
|
|
198
|
+
this.sendEvent(this.events.cmdExecutionSuccess, data.cmdId);
|
|
199
|
+
break;
|
|
200
|
+
case "cmdExecutionFailure":
|
|
201
|
+
console.log("Sending cmdExecutionFailure event for cmdId:", data.cmdId);
|
|
202
|
+
this.sendEvent(this.events.cmdExecutionFailure, data.cmdId);
|
|
203
|
+
break;
|
|
204
|
+
default:
|
|
205
|
+
console.warn("Unknown command execution status type:", data.type);
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
},
|
|
188
210
|
});
|
|
189
211
|
this.pageSet = new Set();
|
|
190
|
-
|
|
212
|
+
this.networkMonitor = new NetworkMonitor();
|
|
191
213
|
this.lastKnownUrlPath = "";
|
|
192
214
|
// TODO: what is world?
|
|
193
|
-
this.world = { attach: () => {
|
|
215
|
+
this.world = { attach: () => {} };
|
|
194
216
|
this.shouldTakeScreenshot = true;
|
|
195
217
|
this.watcher = null;
|
|
196
218
|
}
|
|
@@ -203,6 +225,9 @@ export class BVTRecorder {
|
|
|
203
225
|
onStepDetails: "BVTRecorder.onStepDetails",
|
|
204
226
|
getTestData: "BVTRecorder.getTestData",
|
|
205
227
|
onGoto: "BVTRecorder.onGoto",
|
|
228
|
+
cmdExecutionStart: "BVTRecorder.cmdExecutionStart",
|
|
229
|
+
cmdExecutionSuccess: "BVTRecorder.cmdExecutionSuccess",
|
|
230
|
+
cmdExecutionFailure: "BVTRecorder.cmdExecutionFailure",
|
|
206
231
|
};
|
|
207
232
|
bindings = {
|
|
208
233
|
__bvt_recordCommand: async ({ frame, page, context }, event) => {
|
|
@@ -294,7 +319,7 @@ export class BVTRecorder {
|
|
|
294
319
|
process.env.CDP_LISTEN_PORT = this.#remoteDebuggerPort;
|
|
295
320
|
|
|
296
321
|
this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
|
|
297
|
-
this.world = { attach: () => {
|
|
322
|
+
this.world = { attach: () => {} };
|
|
298
323
|
|
|
299
324
|
const ai_config_file = path.join(this.projectDir, "ai_config.json");
|
|
300
325
|
let ai_config = {};
|
|
@@ -700,7 +725,7 @@ export class BVTRecorder {
|
|
|
700
725
|
}
|
|
701
726
|
async closeBrowser() {
|
|
702
727
|
delete process.env.TEMP_RUN;
|
|
703
|
-
await this.watcher.close().then(() => {
|
|
728
|
+
await this.watcher.close().then(() => {});
|
|
704
729
|
this.watcher = null;
|
|
705
730
|
this.previousIndex = null;
|
|
706
731
|
this.previousHistoryLength = null;
|
|
@@ -789,30 +814,34 @@ export class BVTRecorder {
|
|
|
789
814
|
}, 100);
|
|
790
815
|
this.timerId = timerId;
|
|
791
816
|
}
|
|
792
|
-
async runStep({ step, parametersMap, tags, isFirstStep }, options) {
|
|
817
|
+
async runStep({ step, parametersMap, tags, isFirstStep, listenNetwork }, options) {
|
|
793
818
|
const _env = {
|
|
794
819
|
TOKEN: this.TOKEN,
|
|
795
820
|
TEMP_RUN: true,
|
|
796
821
|
REPORT_FOLDER: this.bvtContext.reportFolder,
|
|
797
822
|
BLINQ_ENV: this.envName,
|
|
823
|
+
STORE_DETAILED_NETWORK_DATA: listenNetwork ? "true" : "false",
|
|
824
|
+
CURRENT_STEP_ID: step.id,
|
|
798
825
|
};
|
|
799
826
|
|
|
800
827
|
this.bvtContext.navigate = true;
|
|
801
828
|
for (const [key, value] of Object.entries(_env)) {
|
|
802
829
|
process.env[key] = value;
|
|
803
830
|
}
|
|
831
|
+
|
|
804
832
|
if (this.timerId) {
|
|
805
833
|
clearTimeout(this.timerId);
|
|
806
834
|
this.timerId = null;
|
|
807
835
|
}
|
|
808
836
|
await this.setMode("running");
|
|
837
|
+
|
|
809
838
|
try {
|
|
810
839
|
const { result, info } = await this.stepRunner.runStep(
|
|
811
840
|
{
|
|
812
841
|
step,
|
|
813
842
|
parametersMap,
|
|
814
843
|
envPath: this.envName,
|
|
815
|
-
tags
|
|
844
|
+
tags,
|
|
816
845
|
},
|
|
817
846
|
this.bvtContext,
|
|
818
847
|
options ? { ...options, skipBefore: !isFirstStep } : { skipBefore: !isFirstStep }
|
|
@@ -51,6 +51,37 @@ class PromisifiedSocketServer {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
function memorySizeOf(obj) {
|
|
55
|
+
var bytes = 0;
|
|
56
|
+
|
|
57
|
+
function sizeOf(obj) {
|
|
58
|
+
if (obj !== null && obj !== undefined) {
|
|
59
|
+
switch (typeof obj) {
|
|
60
|
+
case "number":
|
|
61
|
+
bytes += 8;
|
|
62
|
+
break;
|
|
63
|
+
case "string":
|
|
64
|
+
bytes += obj.length * 2;
|
|
65
|
+
break;
|
|
66
|
+
case "boolean":
|
|
67
|
+
bytes += 4;
|
|
68
|
+
break;
|
|
69
|
+
case "object":
|
|
70
|
+
var objClass = Object.prototype.toString.call(obj).slice(8, -1);
|
|
71
|
+
if (objClass === "Object" || objClass === "Array") {
|
|
72
|
+
for (var key in obj) {
|
|
73
|
+
if (!obj.hasOwnProperty(key)) continue;
|
|
74
|
+
sizeOf(obj[key]);
|
|
75
|
+
}
|
|
76
|
+
} else bytes += obj.toString().length * 2;
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return bytes;
|
|
81
|
+
}
|
|
82
|
+
return sizeOf(obj);
|
|
83
|
+
}
|
|
84
|
+
|
|
54
85
|
const init = ({ envName, projectDir, roomId, TOKEN }) => {
|
|
55
86
|
console.log("connecting to " + WS_URL);
|
|
56
87
|
const socket = io(WS_URL);
|
|
@@ -66,8 +97,10 @@ const init = ({ envName, projectDir, roomId, TOKEN }) => {
|
|
|
66
97
|
projectDir,
|
|
67
98
|
TOKEN,
|
|
68
99
|
sendEvent: (event, data) => {
|
|
100
|
+
console.log("Size of data", memorySizeOf(data), "bytes");
|
|
69
101
|
console.log("----", event, data, "roomId", roomId);
|
|
70
102
|
socket.emit(event, data, roomId);
|
|
103
|
+
console.log("Successfully sent event", event, "to room", roomId);
|
|
71
104
|
},
|
|
72
105
|
});
|
|
73
106
|
recorder
|
|
@@ -77,7 +110,7 @@ const init = ({ envName, projectDir, roomId, TOKEN }) => {
|
|
|
77
110
|
socket.emit("BVTRecorder.browserOpened", null, roomId);
|
|
78
111
|
})
|
|
79
112
|
.catch((e) => {
|
|
80
|
-
socket.emit("BVTRecorder.browserLaunchFailed",
|
|
113
|
+
socket.emit("BVTRecorder.browserLaunchFailed", e, roomId);
|
|
81
114
|
});
|
|
82
115
|
const timeOutForFunction = async (promise, timeout = 5000) => {
|
|
83
116
|
const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve(), timeout));
|
|
@@ -239,6 +272,9 @@ const init = ({ envName, projectDir, roomId, TOKEN }) => {
|
|
|
239
272
|
"recorderWindow.getStepsAndCommandsForScenario": async (input) => {
|
|
240
273
|
return await recorder.getStepsAndCommandsForScenario(input);
|
|
241
274
|
},
|
|
275
|
+
"recorderWindow.getNetworkEvents": async (input) => {
|
|
276
|
+
return await recorder.getNetworkEvents(input);
|
|
277
|
+
},
|
|
242
278
|
});
|
|
243
279
|
socket.on("targetBrowser.command.event", async (input) => {
|
|
244
280
|
return recorder.onAction(input);
|