@dev-blinq/cucumber_client 1.0.1318-dev → 1.0.1318-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 +108 -108
- package/bin/assets/preload/css_gen.js +10 -10
- package/bin/assets/preload/recorderv3.js +3 -1
- package/bin/assets/preload/toolbar.js +27 -29
- package/bin/assets/preload/unique_locators.js +1 -1
- package/bin/assets/preload/yaml.js +288 -275
- package/bin/assets/scripts/aria_snapshot.js +223 -220
- package/bin/assets/scripts/dom_attr.js +329 -329
- package/bin/assets/scripts/dom_parent.js +169 -174
- package/bin/assets/scripts/event_utils.js +94 -94
- package/bin/assets/scripts/pw.js +2050 -1949
- package/bin/assets/scripts/recorder.js +5 -17
- package/bin/assets/scripts/snapshot_capturer.js +153 -146
- package/bin/assets/scripts/unique_locators.js +156 -48
- package/bin/assets/scripts/yaml.js +796 -783
- package/bin/assets/templates/_hooks_template.txt +41 -0
- package/bin/assets/templates/utils_template.txt +1 -44
- 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/api_codegen.js +2 -2
- package/bin/client/code_gen/code_inversion.js +53 -4
- package/bin/client/code_gen/page_reflection.js +839 -906
- package/bin/client/code_gen/playwright_codeget.js +32 -17
- package/bin/client/cucumber/feature.js +89 -27
- package/bin/client/cucumber/feature_data.js +2 -2
- package/bin/client/cucumber/project_to_document.js +9 -3
- package/bin/client/cucumber/steps_definitions.js +90 -87
- package/bin/client/cucumber_selector.js +17 -1
- package/bin/client/local_agent.js +6 -5
- package/bin/client/parse_feature_file.js +23 -26
- package/bin/client/playground/projects/env.json +2 -2
- package/bin/client/project.js +186 -196
- package/bin/client/recorderv3/bvt_recorder.js +187 -76
- package/bin/client/recorderv3/implemented_steps.js +74 -16
- package/bin/client/recorderv3/index.js +68 -54
- package/bin/client/recorderv3/scriptTest.js +1 -1
- package/bin/client/recorderv3/services.js +4 -16
- package/bin/client/recorderv3/step_runner.js +313 -170
- package/bin/client/recorderv3/step_utils.js +516 -4
- package/bin/client/recorderv3/update_feature.js +32 -30
- 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/upload-service.js +2 -2
- package/bin/client/utils/socket_logger.js +132 -0
- package/bin/index.js +1 -0
- package/bin/logger.js +3 -2
- package/bin/min/consoleApi.min.cjs +2 -3
- package/bin/min/injectedScript.min.cjs +16 -16
- package/package.json +21 -12
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// define the jsdoc type for the input
|
|
2
2
|
import { closeContext, initContext, _getDataFile, resetTestData } from "automation_model";
|
|
3
|
-
import { existsSync,
|
|
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,11 +12,11 @@ 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
|
-
import
|
|
19
|
-
|
|
17
|
+
import socketLogger from "../utils/socket_logger.js";
|
|
18
|
+
import { tmpdir } from "os";
|
|
19
|
+
import { faker } from "@faker-js/faker";
|
|
20
20
|
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
21
21
|
|
|
22
22
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -45,15 +45,15 @@ async function evaluate(frame, script) {
|
|
|
45
45
|
async function findNestedFrameSelector(frame, obj) {
|
|
46
46
|
try {
|
|
47
47
|
const parent = frame.parentFrame();
|
|
48
|
-
if (parent) console.log(`Parent frame: ${JSON.stringify(parent)}`);
|
|
49
48
|
if (!parent) return { children: obj };
|
|
50
49
|
const frameElement = await frame.frameElement();
|
|
51
50
|
if (!frameElement) return;
|
|
52
51
|
const selectors = await parent.evaluate((element) => {
|
|
53
|
-
return window.
|
|
52
|
+
return window.__bvt_Recorder.locatorGenerator.getElementLocators(element, { excludeText: true }).locators;
|
|
54
53
|
}, frameElement);
|
|
55
54
|
return findNestedFrameSelector(parent, { children: obj, selectors });
|
|
56
55
|
} catch (e) {
|
|
56
|
+
socketLogger.error(`Error in findNestedFrameSelector: ${e}`);
|
|
57
57
|
console.error(e);
|
|
58
58
|
}
|
|
59
59
|
}
|
|
@@ -150,6 +150,7 @@ const transformAction = (action, el, isVerify, isPopupCloseClick, isInHoverMode,
|
|
|
150
150
|
};
|
|
151
151
|
}
|
|
152
152
|
default: {
|
|
153
|
+
socketLogger.error(`Action not supported: ${action.name}`);
|
|
153
154
|
console.log("action not supported", action);
|
|
154
155
|
throw new Error("action not supported");
|
|
155
156
|
}
|
|
@@ -161,6 +162,7 @@ const transformAction = (action, el, isVerify, isPopupCloseClick, isInHoverMode,
|
|
|
161
162
|
* @property {string} projectDir
|
|
162
163
|
* @property {string} TOKEN
|
|
163
164
|
* @property {(name:string, data:any)=> void} sendEvent
|
|
165
|
+
* @property {Object} logger
|
|
164
166
|
*/
|
|
165
167
|
export class BVTRecorder {
|
|
166
168
|
#currentURL = "";
|
|
@@ -174,7 +176,6 @@ export class BVTRecorder {
|
|
|
174
176
|
*/
|
|
175
177
|
constructor(initialState) {
|
|
176
178
|
Object.assign(this, initialState);
|
|
177
|
-
this.logger = logger;
|
|
178
179
|
this.screenshotMap = new Map();
|
|
179
180
|
this.snapshotMap = new Map();
|
|
180
181
|
this.scenariosStepsMap = new Map();
|
|
@@ -184,37 +185,16 @@ export class BVTRecorder {
|
|
|
184
185
|
projectDir: this.projectDir,
|
|
185
186
|
logger: this.logger,
|
|
186
187
|
});
|
|
187
|
-
this.stepRunner = new BVTStepRunner({
|
|
188
|
-
projectDir: this.projectDir,
|
|
189
|
-
sendExecutionStatus: (data) => {
|
|
190
|
-
if (data && data.type) {
|
|
191
|
-
switch (data.type) {
|
|
192
|
-
case "cmdExecutionStart":
|
|
193
|
-
console.log("Sending cmdExecutionStart event for cmdId:", data.cmdId);
|
|
194
|
-
this.sendEvent(this.events.cmdExecutionStart, data.cmdId);
|
|
195
|
-
break;
|
|
196
|
-
case "cmdExecutionSuccess":
|
|
197
|
-
console.log("Sending cmdExecutionSuccess event for cmdId:", data.cmdId);
|
|
198
|
-
this.sendEvent(this.events.cmdExecutionSuccess, data.cmdId);
|
|
199
|
-
break;
|
|
200
|
-
case "cmdExecutionFailure":
|
|
201
|
-
console.log("Sending cmdExecutionFailure event for cmdId:", data.cmdId);
|
|
202
|
-
this.sendEvent(this.events.cmdExecutionFailure, data.cmdId);
|
|
203
|
-
break;
|
|
204
|
-
default:
|
|
205
|
-
console.warn("Unknown command execution status type:", data.type);
|
|
206
|
-
break;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
},
|
|
210
|
-
});
|
|
211
188
|
this.pageSet = new Set();
|
|
212
|
-
this.
|
|
189
|
+
this.pageMetaDataSet = new Set();
|
|
213
190
|
this.lastKnownUrlPath = "";
|
|
214
|
-
// TODO: what is world?
|
|
215
191
|
this.world = { attach: () => {} };
|
|
216
192
|
this.shouldTakeScreenshot = true;
|
|
217
193
|
this.watcher = null;
|
|
194
|
+
this.networkEventsFolder = path.join(tmpdir(), "blinq_network_events");
|
|
195
|
+
if (existsSync(this.networkEventsFolder)) {
|
|
196
|
+
rmSync(this.networkEventsFolder, { recursive: true, force: true });
|
|
197
|
+
}
|
|
218
198
|
}
|
|
219
199
|
events = {
|
|
220
200
|
onFrameNavigate: "BVTRecorder.onFrameNavigate",
|
|
@@ -227,13 +207,14 @@ export class BVTRecorder {
|
|
|
227
207
|
onGoto: "BVTRecorder.onGoto",
|
|
228
208
|
cmdExecutionStart: "BVTRecorder.cmdExecutionStart",
|
|
229
209
|
cmdExecutionSuccess: "BVTRecorder.cmdExecutionSuccess",
|
|
230
|
-
|
|
210
|
+
cmdExecutionError: "BVTRecorder.cmdExecutionError",
|
|
211
|
+
interceptResults: "BVTRecorder.interceptResults",
|
|
231
212
|
};
|
|
232
213
|
bindings = {
|
|
233
214
|
__bvt_recordCommand: async ({ frame, page, context }, event) => {
|
|
234
215
|
this.#activeFrame = frame;
|
|
235
216
|
const nestFrmLoc = await findNestedFrameSelector(frame);
|
|
236
|
-
|
|
217
|
+
this.logger.info(`Time taken for action: ${event.statistics.time}`);
|
|
237
218
|
await this.onAction({ ...event, nestFrmLoc });
|
|
238
219
|
},
|
|
239
220
|
__bvt_getMode: async () => {
|
|
@@ -252,8 +233,7 @@ export class BVTRecorder {
|
|
|
252
233
|
await this.onClosePopup();
|
|
253
234
|
},
|
|
254
235
|
__bvt_log: async (src, message) => {
|
|
255
|
-
|
|
256
|
-
console.log(`Inside Browser: ${message}`);
|
|
236
|
+
this.logger.info(`Inside Browser: ${message}`);
|
|
257
237
|
},
|
|
258
238
|
__bvt_getObject: (_src, obj) => {
|
|
259
239
|
this.processObject(obj);
|
|
@@ -265,7 +245,6 @@ export class BVTRecorder {
|
|
|
265
245
|
const locator = await this.web.page.locator(selector);
|
|
266
246
|
const snapshot = await locator.ariaSnapshot();
|
|
267
247
|
return snapshot;
|
|
268
|
-
// Triggering workflow
|
|
269
248
|
};
|
|
270
249
|
|
|
271
250
|
processObject = async ({ type, action, value }) => {
|
|
@@ -316,10 +295,11 @@ export class BVTRecorder {
|
|
|
316
295
|
}
|
|
317
296
|
|
|
318
297
|
async _initBrowser({ url }) {
|
|
298
|
+
socketLogger.info("Only present in 1.0.1293-stage");
|
|
319
299
|
this.#remoteDebuggerPort = await findAvailablePort();
|
|
320
300
|
process.env.CDP_LISTEN_PORT = this.#remoteDebuggerPort;
|
|
321
301
|
|
|
322
|
-
this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
|
|
302
|
+
// this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
|
|
323
303
|
this.world = { attach: () => {} };
|
|
324
304
|
|
|
325
305
|
const ai_config_file = path.join(this.projectDir, "ai_config.json");
|
|
@@ -328,9 +308,10 @@ export class BVTRecorder {
|
|
|
328
308
|
try {
|
|
329
309
|
ai_config = JSON.parse(readFileSync(ai_config_file, "utf8"));
|
|
330
310
|
} catch (error) {
|
|
331
|
-
|
|
311
|
+
this.logger.error("Error reading ai_config.json", error);
|
|
332
312
|
}
|
|
333
313
|
}
|
|
314
|
+
this.config = ai_config;
|
|
334
315
|
const initScripts = {
|
|
335
316
|
// recorderCjs: injectedScriptSource,
|
|
336
317
|
scripts: [
|
|
@@ -339,16 +320,37 @@ export class BVTRecorder {
|
|
|
339
320
|
],
|
|
340
321
|
};
|
|
341
322
|
|
|
342
|
-
let startTime = Date.now();
|
|
343
323
|
const bvtContext = await initContext(url, false, false, this.world, 450, initScripts, this.envName);
|
|
344
|
-
let stopTime = Date.now();
|
|
345
|
-
this.logger.info(`Browser launched in ${(stopTime - startTime) / 1000} s`);
|
|
346
324
|
this.bvtContext = bvtContext;
|
|
325
|
+
this.stepRunner = new BVTStepRunner({
|
|
326
|
+
projectDir: this.projectDir,
|
|
327
|
+
sendExecutionStatus: (data) => {
|
|
328
|
+
if (data && data.type) {
|
|
329
|
+
switch (data.type) {
|
|
330
|
+
case "cmdExecutionStart":
|
|
331
|
+
this.sendEvent(this.events.cmdExecutionStart, data);
|
|
332
|
+
break;
|
|
333
|
+
case "cmdExecutionSuccess":
|
|
334
|
+
this.sendEvent(this.events.cmdExecutionSuccess, data);
|
|
335
|
+
break;
|
|
336
|
+
case "cmdExecutionError":
|
|
337
|
+
this.sendEvent(this.events.cmdExecutionError, data);
|
|
338
|
+
break;
|
|
339
|
+
case "interceptResults":
|
|
340
|
+
this.sendEvent(this.events.interceptResults, data);
|
|
341
|
+
break;
|
|
342
|
+
default:
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
bvtContext: this.bvtContext,
|
|
348
|
+
});
|
|
347
349
|
const context = bvtContext.playContext;
|
|
348
350
|
this.context = context;
|
|
349
351
|
this.web = bvtContext.stable || bvtContext.web;
|
|
352
|
+
this.web.tryAllStrategies = true;
|
|
350
353
|
this.page = bvtContext.page;
|
|
351
|
-
|
|
352
354
|
this.pageSet.add(this.page);
|
|
353
355
|
this.lastKnownUrlPath = this._updateUrlPath();
|
|
354
356
|
const browser = await this.context.browser();
|
|
@@ -376,13 +378,15 @@ export class BVTRecorder {
|
|
|
376
378
|
}
|
|
377
379
|
return;
|
|
378
380
|
} catch (error) {
|
|
379
|
-
console.error("Error evaluting in context:", error);
|
|
381
|
+
// console.error("Error evaluting in context:", error);
|
|
382
|
+
this.logger.error("Error evaluating in context:", error);
|
|
380
383
|
}
|
|
381
384
|
}
|
|
382
385
|
}
|
|
383
386
|
|
|
384
387
|
getMode() {
|
|
385
|
-
console.log("getMode", this.#mode);
|
|
388
|
+
// console.log("getMode", this.#mode);
|
|
389
|
+
this.logger.info("Current mode:", this.#mode);
|
|
386
390
|
return this.#mode;
|
|
387
391
|
}
|
|
388
392
|
|
|
@@ -424,6 +428,8 @@ export class BVTRecorder {
|
|
|
424
428
|
this.sendEvent(this.events.onBrowserClose);
|
|
425
429
|
}
|
|
426
430
|
} catch (error) {
|
|
431
|
+
this.logger.error("Error in page close event");
|
|
432
|
+
this.logger.error(error);
|
|
427
433
|
console.error("Error in page close event");
|
|
428
434
|
console.error(error);
|
|
429
435
|
}
|
|
@@ -434,8 +440,10 @@ export class BVTRecorder {
|
|
|
434
440
|
if (frame !== page.mainFrame()) return;
|
|
435
441
|
this.handlePageTransition();
|
|
436
442
|
} catch (error) {
|
|
443
|
+
this.logger.error("Error in handlePageTransition event");
|
|
444
|
+
this.logger.error(error);
|
|
437
445
|
console.error("Error in handlePageTransition event");
|
|
438
|
-
|
|
446
|
+
console.error(error);
|
|
439
447
|
}
|
|
440
448
|
try {
|
|
441
449
|
if (frame !== this.#activeFrame) return;
|
|
@@ -454,6 +462,8 @@ export class BVTRecorder {
|
|
|
454
462
|
// await this._setRecordingMode(frame);
|
|
455
463
|
// await this._initPage(page);
|
|
456
464
|
} catch (error) {
|
|
465
|
+
this.logger.error("Error in frame navigate event");
|
|
466
|
+
this.logger.error(error);
|
|
457
467
|
console.error("Error in frame navigate event");
|
|
458
468
|
// console.error(error);
|
|
459
469
|
}
|
|
@@ -536,13 +546,9 @@ export class BVTRecorder {
|
|
|
536
546
|
|
|
537
547
|
try {
|
|
538
548
|
const result = await client.send("Page.getNavigationHistory");
|
|
539
|
-
// console.log("Navigation History:", result);
|
|
540
549
|
const entries = result.entries;
|
|
541
550
|
const currentIndex = result.currentIndex;
|
|
542
551
|
|
|
543
|
-
// ignore if currentIndex is not the last entry
|
|
544
|
-
// if (currentIndex !== entries.length - 1) return;
|
|
545
|
-
|
|
546
552
|
const currentEntry = entries[currentIndex];
|
|
547
553
|
const transitionInfo = this.analyzeTransitionType(entries, currentIndex, currentEntry);
|
|
548
554
|
this.previousIndex = currentIndex;
|
|
@@ -555,6 +561,8 @@ export class BVTRecorder {
|
|
|
555
561
|
navigationAction: transitionInfo.action,
|
|
556
562
|
};
|
|
557
563
|
} catch (error) {
|
|
564
|
+
this.logger.error("Error in getCurrentTransition event");
|
|
565
|
+
this.logger.error(error);
|
|
558
566
|
console.error("Error in getTransistionType event", error);
|
|
559
567
|
} finally {
|
|
560
568
|
await client.detach();
|
|
@@ -623,6 +631,8 @@ export class BVTRecorder {
|
|
|
623
631
|
// add listener for frame navigation on new tab
|
|
624
632
|
this._addFrameNavigateListener(page);
|
|
625
633
|
} catch (error) {
|
|
634
|
+
this.logger.error("Error in page event");
|
|
635
|
+
this.logger.error(error);
|
|
626
636
|
console.error("Error in page event");
|
|
627
637
|
console.error(error);
|
|
628
638
|
}
|
|
@@ -664,6 +674,7 @@ export class BVTRecorder {
|
|
|
664
674
|
const { data } = await client.send("Page.captureScreenshot", { format: "png" });
|
|
665
675
|
return data;
|
|
666
676
|
} catch (error) {
|
|
677
|
+
this.logger.error("Error in taking browser screenshot");
|
|
667
678
|
console.error("Error in taking browser screenshot", error);
|
|
668
679
|
} finally {
|
|
669
680
|
await client.detach();
|
|
@@ -809,6 +820,15 @@ export class BVTRecorder {
|
|
|
809
820
|
async abortExecution() {
|
|
810
821
|
await this.stepRunner.abortExecution();
|
|
811
822
|
}
|
|
823
|
+
|
|
824
|
+
async pauseExecution({ cmdId }) {
|
|
825
|
+
await this.stepRunner.pauseExecution(cmdId);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
async resumeExecution({ cmdId }) {
|
|
829
|
+
await this.stepRunner.resumeExecution(cmdId);
|
|
830
|
+
}
|
|
831
|
+
|
|
812
832
|
async dealyedRevertMode() {
|
|
813
833
|
const timerId = setTimeout(async () => {
|
|
814
834
|
await this.revertMode();
|
|
@@ -816,16 +836,21 @@ export class BVTRecorder {
|
|
|
816
836
|
this.timerId = timerId;
|
|
817
837
|
}
|
|
818
838
|
async runStep({ step, parametersMap, tags, isFirstStep, listenNetwork }, options) {
|
|
839
|
+
const { skipAfter = true, skipBefore = !isFirstStep } = options || {};
|
|
819
840
|
const _env = {
|
|
820
841
|
TOKEN: this.TOKEN,
|
|
821
842
|
TEMP_RUN: true,
|
|
822
843
|
REPORT_FOLDER: this.bvtContext.reportFolder,
|
|
823
844
|
BLINQ_ENV: this.envName,
|
|
824
|
-
STORE_DETAILED_NETWORK_DATA: listenNetwork ? "true" : "false",
|
|
825
|
-
CURRENT_STEP_ID: step.id,
|
|
826
845
|
};
|
|
827
846
|
|
|
828
847
|
this.bvtContext.navigate = true;
|
|
848
|
+
this.bvtContext.loadedRoutes = null;
|
|
849
|
+
if (listenNetwork) {
|
|
850
|
+
this.bvtContext.STORE_DETAILED_NETWORK_DATA = true;
|
|
851
|
+
} else {
|
|
852
|
+
this.bvtContext.STORE_DETAILED_NETWORK_DATA = false;
|
|
853
|
+
}
|
|
829
854
|
for (const [key, value] of Object.entries(_env)) {
|
|
830
855
|
process.env[key] = value;
|
|
831
856
|
}
|
|
@@ -843,9 +868,13 @@ export class BVTRecorder {
|
|
|
843
868
|
parametersMap,
|
|
844
869
|
envPath: this.envName,
|
|
845
870
|
tags,
|
|
871
|
+
config: this.config,
|
|
846
872
|
},
|
|
847
873
|
this.bvtContext,
|
|
848
|
-
|
|
874
|
+
{
|
|
875
|
+
skipAfter,
|
|
876
|
+
skipBefore,
|
|
877
|
+
}
|
|
849
878
|
);
|
|
850
879
|
await this.revertMode();
|
|
851
880
|
return { info };
|
|
@@ -886,6 +915,7 @@ export class BVTRecorder {
|
|
|
886
915
|
step.commands = [];
|
|
887
916
|
}
|
|
888
917
|
}
|
|
918
|
+
return steps;
|
|
889
919
|
// return getStepsAndCommandsForScenario({
|
|
890
920
|
// name,
|
|
891
921
|
// featureName,
|
|
@@ -893,6 +923,7 @@ export class BVTRecorder {
|
|
|
893
923
|
// map: this.scenariosStepsMap,
|
|
894
924
|
// });
|
|
895
925
|
}
|
|
926
|
+
|
|
896
927
|
async generateStepName({ commands, stepsNames, parameters, map }) {
|
|
897
928
|
return await this.namesService.generateStepName({ commands, stepsNames, parameters, map });
|
|
898
929
|
}
|
|
@@ -944,10 +975,11 @@ export class BVTRecorder {
|
|
|
944
975
|
if (existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
|
|
945
976
|
try {
|
|
946
977
|
const testData = JSON.parse(readFileSync(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
|
|
947
|
-
this.logger.info("Test data", testData);
|
|
978
|
+
// this.logger.info("Test data", testData);
|
|
948
979
|
this.sendEvent(this.events.getTestData, testData);
|
|
949
980
|
} catch (e) {
|
|
950
|
-
this.logger.error("Error reading test data file", e);
|
|
981
|
+
// this.logger.error("Error reading test data file", e);
|
|
982
|
+
console.log("Error reading test data file", e);
|
|
951
983
|
}
|
|
952
984
|
}
|
|
953
985
|
|
|
@@ -956,10 +988,12 @@ export class BVTRecorder {
|
|
|
956
988
|
this.watcher.on("all", async (event, path) => {
|
|
957
989
|
try {
|
|
958
990
|
const testData = JSON.parse(await readFile(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
|
|
959
|
-
this.logger.info("Test data", testData);
|
|
991
|
+
// this.logger.info("Test data", testData);
|
|
992
|
+
console.log("Test data changed", testData);
|
|
960
993
|
this.sendEvent(this.events.getTestData, testData);
|
|
961
994
|
} catch (e) {
|
|
962
|
-
this.logger.error("Error reading test data file", e);
|
|
995
|
+
// this.logger.error("Error reading test data file", e);
|
|
996
|
+
console.log("Error reading test data file", e);
|
|
963
997
|
}
|
|
964
998
|
});
|
|
965
999
|
}
|
|
@@ -992,7 +1026,7 @@ export class BVTRecorder {
|
|
|
992
1026
|
.filter((file) => file.endsWith(".feature"))
|
|
993
1027
|
.map((file) => path.join(this.projectDir, "features", file));
|
|
994
1028
|
try {
|
|
995
|
-
const parsedFiles = featureFiles.map((file) => parseFeatureFile(file));
|
|
1029
|
+
const parsedFiles = featureFiles.map((file) => this.parseFeatureFile(file));
|
|
996
1030
|
const output = {};
|
|
997
1031
|
parsedFiles.forEach((file) => {
|
|
998
1032
|
if (!file.feature) return;
|
|
@@ -1016,10 +1050,11 @@ export class BVTRecorder {
|
|
|
1016
1050
|
const stepParams = parseStepTextParameters(stepName);
|
|
1017
1051
|
return getCommandsForImplementedStep(stepName, step_definitions, stepParams).commands;
|
|
1018
1052
|
}
|
|
1053
|
+
|
|
1019
1054
|
loadExistingScenario({ featureName, scenarioName }) {
|
|
1020
1055
|
const step_definitions = loadStepDefinitions(this.projectDir);
|
|
1021
1056
|
const featureFilePath = path.join(this.projectDir, "features", featureName);
|
|
1022
|
-
const gherkinDoc = parseFeatureFile(featureFilePath);
|
|
1057
|
+
const gherkinDoc = this.parseFeatureFile(featureFilePath);
|
|
1023
1058
|
const scenario = gherkinDoc.feature.children.find((child) => child.scenario.name === scenarioName)?.scenario;
|
|
1024
1059
|
|
|
1025
1060
|
const steps = [];
|
|
@@ -1044,6 +1079,7 @@ export class BVTRecorder {
|
|
|
1044
1079
|
..._s,
|
|
1045
1080
|
keyword: step.keyword.trim(),
|
|
1046
1081
|
};
|
|
1082
|
+
parseRouteFiles(this.projectDir, _step);
|
|
1047
1083
|
steps.push(_step);
|
|
1048
1084
|
}
|
|
1049
1085
|
return {
|
|
@@ -1136,20 +1172,95 @@ export class BVTRecorder {
|
|
|
1136
1172
|
return false;
|
|
1137
1173
|
}
|
|
1138
1174
|
}
|
|
1139
|
-
}
|
|
1175
|
+
async initExecution({ tags = [] }) {
|
|
1176
|
+
// run before hooks
|
|
1177
|
+
const noopStep = {
|
|
1178
|
+
text: "Noop",
|
|
1179
|
+
isImplemented: true,
|
|
1180
|
+
};
|
|
1181
|
+
await this.runStep(
|
|
1182
|
+
{
|
|
1183
|
+
step: noopStep,
|
|
1184
|
+
parametersMap: {},
|
|
1185
|
+
tags,
|
|
1186
|
+
},
|
|
1187
|
+
{
|
|
1188
|
+
skipBefore: false,
|
|
1189
|
+
skipAfter: true,
|
|
1190
|
+
}
|
|
1191
|
+
);
|
|
1192
|
+
}
|
|
1193
|
+
async cleanupExecution({ tags = [] }) {
|
|
1194
|
+
// run after hooks
|
|
1195
|
+
const noopStep = {
|
|
1196
|
+
text: "Noop",
|
|
1197
|
+
isImplemented: true,
|
|
1198
|
+
};
|
|
1199
|
+
await this.runStep(
|
|
1200
|
+
{
|
|
1201
|
+
step: noopStep,
|
|
1202
|
+
parametersMap: {},
|
|
1203
|
+
tags,
|
|
1204
|
+
},
|
|
1205
|
+
{
|
|
1206
|
+
skipBefore: true,
|
|
1207
|
+
skipAfter: false,
|
|
1208
|
+
}
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
async resetExecution({ tags = [] }) {
|
|
1212
|
+
// run after hooks followed by before hooks
|
|
1213
|
+
await this.cleanupExecution({ tags });
|
|
1214
|
+
await this.initExecution({ tags });
|
|
1215
|
+
}
|
|
1140
1216
|
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1217
|
+
parseFeatureFile(featureFilePath) {
|
|
1218
|
+
try {
|
|
1219
|
+
let id = 0;
|
|
1220
|
+
const uuidFn = () => (++id).toString(16);
|
|
1221
|
+
const builder = new AstBuilder(uuidFn);
|
|
1222
|
+
const matcher = new GherkinClassicTokenMatcher();
|
|
1223
|
+
const parser = new Parser(builder, matcher);
|
|
1224
|
+
const source = readFileSync(featureFilePath, "utf8");
|
|
1225
|
+
const gherkinDocument = parser.parse(source);
|
|
1226
|
+
return gherkinDocument;
|
|
1227
|
+
} catch (e) {
|
|
1228
|
+
this.logger.error(`Error parsing feature file: ${featureFilePath}`);
|
|
1229
|
+
console.log(e);
|
|
1230
|
+
}
|
|
1231
|
+
return {};
|
|
1153
1232
|
}
|
|
1154
|
-
|
|
1155
|
-
|
|
1233
|
+
|
|
1234
|
+
stopRecordingNetwork(input) {
|
|
1235
|
+
if (this.bvtContext) {
|
|
1236
|
+
this.bvtContext.STORE_DETAILED_NETWORK_DATA = false;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
async fakeParams(params) {
|
|
1241
|
+
const newFakeParams = {};
|
|
1242
|
+
Object.keys(params).forEach((key) => {
|
|
1243
|
+
if (!params[key].startsWith("{{") || !params[key].endsWith("}}")) {
|
|
1244
|
+
newFakeParams[key] = params[key];
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
try {
|
|
1249
|
+
const value = params[key].substring(2, params[key].length - 2).trim();
|
|
1250
|
+
const faking = value.split("(")[0].split(".");
|
|
1251
|
+
let argument = value.substring(value.indexOf("(") + 1, value.lastIndexOf(")"));
|
|
1252
|
+
argument = isNaN(Number(argument)) ? argument : Number(argument);
|
|
1253
|
+
let fakeFunc = faker;
|
|
1254
|
+
faking.forEach((f) => {
|
|
1255
|
+
fakeFunc = fakeFunc[f];
|
|
1256
|
+
});
|
|
1257
|
+
const newValue = fakeFunc(argument);
|
|
1258
|
+
newFakeParams[key] = newValue;
|
|
1259
|
+
} catch (error) {
|
|
1260
|
+
newFakeParams[key] = params[key];
|
|
1261
|
+
}
|
|
1262
|
+
});
|
|
1263
|
+
|
|
1264
|
+
return newFakeParams;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { AstBuilder, GherkinClassicTokenMatcher, Parser } from "@cucumber/gherkin";
|
|
2
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from "fs";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import url from "url";
|
|
5
5
|
import { findFilesWithExtension, StepsDefinitions } from "../cucumber/steps_definitions.js";
|
|
6
|
-
import { Feature } from "../cucumber/feature.js";
|
|
6
|
+
import { Feature, Step } from "../cucumber/feature.js";
|
|
7
7
|
import { CodePage } from "../code_gen/page_reflection.js";
|
|
8
8
|
import { getCommandsForImplementedStep, loadStepDefinitions } from "./step_utils.js";
|
|
9
9
|
import { parseStepTextParameters } from "../cucumber/utils.js";
|
|
@@ -13,7 +13,6 @@ const uuidFn = () => (++id).toString(16);
|
|
|
13
13
|
const builder = new AstBuilder(uuidFn);
|
|
14
14
|
const matcher = new GherkinClassicTokenMatcher();
|
|
15
15
|
const parser = new Parser(builder, matcher);
|
|
16
|
-
|
|
17
16
|
let i = 0;
|
|
18
17
|
const getImplId = () => {
|
|
19
18
|
return `I-${i++}`;
|
|
@@ -57,6 +56,53 @@ function memorySizeOf(obj) {
|
|
|
57
56
|
return sizeOf(obj);
|
|
58
57
|
}
|
|
59
58
|
|
|
59
|
+
export function parseRouteFiles(projectDir, step) {
|
|
60
|
+
const routeFolder = path.join(projectDir, "data", "routes");
|
|
61
|
+
const templateRouteMap = new Map();
|
|
62
|
+
|
|
63
|
+
if (!existsSync(routeFolder)) {
|
|
64
|
+
step.routeItems = null;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Go over all the files in the route folder and parse them
|
|
69
|
+
const routeFiles = readdirSync(routeFolder).filter((file) => file.endsWith(".json"));
|
|
70
|
+
for (const file of routeFiles) {
|
|
71
|
+
const filePath = path.join(routeFolder, file);
|
|
72
|
+
const routeData = JSON.parse(readFileSync(filePath, "utf8"));
|
|
73
|
+
if (routeData && routeData.template) {
|
|
74
|
+
const template = routeData.template;
|
|
75
|
+
const routes = routeData.routes;
|
|
76
|
+
|
|
77
|
+
templateRouteMap.set(template, routes);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!existsSync(routeFolder)) {
|
|
82
|
+
return null;
|
|
83
|
+
} else if (step && step.text) {
|
|
84
|
+
// Convert the step text to cucumber template
|
|
85
|
+
const cucumberStep = new Step();
|
|
86
|
+
cucumberStep.text = step.text;
|
|
87
|
+
const template = cucumberStep.getTemplate();
|
|
88
|
+
if (templateRouteMap.has(template)) {
|
|
89
|
+
const routeItems = templateRouteMap.get(template);
|
|
90
|
+
routeItems.forEach((item) => {
|
|
91
|
+
const filters = item.filters || {};
|
|
92
|
+
const queryParams = filters?.queryParams || {};
|
|
93
|
+
const queryParamsArray = Object.keys(queryParams).map((key) => ({
|
|
94
|
+
key: key,
|
|
95
|
+
value: queryParams[key],
|
|
96
|
+
}));
|
|
97
|
+
filters.queryParams = queryParamsArray || [];
|
|
98
|
+
});
|
|
99
|
+
step.routeItems = routeItems;
|
|
100
|
+
} else {
|
|
101
|
+
step.routeItems = null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
60
106
|
export const getImplementedSteps = async (projectDir) => {
|
|
61
107
|
const foundErrors = [];
|
|
62
108
|
try {
|
|
@@ -70,6 +116,12 @@ export const getImplementedSteps = async (projectDir) => {
|
|
|
70
116
|
const utilsContent = readFileSync(utilsTemplateFilePath, "utf8");
|
|
71
117
|
//console.log({ utilsContent });
|
|
72
118
|
writeFileSync(utilsFilePath, utilsContent, "utf8");
|
|
119
|
+
const hooksTemplateFilePath = path.join(__dirname, "../../assets", "templates", "_hooks_template.txt");
|
|
120
|
+
if (existsSync(hooksTemplateFilePath)) {
|
|
121
|
+
const hooksFilePath = path.join(stepDefinitionFolderPath, "_hooks.mjs");
|
|
122
|
+
const hooksContent = readFileSync(hooksTemplateFilePath, "utf8");
|
|
123
|
+
writeFileSync(hooksFilePath, hooksContent, "utf8");
|
|
124
|
+
}
|
|
73
125
|
} catch (error) {
|
|
74
126
|
foundErrors.push({ error });
|
|
75
127
|
}
|
|
@@ -168,7 +220,10 @@ export const getImplementedSteps = async (projectDir) => {
|
|
|
168
220
|
}
|
|
169
221
|
stepLineSet.add(stepLine);
|
|
170
222
|
step.templateIndex = implementedSteps.length;
|
|
171
|
-
|
|
223
|
+
|
|
224
|
+
parseRouteFiles(projectDir, step);
|
|
225
|
+
|
|
226
|
+
const implementedStep = {
|
|
172
227
|
keyword: step.keyword.trim(),
|
|
173
228
|
keywordAlias: step.keywordAlias?.trim(),
|
|
174
229
|
text: updateStepText(template.pattern, step.parameters),
|
|
@@ -179,7 +234,10 @@ export const getImplementedSteps = async (projectDir) => {
|
|
|
179
234
|
templateIndex: step.templateIndex,
|
|
180
235
|
pattern: template.pattern,
|
|
181
236
|
paths: template.paths,
|
|
182
|
-
|
|
237
|
+
routeItems: step.routeItems,
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
implementedSteps.push(implementedStep);
|
|
183
241
|
}
|
|
184
242
|
}
|
|
185
243
|
|
|
@@ -236,17 +294,17 @@ export const getImplementedSteps = async (projectDir) => {
|
|
|
236
294
|
for (const tag of scenario.tags) {
|
|
237
295
|
delete tag.location;
|
|
238
296
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
297
|
+
for (const scenario of scenarios) {
|
|
298
|
+
for (const step of scenario.steps) {
|
|
299
|
+
if (step.templateIndex === undefined) {
|
|
300
|
+
const cleanStepName = stepsDefinitions._stepNameToTemplate(step.text);
|
|
301
|
+
const index = implementedSteps.findIndex((istep) => {
|
|
302
|
+
return cleanStepName === istep.pattern;
|
|
303
|
+
});
|
|
304
|
+
step.templateIndex = index;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
250
308
|
}
|
|
251
309
|
if (foundErrors.length > 0) {
|
|
252
310
|
console.log("foundErrors", foundErrors);
|