@dev-blinq/cucumber_client 1.0.1317-dev → 1.0.1317-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 +159 -76
- package/bin/client/recorderv3/implemented_steps.js +74 -16
- package/bin/client/recorderv3/index.js +65 -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 +20 -11
|
@@ -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,10 @@ 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";
|
|
20
19
|
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
21
20
|
|
|
22
21
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -45,15 +44,15 @@ async function evaluate(frame, script) {
|
|
|
45
44
|
async function findNestedFrameSelector(frame, obj) {
|
|
46
45
|
try {
|
|
47
46
|
const parent = frame.parentFrame();
|
|
48
|
-
if (parent) console.log(`Parent frame: ${JSON.stringify(parent)}`);
|
|
49
47
|
if (!parent) return { children: obj };
|
|
50
48
|
const frameElement = await frame.frameElement();
|
|
51
49
|
if (!frameElement) return;
|
|
52
50
|
const selectors = await parent.evaluate((element) => {
|
|
53
|
-
return window.
|
|
51
|
+
return window.__bvt_Recorder.locatorGenerator.getElementLocators(element, { excludeText: true }).locators;
|
|
54
52
|
}, frameElement);
|
|
55
53
|
return findNestedFrameSelector(parent, { children: obj, selectors });
|
|
56
54
|
} catch (e) {
|
|
55
|
+
socketLogger.error(`Error in findNestedFrameSelector: ${e}`);
|
|
57
56
|
console.error(e);
|
|
58
57
|
}
|
|
59
58
|
}
|
|
@@ -150,6 +149,7 @@ const transformAction = (action, el, isVerify, isPopupCloseClick, isInHoverMode,
|
|
|
150
149
|
};
|
|
151
150
|
}
|
|
152
151
|
default: {
|
|
152
|
+
socketLogger.error(`Action not supported: ${action.name}`);
|
|
153
153
|
console.log("action not supported", action);
|
|
154
154
|
throw new Error("action not supported");
|
|
155
155
|
}
|
|
@@ -161,6 +161,7 @@ const transformAction = (action, el, isVerify, isPopupCloseClick, isInHoverMode,
|
|
|
161
161
|
* @property {string} projectDir
|
|
162
162
|
* @property {string} TOKEN
|
|
163
163
|
* @property {(name:string, data:any)=> void} sendEvent
|
|
164
|
+
* @property {Object} logger
|
|
164
165
|
*/
|
|
165
166
|
export class BVTRecorder {
|
|
166
167
|
#currentURL = "";
|
|
@@ -174,7 +175,6 @@ export class BVTRecorder {
|
|
|
174
175
|
*/
|
|
175
176
|
constructor(initialState) {
|
|
176
177
|
Object.assign(this, initialState);
|
|
177
|
-
this.logger = logger;
|
|
178
178
|
this.screenshotMap = new Map();
|
|
179
179
|
this.snapshotMap = new Map();
|
|
180
180
|
this.scenariosStepsMap = new Map();
|
|
@@ -184,37 +184,16 @@ export class BVTRecorder {
|
|
|
184
184
|
projectDir: this.projectDir,
|
|
185
185
|
logger: this.logger,
|
|
186
186
|
});
|
|
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
187
|
this.pageSet = new Set();
|
|
212
|
-
this.
|
|
188
|
+
this.pageMetaDataSet = new Set();
|
|
213
189
|
this.lastKnownUrlPath = "";
|
|
214
|
-
// TODO: what is world?
|
|
215
190
|
this.world = { attach: () => {} };
|
|
216
191
|
this.shouldTakeScreenshot = true;
|
|
217
192
|
this.watcher = null;
|
|
193
|
+
this.networkEventsFolder = path.join(tmpdir(), "blinq_network_events");
|
|
194
|
+
if (existsSync(this.networkEventsFolder)) {
|
|
195
|
+
rmSync(this.networkEventsFolder, { recursive: true, force: true });
|
|
196
|
+
}
|
|
218
197
|
}
|
|
219
198
|
events = {
|
|
220
199
|
onFrameNavigate: "BVTRecorder.onFrameNavigate",
|
|
@@ -227,13 +206,14 @@ export class BVTRecorder {
|
|
|
227
206
|
onGoto: "BVTRecorder.onGoto",
|
|
228
207
|
cmdExecutionStart: "BVTRecorder.cmdExecutionStart",
|
|
229
208
|
cmdExecutionSuccess: "BVTRecorder.cmdExecutionSuccess",
|
|
230
|
-
|
|
209
|
+
cmdExecutionError: "BVTRecorder.cmdExecutionError",
|
|
210
|
+
interceptResults: "BVTRecorder.interceptResults",
|
|
231
211
|
};
|
|
232
212
|
bindings = {
|
|
233
213
|
__bvt_recordCommand: async ({ frame, page, context }, event) => {
|
|
234
214
|
this.#activeFrame = frame;
|
|
235
215
|
const nestFrmLoc = await findNestedFrameSelector(frame);
|
|
236
|
-
|
|
216
|
+
this.logger.info(`Time taken for action: ${event.statistics.time}`);
|
|
237
217
|
await this.onAction({ ...event, nestFrmLoc });
|
|
238
218
|
},
|
|
239
219
|
__bvt_getMode: async () => {
|
|
@@ -252,8 +232,7 @@ export class BVTRecorder {
|
|
|
252
232
|
await this.onClosePopup();
|
|
253
233
|
},
|
|
254
234
|
__bvt_log: async (src, message) => {
|
|
255
|
-
|
|
256
|
-
console.log(`Inside Browser: ${message}`);
|
|
235
|
+
this.logger.info(`Inside Browser: ${message}`);
|
|
257
236
|
},
|
|
258
237
|
__bvt_getObject: (_src, obj) => {
|
|
259
238
|
this.processObject(obj);
|
|
@@ -265,7 +244,6 @@ export class BVTRecorder {
|
|
|
265
244
|
const locator = await this.web.page.locator(selector);
|
|
266
245
|
const snapshot = await locator.ariaSnapshot();
|
|
267
246
|
return snapshot;
|
|
268
|
-
// Triggering workflow
|
|
269
247
|
};
|
|
270
248
|
|
|
271
249
|
processObject = async ({ type, action, value }) => {
|
|
@@ -316,10 +294,11 @@ export class BVTRecorder {
|
|
|
316
294
|
}
|
|
317
295
|
|
|
318
296
|
async _initBrowser({ url }) {
|
|
297
|
+
socketLogger.info("Only present in 1.0.1293-stage");
|
|
319
298
|
this.#remoteDebuggerPort = await findAvailablePort();
|
|
320
299
|
process.env.CDP_LISTEN_PORT = this.#remoteDebuggerPort;
|
|
321
300
|
|
|
322
|
-
this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
|
|
301
|
+
// this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
|
|
323
302
|
this.world = { attach: () => {} };
|
|
324
303
|
|
|
325
304
|
const ai_config_file = path.join(this.projectDir, "ai_config.json");
|
|
@@ -328,9 +307,10 @@ export class BVTRecorder {
|
|
|
328
307
|
try {
|
|
329
308
|
ai_config = JSON.parse(readFileSync(ai_config_file, "utf8"));
|
|
330
309
|
} catch (error) {
|
|
331
|
-
|
|
310
|
+
this.logger.error("Error reading ai_config.json", error);
|
|
332
311
|
}
|
|
333
312
|
}
|
|
313
|
+
this.config = ai_config;
|
|
334
314
|
const initScripts = {
|
|
335
315
|
// recorderCjs: injectedScriptSource,
|
|
336
316
|
scripts: [
|
|
@@ -339,16 +319,37 @@ export class BVTRecorder {
|
|
|
339
319
|
],
|
|
340
320
|
};
|
|
341
321
|
|
|
342
|
-
let startTime = Date.now();
|
|
343
322
|
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
323
|
this.bvtContext = bvtContext;
|
|
324
|
+
this.stepRunner = new BVTStepRunner({
|
|
325
|
+
projectDir: this.projectDir,
|
|
326
|
+
sendExecutionStatus: (data) => {
|
|
327
|
+
if (data && data.type) {
|
|
328
|
+
switch (data.type) {
|
|
329
|
+
case "cmdExecutionStart":
|
|
330
|
+
this.sendEvent(this.events.cmdExecutionStart, data);
|
|
331
|
+
break;
|
|
332
|
+
case "cmdExecutionSuccess":
|
|
333
|
+
this.sendEvent(this.events.cmdExecutionSuccess, data);
|
|
334
|
+
break;
|
|
335
|
+
case "cmdExecutionError":
|
|
336
|
+
this.sendEvent(this.events.cmdExecutionError, data);
|
|
337
|
+
break;
|
|
338
|
+
case "interceptResults":
|
|
339
|
+
this.sendEvent(this.events.interceptResults, data);
|
|
340
|
+
break;
|
|
341
|
+
default:
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
bvtContext: this.bvtContext,
|
|
347
|
+
});
|
|
347
348
|
const context = bvtContext.playContext;
|
|
348
349
|
this.context = context;
|
|
349
350
|
this.web = bvtContext.stable || bvtContext.web;
|
|
351
|
+
this.web.tryAllStrategies = true;
|
|
350
352
|
this.page = bvtContext.page;
|
|
351
|
-
|
|
352
353
|
this.pageSet.add(this.page);
|
|
353
354
|
this.lastKnownUrlPath = this._updateUrlPath();
|
|
354
355
|
const browser = await this.context.browser();
|
|
@@ -376,13 +377,15 @@ export class BVTRecorder {
|
|
|
376
377
|
}
|
|
377
378
|
return;
|
|
378
379
|
} catch (error) {
|
|
379
|
-
console.error("Error evaluting in context:", error);
|
|
380
|
+
// console.error("Error evaluting in context:", error);
|
|
381
|
+
this.logger.error("Error evaluating in context:", error);
|
|
380
382
|
}
|
|
381
383
|
}
|
|
382
384
|
}
|
|
383
385
|
|
|
384
386
|
getMode() {
|
|
385
|
-
console.log("getMode", this.#mode);
|
|
387
|
+
// console.log("getMode", this.#mode);
|
|
388
|
+
this.logger.info("Current mode:", this.#mode);
|
|
386
389
|
return this.#mode;
|
|
387
390
|
}
|
|
388
391
|
|
|
@@ -424,6 +427,8 @@ export class BVTRecorder {
|
|
|
424
427
|
this.sendEvent(this.events.onBrowserClose);
|
|
425
428
|
}
|
|
426
429
|
} catch (error) {
|
|
430
|
+
this.logger.error("Error in page close event");
|
|
431
|
+
this.logger.error(error);
|
|
427
432
|
console.error("Error in page close event");
|
|
428
433
|
console.error(error);
|
|
429
434
|
}
|
|
@@ -434,8 +439,10 @@ export class BVTRecorder {
|
|
|
434
439
|
if (frame !== page.mainFrame()) return;
|
|
435
440
|
this.handlePageTransition();
|
|
436
441
|
} catch (error) {
|
|
442
|
+
this.logger.error("Error in handlePageTransition event");
|
|
443
|
+
this.logger.error(error);
|
|
437
444
|
console.error("Error in handlePageTransition event");
|
|
438
|
-
|
|
445
|
+
console.error(error);
|
|
439
446
|
}
|
|
440
447
|
try {
|
|
441
448
|
if (frame !== this.#activeFrame) return;
|
|
@@ -454,6 +461,8 @@ export class BVTRecorder {
|
|
|
454
461
|
// await this._setRecordingMode(frame);
|
|
455
462
|
// await this._initPage(page);
|
|
456
463
|
} catch (error) {
|
|
464
|
+
this.logger.error("Error in frame navigate event");
|
|
465
|
+
this.logger.error(error);
|
|
457
466
|
console.error("Error in frame navigate event");
|
|
458
467
|
// console.error(error);
|
|
459
468
|
}
|
|
@@ -536,13 +545,9 @@ export class BVTRecorder {
|
|
|
536
545
|
|
|
537
546
|
try {
|
|
538
547
|
const result = await client.send("Page.getNavigationHistory");
|
|
539
|
-
// console.log("Navigation History:", result);
|
|
540
548
|
const entries = result.entries;
|
|
541
549
|
const currentIndex = result.currentIndex;
|
|
542
550
|
|
|
543
|
-
// ignore if currentIndex is not the last entry
|
|
544
|
-
// if (currentIndex !== entries.length - 1) return;
|
|
545
|
-
|
|
546
551
|
const currentEntry = entries[currentIndex];
|
|
547
552
|
const transitionInfo = this.analyzeTransitionType(entries, currentIndex, currentEntry);
|
|
548
553
|
this.previousIndex = currentIndex;
|
|
@@ -555,6 +560,8 @@ export class BVTRecorder {
|
|
|
555
560
|
navigationAction: transitionInfo.action,
|
|
556
561
|
};
|
|
557
562
|
} catch (error) {
|
|
563
|
+
this.logger.error("Error in getCurrentTransition event");
|
|
564
|
+
this.logger.error(error);
|
|
558
565
|
console.error("Error in getTransistionType event", error);
|
|
559
566
|
} finally {
|
|
560
567
|
await client.detach();
|
|
@@ -623,6 +630,8 @@ export class BVTRecorder {
|
|
|
623
630
|
// add listener for frame navigation on new tab
|
|
624
631
|
this._addFrameNavigateListener(page);
|
|
625
632
|
} catch (error) {
|
|
633
|
+
this.logger.error("Error in page event");
|
|
634
|
+
this.logger.error(error);
|
|
626
635
|
console.error("Error in page event");
|
|
627
636
|
console.error(error);
|
|
628
637
|
}
|
|
@@ -664,6 +673,7 @@ export class BVTRecorder {
|
|
|
664
673
|
const { data } = await client.send("Page.captureScreenshot", { format: "png" });
|
|
665
674
|
return data;
|
|
666
675
|
} catch (error) {
|
|
676
|
+
this.logger.error("Error in taking browser screenshot");
|
|
667
677
|
console.error("Error in taking browser screenshot", error);
|
|
668
678
|
} finally {
|
|
669
679
|
await client.detach();
|
|
@@ -809,6 +819,15 @@ export class BVTRecorder {
|
|
|
809
819
|
async abortExecution() {
|
|
810
820
|
await this.stepRunner.abortExecution();
|
|
811
821
|
}
|
|
822
|
+
|
|
823
|
+
async pauseExecution({ cmdId }) {
|
|
824
|
+
await this.stepRunner.pauseExecution(cmdId);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
async resumeExecution({ cmdId }) {
|
|
828
|
+
await this.stepRunner.resumeExecution(cmdId);
|
|
829
|
+
}
|
|
830
|
+
|
|
812
831
|
async dealyedRevertMode() {
|
|
813
832
|
const timerId = setTimeout(async () => {
|
|
814
833
|
await this.revertMode();
|
|
@@ -816,16 +835,21 @@ export class BVTRecorder {
|
|
|
816
835
|
this.timerId = timerId;
|
|
817
836
|
}
|
|
818
837
|
async runStep({ step, parametersMap, tags, isFirstStep, listenNetwork }, options) {
|
|
838
|
+
const { skipAfter = true, skipBefore = !isFirstStep } = options || {};
|
|
819
839
|
const _env = {
|
|
820
840
|
TOKEN: this.TOKEN,
|
|
821
841
|
TEMP_RUN: true,
|
|
822
842
|
REPORT_FOLDER: this.bvtContext.reportFolder,
|
|
823
843
|
BLINQ_ENV: this.envName,
|
|
824
|
-
STORE_DETAILED_NETWORK_DATA: listenNetwork ? "true" : "false",
|
|
825
|
-
CURRENT_STEP_ID: step.id,
|
|
826
844
|
};
|
|
827
845
|
|
|
828
846
|
this.bvtContext.navigate = true;
|
|
847
|
+
this.bvtContext.loadedRoutes = null;
|
|
848
|
+
if (listenNetwork) {
|
|
849
|
+
this.bvtContext.STORE_DETAILED_NETWORK_DATA = true;
|
|
850
|
+
} else {
|
|
851
|
+
this.bvtContext.STORE_DETAILED_NETWORK_DATA = false;
|
|
852
|
+
}
|
|
829
853
|
for (const [key, value] of Object.entries(_env)) {
|
|
830
854
|
process.env[key] = value;
|
|
831
855
|
}
|
|
@@ -843,9 +867,13 @@ export class BVTRecorder {
|
|
|
843
867
|
parametersMap,
|
|
844
868
|
envPath: this.envName,
|
|
845
869
|
tags,
|
|
870
|
+
config: this.config,
|
|
846
871
|
},
|
|
847
872
|
this.bvtContext,
|
|
848
|
-
|
|
873
|
+
{
|
|
874
|
+
skipAfter,
|
|
875
|
+
skipBefore,
|
|
876
|
+
}
|
|
849
877
|
);
|
|
850
878
|
await this.revertMode();
|
|
851
879
|
return { info };
|
|
@@ -886,6 +914,7 @@ export class BVTRecorder {
|
|
|
886
914
|
step.commands = [];
|
|
887
915
|
}
|
|
888
916
|
}
|
|
917
|
+
return steps;
|
|
889
918
|
// return getStepsAndCommandsForScenario({
|
|
890
919
|
// name,
|
|
891
920
|
// featureName,
|
|
@@ -893,6 +922,7 @@ export class BVTRecorder {
|
|
|
893
922
|
// map: this.scenariosStepsMap,
|
|
894
923
|
// });
|
|
895
924
|
}
|
|
925
|
+
|
|
896
926
|
async generateStepName({ commands, stepsNames, parameters, map }) {
|
|
897
927
|
return await this.namesService.generateStepName({ commands, stepsNames, parameters, map });
|
|
898
928
|
}
|
|
@@ -944,10 +974,11 @@ export class BVTRecorder {
|
|
|
944
974
|
if (existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
|
|
945
975
|
try {
|
|
946
976
|
const testData = JSON.parse(readFileSync(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
|
|
947
|
-
this.logger.info("Test data", testData);
|
|
977
|
+
// this.logger.info("Test data", testData);
|
|
948
978
|
this.sendEvent(this.events.getTestData, testData);
|
|
949
979
|
} catch (e) {
|
|
950
|
-
this.logger.error("Error reading test data file", e);
|
|
980
|
+
// this.logger.error("Error reading test data file", e);
|
|
981
|
+
console.log("Error reading test data file", e);
|
|
951
982
|
}
|
|
952
983
|
}
|
|
953
984
|
|
|
@@ -956,10 +987,12 @@ export class BVTRecorder {
|
|
|
956
987
|
this.watcher.on("all", async (event, path) => {
|
|
957
988
|
try {
|
|
958
989
|
const testData = JSON.parse(await readFile(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
|
|
959
|
-
this.logger.info("Test data", testData);
|
|
990
|
+
// this.logger.info("Test data", testData);
|
|
991
|
+
console.log("Test data changed", testData);
|
|
960
992
|
this.sendEvent(this.events.getTestData, testData);
|
|
961
993
|
} catch (e) {
|
|
962
|
-
this.logger.error("Error reading test data file", e);
|
|
994
|
+
// this.logger.error("Error reading test data file", e);
|
|
995
|
+
console.log("Error reading test data file", e);
|
|
963
996
|
}
|
|
964
997
|
});
|
|
965
998
|
}
|
|
@@ -992,7 +1025,7 @@ export class BVTRecorder {
|
|
|
992
1025
|
.filter((file) => file.endsWith(".feature"))
|
|
993
1026
|
.map((file) => path.join(this.projectDir, "features", file));
|
|
994
1027
|
try {
|
|
995
|
-
const parsedFiles = featureFiles.map((file) => parseFeatureFile(file));
|
|
1028
|
+
const parsedFiles = featureFiles.map((file) => this.parseFeatureFile(file));
|
|
996
1029
|
const output = {};
|
|
997
1030
|
parsedFiles.forEach((file) => {
|
|
998
1031
|
if (!file.feature) return;
|
|
@@ -1016,10 +1049,11 @@ export class BVTRecorder {
|
|
|
1016
1049
|
const stepParams = parseStepTextParameters(stepName);
|
|
1017
1050
|
return getCommandsForImplementedStep(stepName, step_definitions, stepParams).commands;
|
|
1018
1051
|
}
|
|
1052
|
+
|
|
1019
1053
|
loadExistingScenario({ featureName, scenarioName }) {
|
|
1020
1054
|
const step_definitions = loadStepDefinitions(this.projectDir);
|
|
1021
1055
|
const featureFilePath = path.join(this.projectDir, "features", featureName);
|
|
1022
|
-
const gherkinDoc = parseFeatureFile(featureFilePath);
|
|
1056
|
+
const gherkinDoc = this.parseFeatureFile(featureFilePath);
|
|
1023
1057
|
const scenario = gherkinDoc.feature.children.find((child) => child.scenario.name === scenarioName)?.scenario;
|
|
1024
1058
|
|
|
1025
1059
|
const steps = [];
|
|
@@ -1044,6 +1078,7 @@ export class BVTRecorder {
|
|
|
1044
1078
|
..._s,
|
|
1045
1079
|
keyword: step.keyword.trim(),
|
|
1046
1080
|
};
|
|
1081
|
+
parseRouteFiles(this.projectDir, _step);
|
|
1047
1082
|
steps.push(_step);
|
|
1048
1083
|
}
|
|
1049
1084
|
return {
|
|
@@ -1136,20 +1171,68 @@ export class BVTRecorder {
|
|
|
1136
1171
|
return false;
|
|
1137
1172
|
}
|
|
1138
1173
|
}
|
|
1139
|
-
}
|
|
1174
|
+
async initExecution({ tags = [] }) {
|
|
1175
|
+
// run before hooks
|
|
1176
|
+
const noopStep = {
|
|
1177
|
+
text: "Noop",
|
|
1178
|
+
isImplemented: true,
|
|
1179
|
+
};
|
|
1180
|
+
await this.runStep(
|
|
1181
|
+
{
|
|
1182
|
+
step: noopStep,
|
|
1183
|
+
parametersMap: {},
|
|
1184
|
+
tags,
|
|
1185
|
+
},
|
|
1186
|
+
{
|
|
1187
|
+
skipBefore: false,
|
|
1188
|
+
skipAfter: true,
|
|
1189
|
+
}
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1192
|
+
async cleanupExecution({ tags = [] }) {
|
|
1193
|
+
// run after hooks
|
|
1194
|
+
const noopStep = {
|
|
1195
|
+
text: "Noop",
|
|
1196
|
+
isImplemented: true,
|
|
1197
|
+
};
|
|
1198
|
+
await this.runStep(
|
|
1199
|
+
{
|
|
1200
|
+
step: noopStep,
|
|
1201
|
+
parametersMap: {},
|
|
1202
|
+
tags,
|
|
1203
|
+
},
|
|
1204
|
+
{
|
|
1205
|
+
skipBefore: true,
|
|
1206
|
+
skipAfter: false,
|
|
1207
|
+
}
|
|
1208
|
+
);
|
|
1209
|
+
}
|
|
1210
|
+
async resetExecution({ tags = [] }) {
|
|
1211
|
+
// run after hooks followed by before hooks
|
|
1212
|
+
await this.cleanupExecution({ tags });
|
|
1213
|
+
await this.initExecution({ tags });
|
|
1214
|
+
}
|
|
1140
1215
|
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1216
|
+
parseFeatureFile(featureFilePath) {
|
|
1217
|
+
try {
|
|
1218
|
+
let id = 0;
|
|
1219
|
+
const uuidFn = () => (++id).toString(16);
|
|
1220
|
+
const builder = new AstBuilder(uuidFn);
|
|
1221
|
+
const matcher = new GherkinClassicTokenMatcher();
|
|
1222
|
+
const parser = new Parser(builder, matcher);
|
|
1223
|
+
const source = readFileSync(featureFilePath, "utf8");
|
|
1224
|
+
const gherkinDocument = parser.parse(source);
|
|
1225
|
+
return gherkinDocument;
|
|
1226
|
+
} catch (e) {
|
|
1227
|
+
this.logger.error(`Error parsing feature file: ${featureFilePath}`);
|
|
1228
|
+
console.log(e);
|
|
1229
|
+
}
|
|
1230
|
+
return {};
|
|
1153
1231
|
}
|
|
1154
|
-
|
|
1155
|
-
|
|
1232
|
+
|
|
1233
|
+
stopRecordingNetwork(input) {
|
|
1234
|
+
if (this.bvtContext) {
|
|
1235
|
+
this.bvtContext.STORE_DETAILED_NETWORK_DATA = false;
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
@@ -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);
|