@dev-blinq/cucumber_client 1.0.1250-dev → 1.0.1250-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/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 +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 +298 -123
- 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 +3 -15
- 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 +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,61 @@ 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
|
+
DEBUG: "blinq:route",
|
|
719
840
|
};
|
|
720
841
|
|
|
721
842
|
this.bvtContext.navigate = true;
|
|
843
|
+
this.bvtContext.loadedRoutes = null;
|
|
722
844
|
for (const [key, value] of Object.entries(_env)) {
|
|
723
845
|
process.env[key] = value;
|
|
724
846
|
}
|
|
847
|
+
|
|
725
848
|
if (this.timerId) {
|
|
726
849
|
clearTimeout(this.timerId);
|
|
727
850
|
this.timerId = null;
|
|
728
851
|
}
|
|
729
852
|
await this.setMode("running");
|
|
853
|
+
|
|
730
854
|
try {
|
|
731
|
-
await this.stepRunner.runStep(
|
|
855
|
+
const { result, info } = await this.stepRunner.runStep(
|
|
732
856
|
{
|
|
733
857
|
step,
|
|
734
858
|
parametersMap,
|
|
735
859
|
envPath: this.envName,
|
|
860
|
+
tags,
|
|
861
|
+
config: this.config,
|
|
736
862
|
},
|
|
737
863
|
this.bvtContext,
|
|
738
|
-
|
|
864
|
+
{
|
|
865
|
+
skipAfter,
|
|
866
|
+
skipBefore,
|
|
867
|
+
}
|
|
739
868
|
);
|
|
740
869
|
await this.revertMode();
|
|
870
|
+
return { info };
|
|
741
871
|
} catch (error) {
|
|
742
872
|
await this.revertMode();
|
|
743
873
|
throw error;
|
|
@@ -748,15 +878,10 @@ export class BVTRecorder {
|
|
|
748
878
|
this.bvtContext.navigate = false;
|
|
749
879
|
}
|
|
750
880
|
}
|
|
751
|
-
async runScenario({ steps, parametersMap }) {
|
|
752
|
-
for (const step of steps) {
|
|
753
|
-
await this.runStep({ step, parametersMap });
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
881
|
async saveScenario({ scenario, featureName, override, isSingleStep }) {
|
|
757
882
|
await updateStepDefinitions({ scenario, featureName, projectDir: this.projectDir }); // updates mjs files
|
|
758
883
|
if (!isSingleStep) await updateFeatureFile({ featureName, scenario, override, projectDir: this.projectDir }); // updates gherkin files
|
|
759
|
-
await this.cleanup();
|
|
884
|
+
await this.cleanup({ tags: scenario.tags });
|
|
760
885
|
}
|
|
761
886
|
async getImplementedSteps() {
|
|
762
887
|
const stepsAndScenarios = await getImplementedSteps(this.projectDir);
|
|
@@ -780,6 +905,7 @@ export class BVTRecorder {
|
|
|
780
905
|
step.commands = [];
|
|
781
906
|
}
|
|
782
907
|
}
|
|
908
|
+
return steps;
|
|
783
909
|
// return getStepsAndCommandsForScenario({
|
|
784
910
|
// name,
|
|
785
911
|
// featureName,
|
|
@@ -787,6 +913,7 @@ export class BVTRecorder {
|
|
|
787
913
|
// map: this.scenariosStepsMap,
|
|
788
914
|
// });
|
|
789
915
|
}
|
|
916
|
+
|
|
790
917
|
async generateStepName({ commands, stepsNames, parameters, map }) {
|
|
791
918
|
return await this.namesService.generateStepName({ commands, stepsNames, parameters, map });
|
|
792
919
|
}
|
|
@@ -838,10 +965,11 @@ export class BVTRecorder {
|
|
|
838
965
|
if (existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
|
|
839
966
|
try {
|
|
840
967
|
const testData = JSON.parse(readFileSync(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
|
|
841
|
-
this.logger.info("Test data", testData);
|
|
968
|
+
// this.logger.info("Test data", testData);
|
|
842
969
|
this.sendEvent(this.events.getTestData, testData);
|
|
843
970
|
} catch (e) {
|
|
844
|
-
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);
|
|
845
973
|
}
|
|
846
974
|
}
|
|
847
975
|
|
|
@@ -850,10 +978,12 @@ export class BVTRecorder {
|
|
|
850
978
|
this.watcher.on("all", async (event, path) => {
|
|
851
979
|
try {
|
|
852
980
|
const testData = JSON.parse(await readFile(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
|
|
853
|
-
this.logger.info("Test data", testData);
|
|
981
|
+
// this.logger.info("Test data", testData);
|
|
982
|
+
console.log("Test data changed", testData);
|
|
854
983
|
this.sendEvent(this.events.getTestData, testData);
|
|
855
984
|
} catch (e) {
|
|
856
|
-
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);
|
|
857
987
|
}
|
|
858
988
|
});
|
|
859
989
|
}
|
|
@@ -869,9 +999,9 @@ export class BVTRecorder {
|
|
|
869
999
|
}
|
|
870
1000
|
}
|
|
871
1001
|
|
|
872
|
-
async discardTestData() {
|
|
1002
|
+
async discardTestData({ tags }) {
|
|
873
1003
|
resetTestData(this.envName, this.world);
|
|
874
|
-
await this.cleanup();
|
|
1004
|
+
await this.cleanup({ tags });
|
|
875
1005
|
}
|
|
876
1006
|
async addToTestData(obj) {
|
|
877
1007
|
if (!existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
|
|
@@ -886,7 +1016,7 @@ export class BVTRecorder {
|
|
|
886
1016
|
.filter((file) => file.endsWith(".feature"))
|
|
887
1017
|
.map((file) => path.join(this.projectDir, "features", file));
|
|
888
1018
|
try {
|
|
889
|
-
const parsedFiles = featureFiles.map((file) => parseFeatureFile(file));
|
|
1019
|
+
const parsedFiles = featureFiles.map((file) => this.parseFeatureFile(file));
|
|
890
1020
|
const output = {};
|
|
891
1021
|
parsedFiles.forEach((file) => {
|
|
892
1022
|
if (!file.feature) return;
|
|
@@ -910,10 +1040,11 @@ export class BVTRecorder {
|
|
|
910
1040
|
const stepParams = parseStepTextParameters(stepName);
|
|
911
1041
|
return getCommandsForImplementedStep(stepName, step_definitions, stepParams).commands;
|
|
912
1042
|
}
|
|
1043
|
+
|
|
913
1044
|
loadExistingScenario({ featureName, scenarioName }) {
|
|
914
1045
|
const step_definitions = loadStepDefinitions(this.projectDir);
|
|
915
1046
|
const featureFilePath = path.join(this.projectDir, "features", featureName);
|
|
916
|
-
const gherkinDoc = parseFeatureFile(featureFilePath);
|
|
1047
|
+
const gherkinDoc = this.parseFeatureFile(featureFilePath);
|
|
917
1048
|
const scenario = gherkinDoc.feature.children.find((child) => child.scenario.name === scenarioName)?.scenario;
|
|
918
1049
|
|
|
919
1050
|
const steps = [];
|
|
@@ -938,6 +1069,7 @@ export class BVTRecorder {
|
|
|
938
1069
|
..._s,
|
|
939
1070
|
keyword: step.keyword.trim(),
|
|
940
1071
|
};
|
|
1072
|
+
parseRouteFiles(this.projectDir, _step);
|
|
941
1073
|
steps.push(_step);
|
|
942
1074
|
}
|
|
943
1075
|
return {
|
|
@@ -977,7 +1109,7 @@ export class BVTRecorder {
|
|
|
977
1109
|
}
|
|
978
1110
|
return result;
|
|
979
1111
|
}
|
|
980
|
-
async cleanup() {
|
|
1112
|
+
async cleanup({ tags }) {
|
|
981
1113
|
const noopStep = {
|
|
982
1114
|
text: "Noop",
|
|
983
1115
|
isImplemented: true,
|
|
@@ -991,6 +1123,7 @@ export class BVTRecorder {
|
|
|
991
1123
|
{
|
|
992
1124
|
step: noopStep,
|
|
993
1125
|
parametersMap: {},
|
|
1126
|
+
tags: tags || [],
|
|
994
1127
|
},
|
|
995
1128
|
{
|
|
996
1129
|
skipAfter: false,
|
|
@@ -1029,20 +1162,62 @@ export class BVTRecorder {
|
|
|
1029
1162
|
return false;
|
|
1030
1163
|
}
|
|
1031
1164
|
}
|
|
1032
|
-
}
|
|
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
|
+
}
|
|
1033
1206
|
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
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 {};
|
|
1046
1222
|
}
|
|
1047
|
-
|
|
1048
|
-
};
|
|
1223
|
+
}
|