@dev-blinq/cucumber_client 1.0.1241-dev → 1.0.1241-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 +220 -0
- package/bin/assets/preload/recorderv3.js +5 -3
- package/bin/assets/preload/unique_locators.js +1 -1
- package/bin/assets/scripts/aria_snapshot.js +235 -0
- package/bin/assets/scripts/dom_attr.js +372 -0
- package/bin/assets/scripts/dom_element.js +0 -0
- package/bin/assets/scripts/dom_parent.js +185 -0
- package/bin/assets/scripts/event_utils.js +105 -0
- package/bin/assets/scripts/pw.js +7886 -0
- package/bin/assets/scripts/recorder.js +1147 -0
- package/bin/assets/scripts/snapshot_capturer.js +155 -0
- package/bin/assets/scripts/unique_locators.js +852 -0
- package/bin/assets/scripts/yaml.js +4770 -0
- 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 +41 -14
- package/bin/client/code_gen/code_inversion.js +68 -10
- package/bin/client/code_gen/page_reflection.js +12 -15
- package/bin/client/code_gen/playwright_codeget.js +134 -42
- 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 +307 -124
- package/bin/client/recorderv3/implemented_steps.js +63 -5
- package/bin/client/recorderv3/index.js +47 -25
- package/bin/client/recorderv3/network.js +299 -0
- package/bin/client/recorderv3/services.js +3 -15
- package/bin/client/recorderv3/step_runner.js +319 -67
- package/bin/client/recorderv3/step_utils.js +152 -5
- package/bin/client/recorderv3/update_feature.js +66 -34
- package/bin/client/recording.js +5 -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 +17 -9
|
@@ -3,8 +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
|
|
7
|
-
import { getImplementedSteps, getStepsAndCommandsForScenario } from "./implemented_steps.js";
|
|
6
|
+
import { getImplementedSteps, parseRouteFiles } from "./implemented_steps.js";
|
|
8
7
|
import { NamesService } from "./services.js";
|
|
9
8
|
import { BVTStepRunner } from "./step_runner.js";
|
|
10
9
|
import { readFile, writeFile } from "fs/promises";
|
|
@@ -13,45 +12,22 @@ import { updateFeatureFile } from "./update_feature.js";
|
|
|
13
12
|
import { parseStepTextParameters } from "../cucumber/utils.js";
|
|
14
13
|
import { AstBuilder, GherkinClassicTokenMatcher, Parser } from "@cucumber/gherkin";
|
|
15
14
|
import chokidar from "chokidar";
|
|
16
|
-
import logger from "../../logger.js";
|
|
17
15
|
import { unEscapeNonPrintables } from "../cucumber/utils.js";
|
|
18
16
|
import { findAvailablePort } from "../utils/index.js";
|
|
19
|
-
import
|
|
17
|
+
import socketLogger from "../utils/socket_logger.js";
|
|
20
18
|
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
21
|
-
|
|
19
|
+
|
|
22
20
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
23
|
-
export function getInitScript(config) {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
window.__bvt_Recorder.setPopupHandlers(${JSON.stringify(popupHandlers)});
|
|
32
|
-
window.__bvt_Recorder.setDisableHighlight(${JSON.stringify(disableHighlight)});
|
|
33
|
-
window.__bvt_Recorder.setImproviseLocators(${JSON.stringify(!disableMultipleLocators)});`;
|
|
34
|
-
return (
|
|
35
|
-
[
|
|
36
|
-
path.join(__dirname, "..", "..", "assets", "preload", "accessibility.js"),
|
|
37
|
-
path.join(__dirname, "..", "..", "assets", "preload", "dom-utils.js"),
|
|
38
|
-
path.join(__dirname, "..", "..", "assets", "preload", "generateSelector.js"),
|
|
39
|
-
path.join(__dirname, "..", "..", "assets", "preload", "locators.js"),
|
|
40
|
-
path.join(__dirname, "..", "..", "assets", "preload", "unique_locators.js"),
|
|
41
|
-
// path.join(__dirname, "..", "..", "assets", "preload", "pw_locators.js"),
|
|
42
|
-
path.join(__dirname, "..", "..", "assets", "preload", "climb.js"),
|
|
43
|
-
path.join(__dirname, "..", "..", "assets", "preload", "text-locator.js"),
|
|
44
|
-
path.join(__dirname, "..", "..", "assets", "preload", "toolbar.js"),
|
|
45
|
-
path.join(__dirname, "..", "..", "assets", "preload", "recording-tool.js"),
|
|
46
|
-
path.join(__dirname, "..", "..", "assets", "preload", "find_context.js"),
|
|
47
|
-
path.join(__dirname, "..", "..", "assets", "preload", "recorderv3.js"),
|
|
48
|
-
path.join(__dirname, "..", "..", "assets", "preload", "findElementText.js"),
|
|
49
|
-
path.join(__dirname, "..", "..", "assets", "preload", "css_gen.js"),
|
|
50
|
-
path.join(__dirname, "..", "..", "assets", "preload", "yaml.js"),
|
|
51
|
-
]
|
|
52
|
-
.map((filePath) => readFileSync(filePath, "utf8"))
|
|
53
|
-
.join("\n") + popupScript
|
|
21
|
+
export function getInitScript(config, options) {
|
|
22
|
+
const preScript = `
|
|
23
|
+
window.__bvt_Recorder_config = ${JSON.stringify(config ?? null)};
|
|
24
|
+
window.__PW_options = ${JSON.stringify(options ?? null)};
|
|
25
|
+
`;
|
|
26
|
+
const recorderScript = readFileSync(
|
|
27
|
+
path.join(__dirname, "..", "..", "assets", "bundled_scripts", "recorder.js"),
|
|
28
|
+
"utf8"
|
|
54
29
|
);
|
|
30
|
+
return preScript + recorderScript;
|
|
55
31
|
}
|
|
56
32
|
|
|
57
33
|
async function evaluate(frame, script) {
|
|
@@ -67,15 +43,15 @@ async function evaluate(frame, script) {
|
|
|
67
43
|
async function findNestedFrameSelector(frame, obj) {
|
|
68
44
|
try {
|
|
69
45
|
const parent = frame.parentFrame();
|
|
70
|
-
if (parent) console.log(`Parent frame: ${JSON.stringify(parent)}`);
|
|
71
46
|
if (!parent) return { children: obj };
|
|
72
47
|
const frameElement = await frame.frameElement();
|
|
73
48
|
if (!frameElement) return;
|
|
74
49
|
const selectors = await parent.evaluate((element) => {
|
|
75
|
-
return window.
|
|
50
|
+
return window.__bvt_Recorder.locatorGenerator.getElementLocators(element, { excludeText: true }).locators;
|
|
76
51
|
}, frameElement);
|
|
77
52
|
return findNestedFrameSelector(parent, { children: obj, selectors });
|
|
78
53
|
} catch (e) {
|
|
54
|
+
socketLogger.error(`Error in findNestedFrameSelector: ${e}`);
|
|
79
55
|
console.error(e);
|
|
80
56
|
}
|
|
81
57
|
}
|
|
@@ -172,6 +148,7 @@ const transformAction = (action, el, isVerify, isPopupCloseClick, isInHoverMode,
|
|
|
172
148
|
};
|
|
173
149
|
}
|
|
174
150
|
default: {
|
|
151
|
+
socketLogger.error(`Action not supported: ${action.name}`);
|
|
175
152
|
console.log("action not supported", action);
|
|
176
153
|
throw new Error("action not supported");
|
|
177
154
|
}
|
|
@@ -183,6 +160,7 @@ const transformAction = (action, el, isVerify, isPopupCloseClick, isInHoverMode,
|
|
|
183
160
|
* @property {string} projectDir
|
|
184
161
|
* @property {string} TOKEN
|
|
185
162
|
* @property {(name:string, data:any)=> void} sendEvent
|
|
163
|
+
* @property {Object} logger
|
|
186
164
|
*/
|
|
187
165
|
export class BVTRecorder {
|
|
188
166
|
#currentURL = "";
|
|
@@ -196,7 +174,6 @@ export class BVTRecorder {
|
|
|
196
174
|
*/
|
|
197
175
|
constructor(initialState) {
|
|
198
176
|
Object.assign(this, initialState);
|
|
199
|
-
this.logger = logger;
|
|
200
177
|
this.screenshotMap = new Map();
|
|
201
178
|
this.snapshotMap = new Map();
|
|
202
179
|
this.scenariosStepsMap = new Map();
|
|
@@ -206,13 +183,9 @@ export class BVTRecorder {
|
|
|
206
183
|
projectDir: this.projectDir,
|
|
207
184
|
logger: this.logger,
|
|
208
185
|
});
|
|
209
|
-
this.stepRunner = new BVTStepRunner({
|
|
210
|
-
projectDir: this.projectDir,
|
|
211
|
-
});
|
|
212
186
|
this.pageSet = new Set();
|
|
213
|
-
|
|
187
|
+
this.pageMetaDataSet = new Set();
|
|
214
188
|
this.lastKnownUrlPath = "";
|
|
215
|
-
// TODO: what is world?
|
|
216
189
|
this.world = { attach: () => {} };
|
|
217
190
|
this.shouldTakeScreenshot = true;
|
|
218
191
|
this.watcher = null;
|
|
@@ -226,12 +199,16 @@ export class BVTRecorder {
|
|
|
226
199
|
onStepDetails: "BVTRecorder.onStepDetails",
|
|
227
200
|
getTestData: "BVTRecorder.getTestData",
|
|
228
201
|
onGoto: "BVTRecorder.onGoto",
|
|
202
|
+
cmdExecutionStart: "BVTRecorder.cmdExecutionStart",
|
|
203
|
+
cmdExecutionSuccess: "BVTRecorder.cmdExecutionSuccess",
|
|
204
|
+
cmdExecutionError: "BVTRecorder.cmdExecutionError",
|
|
205
|
+
interceptResults: "BVTRecorder.interceptResults",
|
|
229
206
|
};
|
|
230
207
|
bindings = {
|
|
231
208
|
__bvt_recordCommand: async ({ frame, page, context }, event) => {
|
|
232
209
|
this.#activeFrame = frame;
|
|
233
210
|
const nestFrmLoc = await findNestedFrameSelector(frame);
|
|
234
|
-
|
|
211
|
+
this.logger.info(`Time taken for action: ${event.statistics.time}`);
|
|
235
212
|
await this.onAction({ ...event, nestFrmLoc });
|
|
236
213
|
},
|
|
237
214
|
__bvt_getMode: async () => {
|
|
@@ -250,8 +227,7 @@ export class BVTRecorder {
|
|
|
250
227
|
await this.onClosePopup();
|
|
251
228
|
},
|
|
252
229
|
__bvt_log: async (src, message) => {
|
|
253
|
-
|
|
254
|
-
console.log(`Inside Browser: ${message}`);
|
|
230
|
+
this.logger.info(`Inside Browser: ${message}`);
|
|
255
231
|
},
|
|
256
232
|
__bvt_getObject: (_src, obj) => {
|
|
257
233
|
this.processObject(obj);
|
|
@@ -301,29 +277,22 @@ export class BVTRecorder {
|
|
|
301
277
|
return result;
|
|
302
278
|
}
|
|
303
279
|
getInitScripts(config) {
|
|
304
|
-
return getInitScript(config
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
// const inlineScript = `
|
|
314
|
-
// window.__bvt_Recorder.setPopupHandlers(${JSON.stringify(popupHandlers)});
|
|
315
|
-
// window.__bvt_Recorder.setDisableHighlight(${JSON.stringify(disableHighlight)});
|
|
316
|
-
// window.__bvt_Recorder.setImproviseLocators(${JSON.stringify(!disableMultipleLocators)});
|
|
317
|
-
// `
|
|
318
|
-
// scripts.push(inlineScript);
|
|
319
|
-
// return scripts;
|
|
280
|
+
return getInitScript(config, {
|
|
281
|
+
sdkLanguage: "javascript",
|
|
282
|
+
testIdAttributeName: "blinq-test-id",
|
|
283
|
+
stableRafCount: 0,
|
|
284
|
+
browserName: this.browser?.browserType().name(),
|
|
285
|
+
inputFileRoleTextbox: false,
|
|
286
|
+
customEngines: [],
|
|
287
|
+
isUnderTest: true,
|
|
288
|
+
});
|
|
320
289
|
}
|
|
321
290
|
|
|
322
291
|
async _initBrowser({ url }) {
|
|
323
292
|
this.#remoteDebuggerPort = await findAvailablePort();
|
|
324
293
|
process.env.CDP_LISTEN_PORT = this.#remoteDebuggerPort;
|
|
325
294
|
|
|
326
|
-
this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
|
|
295
|
+
// this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
|
|
327
296
|
this.world = { attach: () => {} };
|
|
328
297
|
|
|
329
298
|
const ai_config_file = path.join(this.projectDir, "ai_config.json");
|
|
@@ -332,27 +301,49 @@ export class BVTRecorder {
|
|
|
332
301
|
try {
|
|
333
302
|
ai_config = JSON.parse(readFileSync(ai_config_file, "utf8"));
|
|
334
303
|
} catch (error) {
|
|
335
|
-
|
|
304
|
+
this.logger.error("Error reading ai_config.json", error);
|
|
336
305
|
}
|
|
337
306
|
}
|
|
307
|
+
this.config = ai_config;
|
|
338
308
|
const initScripts = {
|
|
339
|
-
recorderCjs: injectedScriptSource,
|
|
309
|
+
// recorderCjs: injectedScriptSource,
|
|
340
310
|
scripts: [
|
|
341
311
|
this.getInitScripts(ai_config),
|
|
342
312
|
`\ndelete Object.getPrototypeOf(navigator).webdriver;${process.env.WINDOW_DEBUGGER ? "window.debug=true;\n" : ""}`,
|
|
343
313
|
],
|
|
344
314
|
};
|
|
345
315
|
|
|
346
|
-
let startTime = Date.now();
|
|
347
316
|
const bvtContext = await initContext(url, false, false, this.world, 450, initScripts, this.envName);
|
|
348
|
-
let stopTime = Date.now();
|
|
349
|
-
this.logger.info(`Browser launched in ${(stopTime - startTime) / 1000} s`);
|
|
350
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
|
+
});
|
|
351
342
|
const context = bvtContext.playContext;
|
|
352
343
|
this.context = context;
|
|
353
344
|
this.web = bvtContext.stable || bvtContext.web;
|
|
345
|
+
this.web.tryAllStrategies = true;
|
|
354
346
|
this.page = bvtContext.page;
|
|
355
|
-
|
|
356
347
|
this.pageSet.add(this.page);
|
|
357
348
|
this.lastKnownUrlPath = this._updateUrlPath();
|
|
358
349
|
const browser = await this.context.browser();
|
|
@@ -380,13 +371,15 @@ export class BVTRecorder {
|
|
|
380
371
|
}
|
|
381
372
|
return;
|
|
382
373
|
} catch (error) {
|
|
383
|
-
console.error("Error evaluting in context:", error);
|
|
374
|
+
// console.error("Error evaluting in context:", error);
|
|
375
|
+
this.logger.error("Error evaluating in context:", error);
|
|
384
376
|
}
|
|
385
377
|
}
|
|
386
378
|
}
|
|
387
379
|
|
|
388
380
|
getMode() {
|
|
389
|
-
console.log("getMode", this.#mode);
|
|
381
|
+
// console.log("getMode", this.#mode);
|
|
382
|
+
this.logger.info("Current mode:", this.#mode);
|
|
390
383
|
return this.#mode;
|
|
391
384
|
}
|
|
392
385
|
|
|
@@ -428,6 +421,8 @@ export class BVTRecorder {
|
|
|
428
421
|
this.sendEvent(this.events.onBrowserClose);
|
|
429
422
|
}
|
|
430
423
|
} catch (error) {
|
|
424
|
+
this.logger.error("Error in page close event");
|
|
425
|
+
this.logger.error(error);
|
|
431
426
|
console.error("Error in page close event");
|
|
432
427
|
console.error(error);
|
|
433
428
|
}
|
|
@@ -438,8 +433,10 @@ export class BVTRecorder {
|
|
|
438
433
|
if (frame !== page.mainFrame()) return;
|
|
439
434
|
this.handlePageTransition();
|
|
440
435
|
} catch (error) {
|
|
436
|
+
this.logger.error("Error in handlePageTransition event");
|
|
437
|
+
this.logger.error(error);
|
|
441
438
|
console.error("Error in handlePageTransition event");
|
|
442
|
-
|
|
439
|
+
console.error(error);
|
|
443
440
|
}
|
|
444
441
|
try {
|
|
445
442
|
if (frame !== this.#activeFrame) return;
|
|
@@ -458,11 +455,82 @@ export class BVTRecorder {
|
|
|
458
455
|
// await this._setRecordingMode(frame);
|
|
459
456
|
// await this._initPage(page);
|
|
460
457
|
} catch (error) {
|
|
458
|
+
this.logger.error("Error in frame navigate event");
|
|
459
|
+
this.logger.error(error);
|
|
461
460
|
console.error("Error in frame navigate event");
|
|
462
461
|
// console.error(error);
|
|
463
462
|
}
|
|
464
463
|
});
|
|
465
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
|
+
|
|
466
534
|
async getCurrentTransition() {
|
|
467
535
|
if (this?.web?.browser?._name !== "chromium") {
|
|
468
536
|
return;
|
|
@@ -471,35 +539,68 @@ export class BVTRecorder {
|
|
|
471
539
|
|
|
472
540
|
try {
|
|
473
541
|
const result = await client.send("Page.getNavigationHistory");
|
|
474
|
-
// console.log("result", result);
|
|
475
542
|
const entries = result.entries;
|
|
476
543
|
const currentIndex = result.currentIndex;
|
|
477
544
|
|
|
478
|
-
// ignore if currentIndex is not the last entry
|
|
479
|
-
if (currentIndex !== entries.length - 1) return;
|
|
480
|
-
|
|
481
545
|
const currentEntry = entries[currentIndex];
|
|
482
|
-
|
|
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
|
+
};
|
|
483
556
|
} catch (error) {
|
|
484
|
-
|
|
557
|
+
this.logger.error("Error in getCurrentTransition event");
|
|
558
|
+
this.logger.error(error);
|
|
559
|
+
console.error("Error in getTransistionType event", error);
|
|
485
560
|
} finally {
|
|
486
561
|
await client.detach();
|
|
487
562
|
}
|
|
488
563
|
}
|
|
489
|
-
|
|
564
|
+
userInitiatedTransitionTypes = ["typed", "address_bar"];
|
|
490
565
|
async handlePageTransition() {
|
|
491
566
|
const transition = await this.getCurrentTransition();
|
|
492
567
|
if (!transition) return;
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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;
|
|
503
604
|
}
|
|
504
605
|
}
|
|
505
606
|
|
|
@@ -523,6 +624,8 @@ export class BVTRecorder {
|
|
|
523
624
|
// add listener for frame navigation on new tab
|
|
524
625
|
this._addFrameNavigateListener(page);
|
|
525
626
|
} catch (error) {
|
|
627
|
+
this.logger.error("Error in page event");
|
|
628
|
+
this.logger.error(error);
|
|
526
629
|
console.error("Error in page event");
|
|
527
630
|
console.error(error);
|
|
528
631
|
}
|
|
@@ -564,6 +667,7 @@ export class BVTRecorder {
|
|
|
564
667
|
const { data } = await client.send("Page.captureScreenshot", { format: "png" });
|
|
565
668
|
return data;
|
|
566
669
|
} catch (error) {
|
|
670
|
+
this.logger.error("Error in taking browser screenshot");
|
|
567
671
|
console.error("Error in taking browser screenshot", error);
|
|
568
672
|
} finally {
|
|
569
673
|
await client.detach();
|
|
@@ -628,6 +732,11 @@ export class BVTRecorder {
|
|
|
628
732
|
delete process.env.TEMP_RUN;
|
|
629
733
|
await this.watcher.close().then(() => {});
|
|
630
734
|
this.watcher = null;
|
|
735
|
+
this.previousIndex = null;
|
|
736
|
+
this.previousHistoryLength = null;
|
|
737
|
+
this.previousUrl = null;
|
|
738
|
+
this.previousEntries = null;
|
|
739
|
+
|
|
631
740
|
await closeContext();
|
|
632
741
|
this.pageSet.clear();
|
|
633
742
|
}
|
|
@@ -704,40 +813,62 @@ export class BVTRecorder {
|
|
|
704
813
|
async abortExecution() {
|
|
705
814
|
await this.stepRunner.abortExecution();
|
|
706
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
|
+
|
|
707
825
|
async dealyedRevertMode() {
|
|
708
826
|
const timerId = setTimeout(async () => {
|
|
709
827
|
await this.revertMode();
|
|
710
828
|
}, 100);
|
|
711
829
|
this.timerId = timerId;
|
|
712
830
|
}
|
|
713
|
-
async runStep({ step, parametersMap }, options) {
|
|
831
|
+
async runStep({ step, parametersMap, tags, isFirstStep, listenNetwork }, options) {
|
|
832
|
+
const { skipAfter = true, skipBefore = !isFirstStep } = options || {};
|
|
714
833
|
const _env = {
|
|
715
834
|
TOKEN: this.TOKEN,
|
|
716
835
|
TEMP_RUN: true,
|
|
717
836
|
REPORT_FOLDER: this.bvtContext.reportFolder,
|
|
718
837
|
BLINQ_ENV: this.envName,
|
|
838
|
+
STORE_DETAILED_NETWORK_DATA: listenNetwork ? "true" : "false",
|
|
839
|
+
CURRENT_STEP_ID: step.id,
|
|
840
|
+
DEBUG: "blinq:route",
|
|
719
841
|
};
|
|
720
842
|
|
|
721
843
|
this.bvtContext.navigate = true;
|
|
844
|
+
this.bvtContext.loadedRoutes = null;
|
|
722
845
|
for (const [key, value] of Object.entries(_env)) {
|
|
723
846
|
process.env[key] = value;
|
|
724
847
|
}
|
|
848
|
+
|
|
725
849
|
if (this.timerId) {
|
|
726
850
|
clearTimeout(this.timerId);
|
|
727
851
|
this.timerId = null;
|
|
728
852
|
}
|
|
729
853
|
await this.setMode("running");
|
|
854
|
+
|
|
730
855
|
try {
|
|
731
|
-
await this.stepRunner.runStep(
|
|
856
|
+
const { result, info } = await this.stepRunner.runStep(
|
|
732
857
|
{
|
|
733
858
|
step,
|
|
734
859
|
parametersMap,
|
|
735
860
|
envPath: this.envName,
|
|
861
|
+
tags,
|
|
862
|
+
config: this.config,
|
|
736
863
|
},
|
|
737
864
|
this.bvtContext,
|
|
738
|
-
|
|
865
|
+
{
|
|
866
|
+
skipAfter,
|
|
867
|
+
skipBefore,
|
|
868
|
+
}
|
|
739
869
|
);
|
|
740
870
|
await this.revertMode();
|
|
871
|
+
return { info };
|
|
741
872
|
} catch (error) {
|
|
742
873
|
await this.revertMode();
|
|
743
874
|
throw error;
|
|
@@ -748,15 +879,10 @@ export class BVTRecorder {
|
|
|
748
879
|
this.bvtContext.navigate = false;
|
|
749
880
|
}
|
|
750
881
|
}
|
|
751
|
-
async runScenario({ steps, parametersMap }) {
|
|
752
|
-
for (const step of steps) {
|
|
753
|
-
await this.runStep({ step, parametersMap });
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
882
|
async saveScenario({ scenario, featureName, override, isSingleStep }) {
|
|
757
883
|
await updateStepDefinitions({ scenario, featureName, projectDir: this.projectDir }); // updates mjs files
|
|
758
884
|
if (!isSingleStep) await updateFeatureFile({ featureName, scenario, override, projectDir: this.projectDir }); // updates gherkin files
|
|
759
|
-
await this.cleanup();
|
|
885
|
+
await this.cleanup({ tags: scenario.tags });
|
|
760
886
|
}
|
|
761
887
|
async getImplementedSteps() {
|
|
762
888
|
const stepsAndScenarios = await getImplementedSteps(this.projectDir);
|
|
@@ -772,7 +898,15 @@ export class BVTRecorder {
|
|
|
772
898
|
};
|
|
773
899
|
}
|
|
774
900
|
async getStepsAndCommandsForScenario({ name, featureName }) {
|
|
775
|
-
|
|
901
|
+
const steps = this.scenariosStepsMap.get(name) || [];
|
|
902
|
+
for (const step of steps) {
|
|
903
|
+
if (step.isImplemented) {
|
|
904
|
+
step.commands = this.getCommandsForImplementedStep({ stepName: step.text });
|
|
905
|
+
} else {
|
|
906
|
+
step.commands = [];
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
return steps;
|
|
776
910
|
// return getStepsAndCommandsForScenario({
|
|
777
911
|
// name,
|
|
778
912
|
// featureName,
|
|
@@ -780,6 +914,7 @@ export class BVTRecorder {
|
|
|
780
914
|
// map: this.scenariosStepsMap,
|
|
781
915
|
// });
|
|
782
916
|
}
|
|
917
|
+
|
|
783
918
|
async generateStepName({ commands, stepsNames, parameters, map }) {
|
|
784
919
|
return await this.namesService.generateStepName({ commands, stepsNames, parameters, map });
|
|
785
920
|
}
|
|
@@ -831,10 +966,11 @@ export class BVTRecorder {
|
|
|
831
966
|
if (existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
|
|
832
967
|
try {
|
|
833
968
|
const testData = JSON.parse(readFileSync(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
|
|
834
|
-
this.logger.info("Test data", testData);
|
|
969
|
+
// this.logger.info("Test data", testData);
|
|
835
970
|
this.sendEvent(this.events.getTestData, testData);
|
|
836
971
|
} catch (e) {
|
|
837
|
-
this.logger.error("Error reading test data file", e);
|
|
972
|
+
// this.logger.error("Error reading test data file", e);
|
|
973
|
+
console.log("Error reading test data file", e);
|
|
838
974
|
}
|
|
839
975
|
}
|
|
840
976
|
|
|
@@ -843,10 +979,12 @@ export class BVTRecorder {
|
|
|
843
979
|
this.watcher.on("all", async (event, path) => {
|
|
844
980
|
try {
|
|
845
981
|
const testData = JSON.parse(await readFile(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
|
|
846
|
-
this.logger.info("Test data", testData);
|
|
982
|
+
// this.logger.info("Test data", testData);
|
|
983
|
+
console.log("Test data changed", testData);
|
|
847
984
|
this.sendEvent(this.events.getTestData, testData);
|
|
848
985
|
} catch (e) {
|
|
849
|
-
this.logger.error("Error reading test data file", e);
|
|
986
|
+
// this.logger.error("Error reading test data file", e);
|
|
987
|
+
console.log("Error reading test data file", e);
|
|
850
988
|
}
|
|
851
989
|
});
|
|
852
990
|
}
|
|
@@ -862,9 +1000,9 @@ export class BVTRecorder {
|
|
|
862
1000
|
}
|
|
863
1001
|
}
|
|
864
1002
|
|
|
865
|
-
async discardTestData() {
|
|
1003
|
+
async discardTestData({ tags }) {
|
|
866
1004
|
resetTestData(this.envName, this.world);
|
|
867
|
-
await this.cleanup();
|
|
1005
|
+
await this.cleanup({ tags });
|
|
868
1006
|
}
|
|
869
1007
|
async addToTestData(obj) {
|
|
870
1008
|
if (!existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
|
|
@@ -879,7 +1017,7 @@ export class BVTRecorder {
|
|
|
879
1017
|
.filter((file) => file.endsWith(".feature"))
|
|
880
1018
|
.map((file) => path.join(this.projectDir, "features", file));
|
|
881
1019
|
try {
|
|
882
|
-
const parsedFiles = featureFiles.map((file) => parseFeatureFile(file));
|
|
1020
|
+
const parsedFiles = featureFiles.map((file) => this.parseFeatureFile(file));
|
|
883
1021
|
const output = {};
|
|
884
1022
|
parsedFiles.forEach((file) => {
|
|
885
1023
|
if (!file.feature) return;
|
|
@@ -903,10 +1041,11 @@ export class BVTRecorder {
|
|
|
903
1041
|
const stepParams = parseStepTextParameters(stepName);
|
|
904
1042
|
return getCommandsForImplementedStep(stepName, step_definitions, stepParams).commands;
|
|
905
1043
|
}
|
|
1044
|
+
|
|
906
1045
|
loadExistingScenario({ featureName, scenarioName }) {
|
|
907
1046
|
const step_definitions = loadStepDefinitions(this.projectDir);
|
|
908
1047
|
const featureFilePath = path.join(this.projectDir, "features", featureName);
|
|
909
|
-
const gherkinDoc = parseFeatureFile(featureFilePath);
|
|
1048
|
+
const gherkinDoc = this.parseFeatureFile(featureFilePath);
|
|
910
1049
|
const scenario = gherkinDoc.feature.children.find((child) => child.scenario.name === scenarioName)?.scenario;
|
|
911
1050
|
|
|
912
1051
|
const steps = [];
|
|
@@ -931,6 +1070,7 @@ export class BVTRecorder {
|
|
|
931
1070
|
..._s,
|
|
932
1071
|
keyword: step.keyword.trim(),
|
|
933
1072
|
};
|
|
1073
|
+
parseRouteFiles(this.projectDir, _step);
|
|
934
1074
|
steps.push(_step);
|
|
935
1075
|
}
|
|
936
1076
|
return {
|
|
@@ -970,7 +1110,7 @@ export class BVTRecorder {
|
|
|
970
1110
|
}
|
|
971
1111
|
return result;
|
|
972
1112
|
}
|
|
973
|
-
async cleanup() {
|
|
1113
|
+
async cleanup({ tags }) {
|
|
974
1114
|
const noopStep = {
|
|
975
1115
|
text: "Noop",
|
|
976
1116
|
isImplemented: true,
|
|
@@ -984,6 +1124,7 @@ export class BVTRecorder {
|
|
|
984
1124
|
{
|
|
985
1125
|
step: noopStep,
|
|
986
1126
|
parametersMap: {},
|
|
1127
|
+
tags: tags || [],
|
|
987
1128
|
},
|
|
988
1129
|
{
|
|
989
1130
|
skipAfter: false,
|
|
@@ -1022,20 +1163,62 @@ export class BVTRecorder {
|
|
|
1022
1163
|
return false;
|
|
1023
1164
|
}
|
|
1024
1165
|
}
|
|
1025
|
-
}
|
|
1166
|
+
async initExecution({ tags = [] }) {
|
|
1167
|
+
// run before hooks
|
|
1168
|
+
const noopStep = {
|
|
1169
|
+
text: "Noop",
|
|
1170
|
+
isImplemented: true,
|
|
1171
|
+
};
|
|
1172
|
+
await this.runStep(
|
|
1173
|
+
{
|
|
1174
|
+
step: noopStep,
|
|
1175
|
+
parametersMap: {},
|
|
1176
|
+
tags,
|
|
1177
|
+
},
|
|
1178
|
+
{
|
|
1179
|
+
skipBefore: false,
|
|
1180
|
+
skipAfter: true,
|
|
1181
|
+
}
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
async cleanupExecution({ tags = [] }) {
|
|
1185
|
+
// run after hooks
|
|
1186
|
+
const noopStep = {
|
|
1187
|
+
text: "Noop",
|
|
1188
|
+
isImplemented: true,
|
|
1189
|
+
};
|
|
1190
|
+
await this.runStep(
|
|
1191
|
+
{
|
|
1192
|
+
step: noopStep,
|
|
1193
|
+
parametersMap: {},
|
|
1194
|
+
tags,
|
|
1195
|
+
},
|
|
1196
|
+
{
|
|
1197
|
+
skipBefore: true,
|
|
1198
|
+
skipAfter: false,
|
|
1199
|
+
}
|
|
1200
|
+
);
|
|
1201
|
+
}
|
|
1202
|
+
async resetExecution({ tags = [] }) {
|
|
1203
|
+
// run after hooks followed by before hooks
|
|
1204
|
+
await this.cleanupExecution({ tags });
|
|
1205
|
+
await this.initExecution({ tags });
|
|
1206
|
+
}
|
|
1026
1207
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1208
|
+
parseFeatureFile(featureFilePath) {
|
|
1209
|
+
try {
|
|
1210
|
+
let id = 0;
|
|
1211
|
+
const uuidFn = () => (++id).toString(16);
|
|
1212
|
+
const builder = new AstBuilder(uuidFn);
|
|
1213
|
+
const matcher = new GherkinClassicTokenMatcher();
|
|
1214
|
+
const parser = new Parser(builder, matcher);
|
|
1215
|
+
const source = readFileSync(featureFilePath, "utf8");
|
|
1216
|
+
const gherkinDocument = parser.parse(source);
|
|
1217
|
+
return gherkinDocument;
|
|
1218
|
+
} catch (e) {
|
|
1219
|
+
this.logger.error(`Error parsing feature file: ${featureFilePath}`);
|
|
1220
|
+
console.log(e);
|
|
1221
|
+
}
|
|
1222
|
+
return {};
|
|
1039
1223
|
}
|
|
1040
|
-
|
|
1041
|
-
};
|
|
1224
|
+
}
|