@dev-blinq/cucumber_client 1.0.1255-dev → 1.0.1255-stage
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 +159 -14071
- package/bin/assets/preload/recorderv3.js +4 -2
- package/bin/assets/scripts/dom_parent.js +26 -0
- package/bin/assets/scripts/recorder.js +9 -8
- package/bin/assets/scripts/unique_locators.js +847 -547
- package/bin/assets/templates/_hooks_template.txt +37 -0
- package/bin/assets/templates/page_template.txt +2 -16
- package/bin/assets/templates/utils_template.txt +44 -71
- package/bin/client/apiTest/apiTest.js +6 -0
- package/bin/client/cli_helpers.js +11 -13
- package/bin/client/code_cleanup/utils.js +5 -1
- package/bin/client/code_gen/code_inversion.js +61 -4
- package/bin/client/code_gen/page_reflection.js +10 -3
- package/bin/client/code_gen/playwright_codeget.js +55 -16
- package/bin/client/cucumber/feature.js +89 -27
- package/bin/client/cucumber/project_to_document.js +1 -1
- package/bin/client/cucumber/steps_definitions.js +84 -76
- package/bin/client/cucumber_selector.js +13 -1
- package/bin/client/local_agent.js +3 -3
- package/bin/client/project.js +7 -1
- package/bin/client/recorderv3/bvt_recorder.js +285 -80
- package/bin/client/recorderv3/implemented_steps.js +74 -16
- package/bin/client/recorderv3/index.js +47 -25
- package/bin/client/recorderv3/network.js +299 -0
- package/bin/client/recorderv3/services.js +4 -16
- package/bin/client/recorderv3/step_runner.js +326 -67
- package/bin/client/recorderv3/step_utils.js +152 -5
- package/bin/client/recorderv3/update_feature.js +66 -34
- package/bin/client/recording.js +3 -0
- package/bin/client/run_cucumber.js +5 -1
- package/bin/client/scenario_report.js +0 -5
- package/bin/client/test_scenario.js +0 -1
- package/bin/client/utils/socket_logger.js +132 -0
- package/bin/index.js +1 -0
- package/package.json +15 -12
|
@@ -3,7 +3,7 @@ import { closeContext, initContext, _getDataFile, resetTestData } from "automati
|
|
|
3
3
|
import { existsSync, readdirSync, readFileSync, rmSync } from "fs";
|
|
4
4
|
import path from "path";
|
|
5
5
|
import url from "url";
|
|
6
|
-
import { getImplementedSteps,
|
|
6
|
+
import { getImplementedSteps, parseRouteFiles } from "./implemented_steps.js";
|
|
7
7
|
import { NamesService } from "./services.js";
|
|
8
8
|
import { BVTStepRunner } from "./step_runner.js";
|
|
9
9
|
import { readFile, writeFile } from "fs/promises";
|
|
@@ -12,10 +12,9 @@ import { updateFeatureFile } from "./update_feature.js";
|
|
|
12
12
|
import { parseStepTextParameters } from "../cucumber/utils.js";
|
|
13
13
|
import { AstBuilder, GherkinClassicTokenMatcher, Parser } from "@cucumber/gherkin";
|
|
14
14
|
import chokidar from "chokidar";
|
|
15
|
-
import logger from "../../logger.js";
|
|
16
15
|
import { unEscapeNonPrintables } from "../cucumber/utils.js";
|
|
17
16
|
import { findAvailablePort } from "../utils/index.js";
|
|
18
|
-
|
|
17
|
+
import socketLogger from "../utils/socket_logger.js";
|
|
19
18
|
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
20
19
|
|
|
21
20
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -24,11 +23,11 @@ export function getInitScript(config, options) {
|
|
|
24
23
|
window.__bvt_Recorder_config = ${JSON.stringify(config ?? null)};
|
|
25
24
|
window.__PW_options = ${JSON.stringify(options ?? null)};
|
|
26
25
|
`;
|
|
27
|
-
const recorderScript = readFileSync(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
recorderScript
|
|
26
|
+
const recorderScript = readFileSync(
|
|
27
|
+
path.join(__dirname, "..", "..", "assets", "bundled_scripts", "recorder.js"),
|
|
28
|
+
"utf8"
|
|
31
29
|
);
|
|
30
|
+
return preScript + recorderScript;
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
async function evaluate(frame, script) {
|
|
@@ -44,15 +43,15 @@ async function evaluate(frame, script) {
|
|
|
44
43
|
async function findNestedFrameSelector(frame, obj) {
|
|
45
44
|
try {
|
|
46
45
|
const parent = frame.parentFrame();
|
|
47
|
-
if (parent) console.log(`Parent frame: ${JSON.stringify(parent)}`);
|
|
48
46
|
if (!parent) return { children: obj };
|
|
49
47
|
const frameElement = await frame.frameElement();
|
|
50
48
|
if (!frameElement) return;
|
|
51
49
|
const selectors = await parent.evaluate((element) => {
|
|
52
|
-
return window.
|
|
50
|
+
return window.__bvt_Recorder.locatorGenerator.getElementLocators(element, { excludeText: true }).locators;
|
|
53
51
|
}, frameElement);
|
|
54
52
|
return findNestedFrameSelector(parent, { children: obj, selectors });
|
|
55
53
|
} catch (e) {
|
|
54
|
+
socketLogger.error(`Error in findNestedFrameSelector: ${e}`);
|
|
56
55
|
console.error(e);
|
|
57
56
|
}
|
|
58
57
|
}
|
|
@@ -149,6 +148,7 @@ const transformAction = (action, el, isVerify, isPopupCloseClick, isInHoverMode,
|
|
|
149
148
|
};
|
|
150
149
|
}
|
|
151
150
|
default: {
|
|
151
|
+
socketLogger.error(`Action not supported: ${action.name}`);
|
|
152
152
|
console.log("action not supported", action);
|
|
153
153
|
throw new Error("action not supported");
|
|
154
154
|
}
|
|
@@ -160,6 +160,7 @@ const transformAction = (action, el, isVerify, isPopupCloseClick, isInHoverMode,
|
|
|
160
160
|
* @property {string} projectDir
|
|
161
161
|
* @property {string} TOKEN
|
|
162
162
|
* @property {(name:string, data:any)=> void} sendEvent
|
|
163
|
+
* @property {Object} logger
|
|
163
164
|
*/
|
|
164
165
|
export class BVTRecorder {
|
|
165
166
|
#currentURL = "";
|
|
@@ -173,7 +174,6 @@ export class BVTRecorder {
|
|
|
173
174
|
*/
|
|
174
175
|
constructor(initialState) {
|
|
175
176
|
Object.assign(this, initialState);
|
|
176
|
-
this.logger = logger;
|
|
177
177
|
this.screenshotMap = new Map();
|
|
178
178
|
this.snapshotMap = new Map();
|
|
179
179
|
this.scenariosStepsMap = new Map();
|
|
@@ -183,14 +183,10 @@ export class BVTRecorder {
|
|
|
183
183
|
projectDir: this.projectDir,
|
|
184
184
|
logger: this.logger,
|
|
185
185
|
});
|
|
186
|
-
this.stepRunner = new BVTStepRunner({
|
|
187
|
-
projectDir: this.projectDir,
|
|
188
|
-
});
|
|
189
186
|
this.pageSet = new Set();
|
|
190
|
-
|
|
187
|
+
this.pageMetaDataSet = new Set();
|
|
191
188
|
this.lastKnownUrlPath = "";
|
|
192
|
-
|
|
193
|
-
this.world = { attach: () => { } };
|
|
189
|
+
this.world = { attach: () => {} };
|
|
194
190
|
this.shouldTakeScreenshot = true;
|
|
195
191
|
this.watcher = null;
|
|
196
192
|
}
|
|
@@ -203,12 +199,16 @@ export class BVTRecorder {
|
|
|
203
199
|
onStepDetails: "BVTRecorder.onStepDetails",
|
|
204
200
|
getTestData: "BVTRecorder.getTestData",
|
|
205
201
|
onGoto: "BVTRecorder.onGoto",
|
|
202
|
+
cmdExecutionStart: "BVTRecorder.cmdExecutionStart",
|
|
203
|
+
cmdExecutionSuccess: "BVTRecorder.cmdExecutionSuccess",
|
|
204
|
+
cmdExecutionError: "BVTRecorder.cmdExecutionError",
|
|
205
|
+
interceptResults: "BVTRecorder.interceptResults",
|
|
206
206
|
};
|
|
207
207
|
bindings = {
|
|
208
208
|
__bvt_recordCommand: async ({ frame, page, context }, event) => {
|
|
209
209
|
this.#activeFrame = frame;
|
|
210
210
|
const nestFrmLoc = await findNestedFrameSelector(frame);
|
|
211
|
-
|
|
211
|
+
this.logger.info(`Time taken for action: ${event.statistics.time}`);
|
|
212
212
|
await this.onAction({ ...event, nestFrmLoc });
|
|
213
213
|
},
|
|
214
214
|
__bvt_getMode: async () => {
|
|
@@ -227,8 +227,7 @@ export class BVTRecorder {
|
|
|
227
227
|
await this.onClosePopup();
|
|
228
228
|
},
|
|
229
229
|
__bvt_log: async (src, message) => {
|
|
230
|
-
|
|
231
|
-
console.log(`Inside Browser: ${message}`);
|
|
230
|
+
this.logger.info(`Inside Browser: ${message}`);
|
|
232
231
|
},
|
|
233
232
|
__bvt_getObject: (_src, obj) => {
|
|
234
233
|
this.processObject(obj);
|
|
@@ -293,8 +292,8 @@ export class BVTRecorder {
|
|
|
293
292
|
this.#remoteDebuggerPort = await findAvailablePort();
|
|
294
293
|
process.env.CDP_LISTEN_PORT = this.#remoteDebuggerPort;
|
|
295
294
|
|
|
296
|
-
this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
|
|
297
|
-
this.world = { attach: () => {
|
|
295
|
+
// this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
|
|
296
|
+
this.world = { attach: () => {} };
|
|
298
297
|
|
|
299
298
|
const ai_config_file = path.join(this.projectDir, "ai_config.json");
|
|
300
299
|
let ai_config = {};
|
|
@@ -302,9 +301,10 @@ export class BVTRecorder {
|
|
|
302
301
|
try {
|
|
303
302
|
ai_config = JSON.parse(readFileSync(ai_config_file, "utf8"));
|
|
304
303
|
} catch (error) {
|
|
305
|
-
|
|
304
|
+
this.logger.error("Error reading ai_config.json", error);
|
|
306
305
|
}
|
|
307
306
|
}
|
|
307
|
+
this.config = ai_config;
|
|
308
308
|
const initScripts = {
|
|
309
309
|
// recorderCjs: injectedScriptSource,
|
|
310
310
|
scripts: [
|
|
@@ -313,16 +313,37 @@ export class BVTRecorder {
|
|
|
313
313
|
],
|
|
314
314
|
};
|
|
315
315
|
|
|
316
|
-
let startTime = Date.now();
|
|
317
316
|
const bvtContext = await initContext(url, false, false, this.world, 450, initScripts, this.envName);
|
|
318
|
-
let stopTime = Date.now();
|
|
319
|
-
this.logger.info(`Browser launched in ${(stopTime - startTime) / 1000} s`);
|
|
320
317
|
this.bvtContext = bvtContext;
|
|
318
|
+
this.stepRunner = new BVTStepRunner({
|
|
319
|
+
projectDir: this.projectDir,
|
|
320
|
+
sendExecutionStatus: (data) => {
|
|
321
|
+
if (data && data.type) {
|
|
322
|
+
switch (data.type) {
|
|
323
|
+
case "cmdExecutionStart":
|
|
324
|
+
this.sendEvent(this.events.cmdExecutionStart, data);
|
|
325
|
+
break;
|
|
326
|
+
case "cmdExecutionSuccess":
|
|
327
|
+
this.sendEvent(this.events.cmdExecutionSuccess, data);
|
|
328
|
+
break;
|
|
329
|
+
case "cmdExecutionError":
|
|
330
|
+
this.sendEvent(this.events.cmdExecutionError, data);
|
|
331
|
+
break;
|
|
332
|
+
case "interceptResults":
|
|
333
|
+
this.sendEvent(this.events.interceptResults, data);
|
|
334
|
+
break;
|
|
335
|
+
default:
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
bvtContext: this.bvtContext,
|
|
341
|
+
});
|
|
321
342
|
const context = bvtContext.playContext;
|
|
322
343
|
this.context = context;
|
|
323
344
|
this.web = bvtContext.stable || bvtContext.web;
|
|
345
|
+
this.web.tryAllStrategies = true;
|
|
324
346
|
this.page = bvtContext.page;
|
|
325
|
-
|
|
326
347
|
this.pageSet.add(this.page);
|
|
327
348
|
this.lastKnownUrlPath = this._updateUrlPath();
|
|
328
349
|
const browser = await this.context.browser();
|
|
@@ -350,13 +371,15 @@ export class BVTRecorder {
|
|
|
350
371
|
}
|
|
351
372
|
return;
|
|
352
373
|
} catch (error) {
|
|
353
|
-
console.error("Error evaluting in context:", error);
|
|
374
|
+
// console.error("Error evaluting in context:", error);
|
|
375
|
+
this.logger.error("Error evaluating in context:", error);
|
|
354
376
|
}
|
|
355
377
|
}
|
|
356
378
|
}
|
|
357
379
|
|
|
358
380
|
getMode() {
|
|
359
|
-
console.log("getMode", this.#mode);
|
|
381
|
+
// console.log("getMode", this.#mode);
|
|
382
|
+
this.logger.info("Current mode:", this.#mode);
|
|
360
383
|
return this.#mode;
|
|
361
384
|
}
|
|
362
385
|
|
|
@@ -398,6 +421,8 @@ export class BVTRecorder {
|
|
|
398
421
|
this.sendEvent(this.events.onBrowserClose);
|
|
399
422
|
}
|
|
400
423
|
} catch (error) {
|
|
424
|
+
this.logger.error("Error in page close event");
|
|
425
|
+
this.logger.error(error);
|
|
401
426
|
console.error("Error in page close event");
|
|
402
427
|
console.error(error);
|
|
403
428
|
}
|
|
@@ -408,8 +433,10 @@ export class BVTRecorder {
|
|
|
408
433
|
if (frame !== page.mainFrame()) return;
|
|
409
434
|
this.handlePageTransition();
|
|
410
435
|
} catch (error) {
|
|
436
|
+
this.logger.error("Error in handlePageTransition event");
|
|
437
|
+
this.logger.error(error);
|
|
411
438
|
console.error("Error in handlePageTransition event");
|
|
412
|
-
|
|
439
|
+
console.error(error);
|
|
413
440
|
}
|
|
414
441
|
try {
|
|
415
442
|
if (frame !== this.#activeFrame) return;
|
|
@@ -428,11 +455,82 @@ export class BVTRecorder {
|
|
|
428
455
|
// await this._setRecordingMode(frame);
|
|
429
456
|
// await this._initPage(page);
|
|
430
457
|
} catch (error) {
|
|
458
|
+
this.logger.error("Error in frame navigate event");
|
|
459
|
+
this.logger.error(error);
|
|
431
460
|
console.error("Error in frame navigate event");
|
|
432
461
|
// console.error(error);
|
|
433
462
|
}
|
|
434
463
|
});
|
|
435
464
|
}
|
|
465
|
+
|
|
466
|
+
hasHistoryReplacementAtIndex(previousEntries, currentEntries, index) {
|
|
467
|
+
if (!previousEntries || !currentEntries) return false;
|
|
468
|
+
if (index >= previousEntries.length || index >= currentEntries.length) return false;
|
|
469
|
+
|
|
470
|
+
const prevEntry = previousEntries[index];
|
|
471
|
+
// console.log("prevEntry", prevEntry);
|
|
472
|
+
const currEntry = currentEntries[index];
|
|
473
|
+
// console.log("currEntry", currEntry);
|
|
474
|
+
|
|
475
|
+
// Check if the entry at this index has been replaced
|
|
476
|
+
return prevEntry.id !== currEntry.id;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Even simpler approach for your specific case
|
|
480
|
+
analyzeTransitionType(entries, currentIndex, currentEntry) {
|
|
481
|
+
// console.log("Analyzing transition type");
|
|
482
|
+
// console.log("===========================");
|
|
483
|
+
// console.log("Current Index:", currentIndex);
|
|
484
|
+
// console.log("Current Entry:", currentEntry);
|
|
485
|
+
// console.log("Current Entries:", entries);
|
|
486
|
+
// console.log("Current entries length:", entries.length);
|
|
487
|
+
// console.log("===========================");
|
|
488
|
+
// console.log("Previous Index:", this.previousIndex);
|
|
489
|
+
// // console.log("Previous Entry:", this.previousEntries[this.previousIndex]);
|
|
490
|
+
// console.log("Previous Entries:", this.previousEntries);
|
|
491
|
+
// console.log("Previous entries length:", this.previousHistoryLength);
|
|
492
|
+
|
|
493
|
+
if (this.previousIndex === null || this.previousHistoryLength === null || !this.previousEntries) {
|
|
494
|
+
return {
|
|
495
|
+
action: "initial",
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const indexDiff = currentIndex - this.previousIndex;
|
|
500
|
+
const lengthDiff = entries.length - this.previousHistoryLength;
|
|
501
|
+
|
|
502
|
+
// Backward navigation
|
|
503
|
+
if (indexDiff < 0) {
|
|
504
|
+
return { action: "back" };
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Forward navigation
|
|
508
|
+
if (indexDiff > 0 && lengthDiff === 0) {
|
|
509
|
+
// Check if the entry at current index is the same as before
|
|
510
|
+
const entryReplaced = this.hasHistoryReplacementAtIndex(this.previousEntries, entries, currentIndex);
|
|
511
|
+
|
|
512
|
+
if (entryReplaced) {
|
|
513
|
+
return { action: "navigate" }; // New navigation that replaced forward history
|
|
514
|
+
} else {
|
|
515
|
+
return { action: "forward" }; // True forward navigation
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// New navigation (history grew)
|
|
520
|
+
if (lengthDiff > 0) {
|
|
521
|
+
return { action: "navigate" };
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Same position, same length
|
|
525
|
+
if (lengthDiff <= 0) {
|
|
526
|
+
const entryReplaced = this.hasHistoryReplacementAtIndex(this.previousEntries, entries, currentIndex);
|
|
527
|
+
|
|
528
|
+
return entryReplaced ? { action: "navigate" } : { action: "reload" };
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return { action: "unknown" };
|
|
532
|
+
}
|
|
533
|
+
|
|
436
534
|
async getCurrentTransition() {
|
|
437
535
|
if (this?.web?.browser?._name !== "chromium") {
|
|
438
536
|
return;
|
|
@@ -441,35 +539,68 @@ export class BVTRecorder {
|
|
|
441
539
|
|
|
442
540
|
try {
|
|
443
541
|
const result = await client.send("Page.getNavigationHistory");
|
|
444
|
-
// console.log("result", result);
|
|
445
542
|
const entries = result.entries;
|
|
446
543
|
const currentIndex = result.currentIndex;
|
|
447
544
|
|
|
448
|
-
// ignore if currentIndex is not the last entry
|
|
449
|
-
if (currentIndex !== entries.length - 1) return;
|
|
450
|
-
|
|
451
545
|
const currentEntry = entries[currentIndex];
|
|
452
|
-
|
|
546
|
+
const transitionInfo = this.analyzeTransitionType(entries, currentIndex, currentEntry);
|
|
547
|
+
this.previousIndex = currentIndex;
|
|
548
|
+
this.previousHistoryLength = entries.length;
|
|
549
|
+
this.previousUrl = currentEntry.url;
|
|
550
|
+
this.previousEntries = [...entries]; // Store a copy of current entries
|
|
551
|
+
|
|
552
|
+
return {
|
|
553
|
+
currentEntry,
|
|
554
|
+
navigationAction: transitionInfo.action,
|
|
555
|
+
};
|
|
453
556
|
} catch (error) {
|
|
454
|
-
|
|
557
|
+
this.logger.error("Error in getCurrentTransition event");
|
|
558
|
+
this.logger.error(error);
|
|
559
|
+
console.error("Error in getTransistionType event", error);
|
|
455
560
|
} finally {
|
|
456
561
|
await client.detach();
|
|
457
562
|
}
|
|
458
563
|
}
|
|
459
|
-
|
|
564
|
+
userInitiatedTransitionTypes = ["typed", "address_bar"];
|
|
460
565
|
async handlePageTransition() {
|
|
461
566
|
const transition = await this.getCurrentTransition();
|
|
462
567
|
if (!transition) return;
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
568
|
+
|
|
569
|
+
const { currentEntry, navigationAction } = transition;
|
|
570
|
+
|
|
571
|
+
switch (navigationAction) {
|
|
572
|
+
case "initial":
|
|
573
|
+
// console.log("Initial navigation, no action taken");
|
|
574
|
+
return;
|
|
575
|
+
case "navigate":
|
|
576
|
+
// console.log("transitionType", transition.transitionType);
|
|
577
|
+
// console.log("sending onGoto event", { url: currentEntry.url,
|
|
578
|
+
// type: "navigate", });
|
|
579
|
+
if (this.userInitiatedTransitionTypes.includes(currentEntry.transitionType)) {
|
|
580
|
+
const env = JSON.parse(readFileSync(this.envName), "utf8");
|
|
581
|
+
const baseUrl = env.baseUrl;
|
|
582
|
+
let url = currentEntry.userTypedURL;
|
|
583
|
+
if (baseUrl && url.startsWith(baseUrl)) {
|
|
584
|
+
url = url.replace(baseUrl, "{{env.baseUrl}}");
|
|
585
|
+
}
|
|
586
|
+
// console.log("User initiated transition");
|
|
587
|
+
this.sendEvent(this.events.onGoto, { url, type: "navigate" });
|
|
588
|
+
}
|
|
589
|
+
return;
|
|
590
|
+
case "back":
|
|
591
|
+
// console.log("User navigated back");
|
|
592
|
+
// console.log("sending onGoto event", {
|
|
593
|
+
// type: "back",
|
|
594
|
+
// });
|
|
595
|
+
this.sendEvent(this.events.onGoto, { type: "back" });
|
|
596
|
+
return;
|
|
597
|
+
case "forward":
|
|
598
|
+
// console.log("User navigated forward"); console.log("sending onGoto event", { type: "forward", });
|
|
599
|
+
this.sendEvent(this.events.onGoto, { type: "forward" });
|
|
600
|
+
return;
|
|
601
|
+
default:
|
|
602
|
+
this.sendEvent(this.events.onGoto, { type: "unknown" });
|
|
603
|
+
return;
|
|
473
604
|
}
|
|
474
605
|
}
|
|
475
606
|
|
|
@@ -493,6 +624,8 @@ export class BVTRecorder {
|
|
|
493
624
|
// add listener for frame navigation on new tab
|
|
494
625
|
this._addFrameNavigateListener(page);
|
|
495
626
|
} catch (error) {
|
|
627
|
+
this.logger.error("Error in page event");
|
|
628
|
+
this.logger.error(error);
|
|
496
629
|
console.error("Error in page event");
|
|
497
630
|
console.error(error);
|
|
498
631
|
}
|
|
@@ -534,6 +667,7 @@ export class BVTRecorder {
|
|
|
534
667
|
const { data } = await client.send("Page.captureScreenshot", { format: "png" });
|
|
535
668
|
return data;
|
|
536
669
|
} catch (error) {
|
|
670
|
+
this.logger.error("Error in taking browser screenshot");
|
|
537
671
|
console.error("Error in taking browser screenshot", error);
|
|
538
672
|
} finally {
|
|
539
673
|
await client.detach();
|
|
@@ -596,8 +730,13 @@ export class BVTRecorder {
|
|
|
596
730
|
}
|
|
597
731
|
async closeBrowser() {
|
|
598
732
|
delete process.env.TEMP_RUN;
|
|
599
|
-
await this.watcher.close().then(() => {
|
|
733
|
+
await this.watcher.close().then(() => {});
|
|
600
734
|
this.watcher = null;
|
|
735
|
+
this.previousIndex = null;
|
|
736
|
+
this.previousHistoryLength = null;
|
|
737
|
+
this.previousUrl = null;
|
|
738
|
+
this.previousEntries = null;
|
|
739
|
+
|
|
601
740
|
await closeContext();
|
|
602
741
|
this.pageSet.clear();
|
|
603
742
|
}
|
|
@@ -674,40 +813,61 @@ export class BVTRecorder {
|
|
|
674
813
|
async abortExecution() {
|
|
675
814
|
await this.stepRunner.abortExecution();
|
|
676
815
|
}
|
|
816
|
+
|
|
817
|
+
async pauseExecution({ cmdId }) {
|
|
818
|
+
await this.stepRunner.pauseExecution(cmdId);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
async resumeExecution({ cmdId }) {
|
|
822
|
+
await this.stepRunner.resumeExecution(cmdId);
|
|
823
|
+
}
|
|
824
|
+
|
|
677
825
|
async dealyedRevertMode() {
|
|
678
826
|
const timerId = setTimeout(async () => {
|
|
679
827
|
await this.revertMode();
|
|
680
828
|
}, 100);
|
|
681
829
|
this.timerId = timerId;
|
|
682
830
|
}
|
|
683
|
-
async runStep({ step, parametersMap }, options) {
|
|
831
|
+
async runStep({ step, parametersMap, tags, isFirstStep, listenNetwork }, options) {
|
|
832
|
+
const { skipAfter = true, skipBefore = !isFirstStep } = options || {};
|
|
684
833
|
const _env = {
|
|
685
834
|
TOKEN: this.TOKEN,
|
|
686
835
|
TEMP_RUN: true,
|
|
687
836
|
REPORT_FOLDER: this.bvtContext.reportFolder,
|
|
688
837
|
BLINQ_ENV: this.envName,
|
|
838
|
+
STORE_DETAILED_NETWORK_DATA: listenNetwork ? "true" : "false",
|
|
839
|
+
DEBUG: "blinq:route",
|
|
689
840
|
};
|
|
690
841
|
|
|
691
842
|
this.bvtContext.navigate = true;
|
|
843
|
+
this.bvtContext.loadedRoutes = null;
|
|
692
844
|
for (const [key, value] of Object.entries(_env)) {
|
|
693
845
|
process.env[key] = value;
|
|
694
846
|
}
|
|
847
|
+
|
|
695
848
|
if (this.timerId) {
|
|
696
849
|
clearTimeout(this.timerId);
|
|
697
850
|
this.timerId = null;
|
|
698
851
|
}
|
|
699
852
|
await this.setMode("running");
|
|
853
|
+
|
|
700
854
|
try {
|
|
701
|
-
await this.stepRunner.runStep(
|
|
855
|
+
const { result, info } = await this.stepRunner.runStep(
|
|
702
856
|
{
|
|
703
857
|
step,
|
|
704
858
|
parametersMap,
|
|
705
859
|
envPath: this.envName,
|
|
860
|
+
tags,
|
|
861
|
+
config: this.config,
|
|
706
862
|
},
|
|
707
863
|
this.bvtContext,
|
|
708
|
-
|
|
864
|
+
{
|
|
865
|
+
skipAfter,
|
|
866
|
+
skipBefore,
|
|
867
|
+
}
|
|
709
868
|
);
|
|
710
869
|
await this.revertMode();
|
|
870
|
+
return { info };
|
|
711
871
|
} catch (error) {
|
|
712
872
|
await this.revertMode();
|
|
713
873
|
throw error;
|
|
@@ -718,15 +878,10 @@ export class BVTRecorder {
|
|
|
718
878
|
this.bvtContext.navigate = false;
|
|
719
879
|
}
|
|
720
880
|
}
|
|
721
|
-
async runScenario({ steps, parametersMap }) {
|
|
722
|
-
for (const step of steps) {
|
|
723
|
-
await this.runStep({ step, parametersMap });
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
881
|
async saveScenario({ scenario, featureName, override, isSingleStep }) {
|
|
727
882
|
await updateStepDefinitions({ scenario, featureName, projectDir: this.projectDir }); // updates mjs files
|
|
728
883
|
if (!isSingleStep) await updateFeatureFile({ featureName, scenario, override, projectDir: this.projectDir }); // updates gherkin files
|
|
729
|
-
await this.cleanup();
|
|
884
|
+
await this.cleanup({ tags: scenario.tags });
|
|
730
885
|
}
|
|
731
886
|
async getImplementedSteps() {
|
|
732
887
|
const stepsAndScenarios = await getImplementedSteps(this.projectDir);
|
|
@@ -750,6 +905,7 @@ export class BVTRecorder {
|
|
|
750
905
|
step.commands = [];
|
|
751
906
|
}
|
|
752
907
|
}
|
|
908
|
+
return steps;
|
|
753
909
|
// return getStepsAndCommandsForScenario({
|
|
754
910
|
// name,
|
|
755
911
|
// featureName,
|
|
@@ -757,6 +913,7 @@ export class BVTRecorder {
|
|
|
757
913
|
// map: this.scenariosStepsMap,
|
|
758
914
|
// });
|
|
759
915
|
}
|
|
916
|
+
|
|
760
917
|
async generateStepName({ commands, stepsNames, parameters, map }) {
|
|
761
918
|
return await this.namesService.generateStepName({ commands, stepsNames, parameters, map });
|
|
762
919
|
}
|
|
@@ -808,10 +965,11 @@ export class BVTRecorder {
|
|
|
808
965
|
if (existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
|
|
809
966
|
try {
|
|
810
967
|
const testData = JSON.parse(readFileSync(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
|
|
811
|
-
this.logger.info("Test data", testData);
|
|
968
|
+
// this.logger.info("Test data", testData);
|
|
812
969
|
this.sendEvent(this.events.getTestData, testData);
|
|
813
970
|
} catch (e) {
|
|
814
|
-
this.logger.error("Error reading test data file", e);
|
|
971
|
+
// this.logger.error("Error reading test data file", e);
|
|
972
|
+
console.log("Error reading test data file", e);
|
|
815
973
|
}
|
|
816
974
|
}
|
|
817
975
|
|
|
@@ -820,10 +978,12 @@ export class BVTRecorder {
|
|
|
820
978
|
this.watcher.on("all", async (event, path) => {
|
|
821
979
|
try {
|
|
822
980
|
const testData = JSON.parse(await readFile(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
|
|
823
|
-
this.logger.info("Test data", testData);
|
|
981
|
+
// this.logger.info("Test data", testData);
|
|
982
|
+
console.log("Test data changed", testData);
|
|
824
983
|
this.sendEvent(this.events.getTestData, testData);
|
|
825
984
|
} catch (e) {
|
|
826
|
-
this.logger.error("Error reading test data file", e);
|
|
985
|
+
// this.logger.error("Error reading test data file", e);
|
|
986
|
+
console.log("Error reading test data file", e);
|
|
827
987
|
}
|
|
828
988
|
});
|
|
829
989
|
}
|
|
@@ -839,9 +999,9 @@ export class BVTRecorder {
|
|
|
839
999
|
}
|
|
840
1000
|
}
|
|
841
1001
|
|
|
842
|
-
async discardTestData() {
|
|
1002
|
+
async discardTestData({ tags }) {
|
|
843
1003
|
resetTestData(this.envName, this.world);
|
|
844
|
-
await this.cleanup();
|
|
1004
|
+
await this.cleanup({ tags });
|
|
845
1005
|
}
|
|
846
1006
|
async addToTestData(obj) {
|
|
847
1007
|
if (!existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
|
|
@@ -856,7 +1016,7 @@ export class BVTRecorder {
|
|
|
856
1016
|
.filter((file) => file.endsWith(".feature"))
|
|
857
1017
|
.map((file) => path.join(this.projectDir, "features", file));
|
|
858
1018
|
try {
|
|
859
|
-
const parsedFiles = featureFiles.map((file) => parseFeatureFile(file));
|
|
1019
|
+
const parsedFiles = featureFiles.map((file) => this.parseFeatureFile(file));
|
|
860
1020
|
const output = {};
|
|
861
1021
|
parsedFiles.forEach((file) => {
|
|
862
1022
|
if (!file.feature) return;
|
|
@@ -880,10 +1040,11 @@ export class BVTRecorder {
|
|
|
880
1040
|
const stepParams = parseStepTextParameters(stepName);
|
|
881
1041
|
return getCommandsForImplementedStep(stepName, step_definitions, stepParams).commands;
|
|
882
1042
|
}
|
|
1043
|
+
|
|
883
1044
|
loadExistingScenario({ featureName, scenarioName }) {
|
|
884
1045
|
const step_definitions = loadStepDefinitions(this.projectDir);
|
|
885
1046
|
const featureFilePath = path.join(this.projectDir, "features", featureName);
|
|
886
|
-
const gherkinDoc = parseFeatureFile(featureFilePath);
|
|
1047
|
+
const gherkinDoc = this.parseFeatureFile(featureFilePath);
|
|
887
1048
|
const scenario = gherkinDoc.feature.children.find((child) => child.scenario.name === scenarioName)?.scenario;
|
|
888
1049
|
|
|
889
1050
|
const steps = [];
|
|
@@ -908,6 +1069,7 @@ export class BVTRecorder {
|
|
|
908
1069
|
..._s,
|
|
909
1070
|
keyword: step.keyword.trim(),
|
|
910
1071
|
};
|
|
1072
|
+
parseRouteFiles(this.projectDir, _step);
|
|
911
1073
|
steps.push(_step);
|
|
912
1074
|
}
|
|
913
1075
|
return {
|
|
@@ -947,7 +1109,7 @@ export class BVTRecorder {
|
|
|
947
1109
|
}
|
|
948
1110
|
return result;
|
|
949
1111
|
}
|
|
950
|
-
async cleanup() {
|
|
1112
|
+
async cleanup({ tags }) {
|
|
951
1113
|
const noopStep = {
|
|
952
1114
|
text: "Noop",
|
|
953
1115
|
isImplemented: true,
|
|
@@ -961,6 +1123,7 @@ export class BVTRecorder {
|
|
|
961
1123
|
{
|
|
962
1124
|
step: noopStep,
|
|
963
1125
|
parametersMap: {},
|
|
1126
|
+
tags: tags || [],
|
|
964
1127
|
},
|
|
965
1128
|
{
|
|
966
1129
|
skipAfter: false,
|
|
@@ -999,20 +1162,62 @@ export class BVTRecorder {
|
|
|
999
1162
|
return false;
|
|
1000
1163
|
}
|
|
1001
1164
|
}
|
|
1002
|
-
}
|
|
1165
|
+
async initExecution({ tags = [] }) {
|
|
1166
|
+
// run before hooks
|
|
1167
|
+
const noopStep = {
|
|
1168
|
+
text: "Noop",
|
|
1169
|
+
isImplemented: true,
|
|
1170
|
+
};
|
|
1171
|
+
await this.runStep(
|
|
1172
|
+
{
|
|
1173
|
+
step: noopStep,
|
|
1174
|
+
parametersMap: {},
|
|
1175
|
+
tags,
|
|
1176
|
+
},
|
|
1177
|
+
{
|
|
1178
|
+
skipBefore: false,
|
|
1179
|
+
skipAfter: true,
|
|
1180
|
+
}
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
async cleanupExecution({ tags = [] }) {
|
|
1184
|
+
// run after hooks
|
|
1185
|
+
const noopStep = {
|
|
1186
|
+
text: "Noop",
|
|
1187
|
+
isImplemented: true,
|
|
1188
|
+
};
|
|
1189
|
+
await this.runStep(
|
|
1190
|
+
{
|
|
1191
|
+
step: noopStep,
|
|
1192
|
+
parametersMap: {},
|
|
1193
|
+
tags,
|
|
1194
|
+
},
|
|
1195
|
+
{
|
|
1196
|
+
skipBefore: true,
|
|
1197
|
+
skipAfter: false,
|
|
1198
|
+
}
|
|
1199
|
+
);
|
|
1200
|
+
}
|
|
1201
|
+
async resetExecution({ tags = [] }) {
|
|
1202
|
+
// run after hooks followed by before hooks
|
|
1203
|
+
await this.cleanupExecution({ tags });
|
|
1204
|
+
await this.initExecution({ tags });
|
|
1205
|
+
}
|
|
1003
1206
|
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1207
|
+
parseFeatureFile(featureFilePath) {
|
|
1208
|
+
try {
|
|
1209
|
+
let id = 0;
|
|
1210
|
+
const uuidFn = () => (++id).toString(16);
|
|
1211
|
+
const builder = new AstBuilder(uuidFn);
|
|
1212
|
+
const matcher = new GherkinClassicTokenMatcher();
|
|
1213
|
+
const parser = new Parser(builder, matcher);
|
|
1214
|
+
const source = readFileSync(featureFilePath, "utf8");
|
|
1215
|
+
const gherkinDocument = parser.parse(source);
|
|
1216
|
+
return gherkinDocument;
|
|
1217
|
+
} catch (e) {
|
|
1218
|
+
this.logger.error(`Error parsing feature file: ${featureFilePath}`);
|
|
1219
|
+
console.log(e);
|
|
1220
|
+
}
|
|
1221
|
+
return {};
|
|
1016
1222
|
}
|
|
1017
|
-
|
|
1018
|
-
};
|
|
1223
|
+
}
|