@dev-blinq/cucumber_client 1.0.1294-dev → 1.0.1294-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 +106 -106
- 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 +4 -16
- package/bin/assets/scripts/snapshot_capturer.js +153 -146
- package/bin/assets/scripts/unique_locators.js +924 -793
- package/bin/assets/scripts/yaml.js +796 -783
- package/bin/assets/templates/_hooks_template.txt +37 -0
- package/bin/assets/templates/utils_template.txt +1 -46
- 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 +43 -12
- 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 -84
- 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 +174 -64
- package/bin/client/recorderv3/implemented_steps.js +74 -16
- package/bin/client/recorderv3/index.js +69 -25
- package/bin/client/recorderv3/network.js +299 -0
- package/bin/client/recorderv3/scriptTest.js +1 -1
- package/bin/client/recorderv3/services.js +4 -16
- package/bin/client/recorderv3/step_runner.js +329 -72
- package/bin/client/recorderv3/step_utils.js +570 -5
- 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
|
@@ -3,7 +3,7 @@ import { closeContext, initContext, _getDataFile, resetTestData } from "automati
|
|
|
3
3
|
import { existsSync, readdirSync, readFileSync, rmSync } from "fs";
|
|
4
4
|
import path from "path";
|
|
5
5
|
import url from "url";
|
|
6
|
-
import { getImplementedSteps,
|
|
6
|
+
import { getImplementedSteps, parseRouteFiles } from "./implemented_steps.js";
|
|
7
7
|
import { NamesService } from "./services.js";
|
|
8
8
|
import { BVTStepRunner } from "./step_runner.js";
|
|
9
9
|
import { readFile, writeFile } from "fs/promises";
|
|
@@ -12,10 +12,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
|
-
|
|
17
|
+
import socketLogger from "../utils/socket_logger.js";
|
|
18
|
+
import { tmpdir } from "os";
|
|
19
19
|
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
20
20
|
|
|
21
21
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -44,15 +44,15 @@ async function evaluate(frame, script) {
|
|
|
44
44
|
async function findNestedFrameSelector(frame, obj) {
|
|
45
45
|
try {
|
|
46
46
|
const parent = frame.parentFrame();
|
|
47
|
-
if (parent) console.log(`Parent frame: ${JSON.stringify(parent)}`);
|
|
48
47
|
if (!parent) return { children: obj };
|
|
49
48
|
const frameElement = await frame.frameElement();
|
|
50
49
|
if (!frameElement) return;
|
|
51
50
|
const selectors = await parent.evaluate((element) => {
|
|
52
|
-
return window.
|
|
51
|
+
return window.__bvt_Recorder.locatorGenerator.getElementLocators(element, { excludeText: true }).locators;
|
|
53
52
|
}, frameElement);
|
|
54
53
|
return findNestedFrameSelector(parent, { children: obj, selectors });
|
|
55
54
|
} catch (e) {
|
|
55
|
+
socketLogger.error(`Error in findNestedFrameSelector: ${e}`);
|
|
56
56
|
console.error(e);
|
|
57
57
|
}
|
|
58
58
|
}
|
|
@@ -149,6 +149,7 @@ const transformAction = (action, el, isVerify, isPopupCloseClick, isInHoverMode,
|
|
|
149
149
|
};
|
|
150
150
|
}
|
|
151
151
|
default: {
|
|
152
|
+
socketLogger.error(`Action not supported: ${action.name}`);
|
|
152
153
|
console.log("action not supported", action);
|
|
153
154
|
throw new Error("action not supported");
|
|
154
155
|
}
|
|
@@ -160,6 +161,7 @@ const transformAction = (action, el, isVerify, isPopupCloseClick, isInHoverMode,
|
|
|
160
161
|
* @property {string} projectDir
|
|
161
162
|
* @property {string} TOKEN
|
|
162
163
|
* @property {(name:string, data:any)=> void} sendEvent
|
|
164
|
+
* @property {Object} logger
|
|
163
165
|
*/
|
|
164
166
|
export class BVTRecorder {
|
|
165
167
|
#currentURL = "";
|
|
@@ -173,7 +175,6 @@ export class BVTRecorder {
|
|
|
173
175
|
*/
|
|
174
176
|
constructor(initialState) {
|
|
175
177
|
Object.assign(this, initialState);
|
|
176
|
-
this.logger = logger;
|
|
177
178
|
this.screenshotMap = new Map();
|
|
178
179
|
this.snapshotMap = new Map();
|
|
179
180
|
this.scenariosStepsMap = new Map();
|
|
@@ -183,16 +184,16 @@ export class BVTRecorder {
|
|
|
183
184
|
projectDir: this.projectDir,
|
|
184
185
|
logger: this.logger,
|
|
185
186
|
});
|
|
186
|
-
this.stepRunner = new BVTStepRunner({
|
|
187
|
-
projectDir: this.projectDir,
|
|
188
|
-
});
|
|
189
187
|
this.pageSet = new Set();
|
|
190
|
-
|
|
188
|
+
this.pageMetaDataSet = new Set();
|
|
191
189
|
this.lastKnownUrlPath = "";
|
|
192
|
-
|
|
193
|
-
this.world = { attach: () => { } };
|
|
190
|
+
this.world = { attach: () => {} };
|
|
194
191
|
this.shouldTakeScreenshot = true;
|
|
195
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
|
+
}
|
|
196
197
|
}
|
|
197
198
|
events = {
|
|
198
199
|
onFrameNavigate: "BVTRecorder.onFrameNavigate",
|
|
@@ -203,12 +204,16 @@ export class BVTRecorder {
|
|
|
203
204
|
onStepDetails: "BVTRecorder.onStepDetails",
|
|
204
205
|
getTestData: "BVTRecorder.getTestData",
|
|
205
206
|
onGoto: "BVTRecorder.onGoto",
|
|
207
|
+
cmdExecutionStart: "BVTRecorder.cmdExecutionStart",
|
|
208
|
+
cmdExecutionSuccess: "BVTRecorder.cmdExecutionSuccess",
|
|
209
|
+
cmdExecutionError: "BVTRecorder.cmdExecutionError",
|
|
210
|
+
interceptResults: "BVTRecorder.interceptResults",
|
|
206
211
|
};
|
|
207
212
|
bindings = {
|
|
208
213
|
__bvt_recordCommand: async ({ frame, page, context }, event) => {
|
|
209
214
|
this.#activeFrame = frame;
|
|
210
215
|
const nestFrmLoc = await findNestedFrameSelector(frame);
|
|
211
|
-
|
|
216
|
+
this.logger.info(`Time taken for action: ${event.statistics.time}`);
|
|
212
217
|
await this.onAction({ ...event, nestFrmLoc });
|
|
213
218
|
},
|
|
214
219
|
__bvt_getMode: async () => {
|
|
@@ -227,8 +232,7 @@ export class BVTRecorder {
|
|
|
227
232
|
await this.onClosePopup();
|
|
228
233
|
},
|
|
229
234
|
__bvt_log: async (src, message) => {
|
|
230
|
-
|
|
231
|
-
console.log(`Inside Browser: ${message}`);
|
|
235
|
+
this.logger.info(`Inside Browser: ${message}`);
|
|
232
236
|
},
|
|
233
237
|
__bvt_getObject: (_src, obj) => {
|
|
234
238
|
this.processObject(obj);
|
|
@@ -290,11 +294,12 @@ export class BVTRecorder {
|
|
|
290
294
|
}
|
|
291
295
|
|
|
292
296
|
async _initBrowser({ url }) {
|
|
297
|
+
socketLogger.info("Only present in 1.0.1293-stage");
|
|
293
298
|
this.#remoteDebuggerPort = await findAvailablePort();
|
|
294
299
|
process.env.CDP_LISTEN_PORT = this.#remoteDebuggerPort;
|
|
295
300
|
|
|
296
|
-
this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
|
|
297
|
-
this.world = { attach: () => {
|
|
301
|
+
// this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
|
|
302
|
+
this.world = { attach: () => {} };
|
|
298
303
|
|
|
299
304
|
const ai_config_file = path.join(this.projectDir, "ai_config.json");
|
|
300
305
|
let ai_config = {};
|
|
@@ -302,9 +307,10 @@ export class BVTRecorder {
|
|
|
302
307
|
try {
|
|
303
308
|
ai_config = JSON.parse(readFileSync(ai_config_file, "utf8"));
|
|
304
309
|
} catch (error) {
|
|
305
|
-
|
|
310
|
+
this.logger.error("Error reading ai_config.json", error);
|
|
306
311
|
}
|
|
307
312
|
}
|
|
313
|
+
this.config = ai_config;
|
|
308
314
|
const initScripts = {
|
|
309
315
|
// recorderCjs: injectedScriptSource,
|
|
310
316
|
scripts: [
|
|
@@ -313,16 +319,37 @@ export class BVTRecorder {
|
|
|
313
319
|
],
|
|
314
320
|
};
|
|
315
321
|
|
|
316
|
-
let startTime = Date.now();
|
|
317
322
|
const bvtContext = await initContext(url, false, false, this.world, 450, initScripts, this.envName);
|
|
318
|
-
let stopTime = Date.now();
|
|
319
|
-
this.logger.info(`Browser launched in ${(stopTime - startTime) / 1000} s`);
|
|
320
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
|
+
});
|
|
321
348
|
const context = bvtContext.playContext;
|
|
322
349
|
this.context = context;
|
|
323
350
|
this.web = bvtContext.stable || bvtContext.web;
|
|
351
|
+
this.web.tryAllStrategies = true;
|
|
324
352
|
this.page = bvtContext.page;
|
|
325
|
-
|
|
326
353
|
this.pageSet.add(this.page);
|
|
327
354
|
this.lastKnownUrlPath = this._updateUrlPath();
|
|
328
355
|
const browser = await this.context.browser();
|
|
@@ -350,13 +377,15 @@ export class BVTRecorder {
|
|
|
350
377
|
}
|
|
351
378
|
return;
|
|
352
379
|
} catch (error) {
|
|
353
|
-
console.error("Error evaluting in context:", error);
|
|
380
|
+
// console.error("Error evaluting in context:", error);
|
|
381
|
+
this.logger.error("Error evaluating in context:", error);
|
|
354
382
|
}
|
|
355
383
|
}
|
|
356
384
|
}
|
|
357
385
|
|
|
358
386
|
getMode() {
|
|
359
|
-
console.log("getMode", this.#mode);
|
|
387
|
+
// console.log("getMode", this.#mode);
|
|
388
|
+
this.logger.info("Current mode:", this.#mode);
|
|
360
389
|
return this.#mode;
|
|
361
390
|
}
|
|
362
391
|
|
|
@@ -398,6 +427,8 @@ export class BVTRecorder {
|
|
|
398
427
|
this.sendEvent(this.events.onBrowserClose);
|
|
399
428
|
}
|
|
400
429
|
} catch (error) {
|
|
430
|
+
this.logger.error("Error in page close event");
|
|
431
|
+
this.logger.error(error);
|
|
401
432
|
console.error("Error in page close event");
|
|
402
433
|
console.error(error);
|
|
403
434
|
}
|
|
@@ -408,8 +439,10 @@ export class BVTRecorder {
|
|
|
408
439
|
if (frame !== page.mainFrame()) return;
|
|
409
440
|
this.handlePageTransition();
|
|
410
441
|
} catch (error) {
|
|
442
|
+
this.logger.error("Error in handlePageTransition event");
|
|
443
|
+
this.logger.error(error);
|
|
411
444
|
console.error("Error in handlePageTransition event");
|
|
412
|
-
|
|
445
|
+
console.error(error);
|
|
413
446
|
}
|
|
414
447
|
try {
|
|
415
448
|
if (frame !== this.#activeFrame) return;
|
|
@@ -428,6 +461,8 @@ export class BVTRecorder {
|
|
|
428
461
|
// await this._setRecordingMode(frame);
|
|
429
462
|
// await this._initPage(page);
|
|
430
463
|
} catch (error) {
|
|
464
|
+
this.logger.error("Error in frame navigate event");
|
|
465
|
+
this.logger.error(error);
|
|
431
466
|
console.error("Error in frame navigate event");
|
|
432
467
|
// console.error(error);
|
|
433
468
|
}
|
|
@@ -510,13 +545,9 @@ export class BVTRecorder {
|
|
|
510
545
|
|
|
511
546
|
try {
|
|
512
547
|
const result = await client.send("Page.getNavigationHistory");
|
|
513
|
-
// console.log("Navigation History:", result);
|
|
514
548
|
const entries = result.entries;
|
|
515
549
|
const currentIndex = result.currentIndex;
|
|
516
550
|
|
|
517
|
-
// ignore if currentIndex is not the last entry
|
|
518
|
-
// if (currentIndex !== entries.length - 1) return;
|
|
519
|
-
|
|
520
551
|
const currentEntry = entries[currentIndex];
|
|
521
552
|
const transitionInfo = this.analyzeTransitionType(entries, currentIndex, currentEntry);
|
|
522
553
|
this.previousIndex = currentIndex;
|
|
@@ -529,6 +560,8 @@ export class BVTRecorder {
|
|
|
529
560
|
navigationAction: transitionInfo.action,
|
|
530
561
|
};
|
|
531
562
|
} catch (error) {
|
|
563
|
+
this.logger.error("Error in getCurrentTransition event");
|
|
564
|
+
this.logger.error(error);
|
|
532
565
|
console.error("Error in getTransistionType event", error);
|
|
533
566
|
} finally {
|
|
534
567
|
await client.detach();
|
|
@@ -597,6 +630,8 @@ export class BVTRecorder {
|
|
|
597
630
|
// add listener for frame navigation on new tab
|
|
598
631
|
this._addFrameNavigateListener(page);
|
|
599
632
|
} catch (error) {
|
|
633
|
+
this.logger.error("Error in page event");
|
|
634
|
+
this.logger.error(error);
|
|
600
635
|
console.error("Error in page event");
|
|
601
636
|
console.error(error);
|
|
602
637
|
}
|
|
@@ -638,6 +673,7 @@ export class BVTRecorder {
|
|
|
638
673
|
const { data } = await client.send("Page.captureScreenshot", { format: "png" });
|
|
639
674
|
return data;
|
|
640
675
|
} catch (error) {
|
|
676
|
+
this.logger.error("Error in taking browser screenshot");
|
|
641
677
|
console.error("Error in taking browser screenshot", error);
|
|
642
678
|
} finally {
|
|
643
679
|
await client.detach();
|
|
@@ -700,7 +736,7 @@ export class BVTRecorder {
|
|
|
700
736
|
}
|
|
701
737
|
async closeBrowser() {
|
|
702
738
|
delete process.env.TEMP_RUN;
|
|
703
|
-
await this.watcher.close().then(() => {
|
|
739
|
+
await this.watcher.close().then(() => {});
|
|
704
740
|
this.watcher = null;
|
|
705
741
|
this.previousIndex = null;
|
|
706
742
|
this.previousHistoryLength = null;
|
|
@@ -783,42 +819,65 @@ export class BVTRecorder {
|
|
|
783
819
|
async abortExecution() {
|
|
784
820
|
await this.stepRunner.abortExecution();
|
|
785
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
|
+
|
|
786
831
|
async dealyedRevertMode() {
|
|
787
832
|
const timerId = setTimeout(async () => {
|
|
788
833
|
await this.revertMode();
|
|
789
834
|
}, 100);
|
|
790
835
|
this.timerId = timerId;
|
|
791
836
|
}
|
|
792
|
-
async runStep({ step, parametersMap, tags }, options) {
|
|
837
|
+
async runStep({ step, parametersMap, tags, isFirstStep, listenNetwork }, options) {
|
|
838
|
+
const { skipAfter = true, skipBefore = !isFirstStep } = options || {};
|
|
793
839
|
const _env = {
|
|
794
840
|
TOKEN: this.TOKEN,
|
|
795
841
|
TEMP_RUN: true,
|
|
796
842
|
REPORT_FOLDER: this.bvtContext.reportFolder,
|
|
797
843
|
BLINQ_ENV: this.envName,
|
|
844
|
+
DEBUG: "blinq:route",
|
|
798
845
|
};
|
|
799
846
|
|
|
800
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
|
+
}
|
|
801
854
|
for (const [key, value] of Object.entries(_env)) {
|
|
802
855
|
process.env[key] = value;
|
|
803
856
|
}
|
|
857
|
+
|
|
804
858
|
if (this.timerId) {
|
|
805
859
|
clearTimeout(this.timerId);
|
|
806
860
|
this.timerId = null;
|
|
807
861
|
}
|
|
808
862
|
await this.setMode("running");
|
|
863
|
+
|
|
809
864
|
try {
|
|
810
865
|
const { result, info } = await this.stepRunner.runStep(
|
|
811
866
|
{
|
|
812
867
|
step,
|
|
813
868
|
parametersMap,
|
|
814
869
|
envPath: this.envName,
|
|
815
|
-
tags
|
|
870
|
+
tags,
|
|
871
|
+
config: this.config,
|
|
816
872
|
},
|
|
817
873
|
this.bvtContext,
|
|
818
|
-
|
|
874
|
+
{
|
|
875
|
+
skipAfter,
|
|
876
|
+
skipBefore,
|
|
877
|
+
}
|
|
819
878
|
);
|
|
820
879
|
await this.revertMode();
|
|
821
|
-
return {
|
|
880
|
+
return { info };
|
|
822
881
|
} catch (error) {
|
|
823
882
|
await this.revertMode();
|
|
824
883
|
throw error;
|
|
@@ -829,15 +888,10 @@ export class BVTRecorder {
|
|
|
829
888
|
this.bvtContext.navigate = false;
|
|
830
889
|
}
|
|
831
890
|
}
|
|
832
|
-
async runScenario({ steps, parametersMap, tags }) {
|
|
833
|
-
for (const step of steps) {
|
|
834
|
-
await this.runStep({ step, parametersMap, tags });
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
891
|
async saveScenario({ scenario, featureName, override, isSingleStep }) {
|
|
838
892
|
await updateStepDefinitions({ scenario, featureName, projectDir: this.projectDir }); // updates mjs files
|
|
839
893
|
if (!isSingleStep) await updateFeatureFile({ featureName, scenario, override, projectDir: this.projectDir }); // updates gherkin files
|
|
840
|
-
await this.cleanup();
|
|
894
|
+
await this.cleanup({ tags: scenario.tags });
|
|
841
895
|
}
|
|
842
896
|
async getImplementedSteps() {
|
|
843
897
|
const stepsAndScenarios = await getImplementedSteps(this.projectDir);
|
|
@@ -861,6 +915,7 @@ export class BVTRecorder {
|
|
|
861
915
|
step.commands = [];
|
|
862
916
|
}
|
|
863
917
|
}
|
|
918
|
+
return steps;
|
|
864
919
|
// return getStepsAndCommandsForScenario({
|
|
865
920
|
// name,
|
|
866
921
|
// featureName,
|
|
@@ -868,6 +923,7 @@ export class BVTRecorder {
|
|
|
868
923
|
// map: this.scenariosStepsMap,
|
|
869
924
|
// });
|
|
870
925
|
}
|
|
926
|
+
|
|
871
927
|
async generateStepName({ commands, stepsNames, parameters, map }) {
|
|
872
928
|
return await this.namesService.generateStepName({ commands, stepsNames, parameters, map });
|
|
873
929
|
}
|
|
@@ -919,10 +975,11 @@ export class BVTRecorder {
|
|
|
919
975
|
if (existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
|
|
920
976
|
try {
|
|
921
977
|
const testData = JSON.parse(readFileSync(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
|
|
922
|
-
this.logger.info("Test data", testData);
|
|
978
|
+
// this.logger.info("Test data", testData);
|
|
923
979
|
this.sendEvent(this.events.getTestData, testData);
|
|
924
980
|
} catch (e) {
|
|
925
|
-
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);
|
|
926
983
|
}
|
|
927
984
|
}
|
|
928
985
|
|
|
@@ -931,10 +988,12 @@ export class BVTRecorder {
|
|
|
931
988
|
this.watcher.on("all", async (event, path) => {
|
|
932
989
|
try {
|
|
933
990
|
const testData = JSON.parse(await readFile(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
|
|
934
|
-
this.logger.info("Test data", testData);
|
|
991
|
+
// this.logger.info("Test data", testData);
|
|
992
|
+
console.log("Test data changed", testData);
|
|
935
993
|
this.sendEvent(this.events.getTestData, testData);
|
|
936
994
|
} catch (e) {
|
|
937
|
-
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);
|
|
938
997
|
}
|
|
939
998
|
});
|
|
940
999
|
}
|
|
@@ -950,9 +1009,9 @@ export class BVTRecorder {
|
|
|
950
1009
|
}
|
|
951
1010
|
}
|
|
952
1011
|
|
|
953
|
-
async discardTestData() {
|
|
1012
|
+
async discardTestData({ tags }) {
|
|
954
1013
|
resetTestData(this.envName, this.world);
|
|
955
|
-
await this.cleanup();
|
|
1014
|
+
await this.cleanup({ tags });
|
|
956
1015
|
}
|
|
957
1016
|
async addToTestData(obj) {
|
|
958
1017
|
if (!existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
|
|
@@ -967,7 +1026,7 @@ export class BVTRecorder {
|
|
|
967
1026
|
.filter((file) => file.endsWith(".feature"))
|
|
968
1027
|
.map((file) => path.join(this.projectDir, "features", file));
|
|
969
1028
|
try {
|
|
970
|
-
const parsedFiles = featureFiles.map((file) => parseFeatureFile(file));
|
|
1029
|
+
const parsedFiles = featureFiles.map((file) => this.parseFeatureFile(file));
|
|
971
1030
|
const output = {};
|
|
972
1031
|
parsedFiles.forEach((file) => {
|
|
973
1032
|
if (!file.feature) return;
|
|
@@ -991,10 +1050,11 @@ export class BVTRecorder {
|
|
|
991
1050
|
const stepParams = parseStepTextParameters(stepName);
|
|
992
1051
|
return getCommandsForImplementedStep(stepName, step_definitions, stepParams).commands;
|
|
993
1052
|
}
|
|
1053
|
+
|
|
994
1054
|
loadExistingScenario({ featureName, scenarioName }) {
|
|
995
1055
|
const step_definitions = loadStepDefinitions(this.projectDir);
|
|
996
1056
|
const featureFilePath = path.join(this.projectDir, "features", featureName);
|
|
997
|
-
const gherkinDoc = parseFeatureFile(featureFilePath);
|
|
1057
|
+
const gherkinDoc = this.parseFeatureFile(featureFilePath);
|
|
998
1058
|
const scenario = gherkinDoc.feature.children.find((child) => child.scenario.name === scenarioName)?.scenario;
|
|
999
1059
|
|
|
1000
1060
|
const steps = [];
|
|
@@ -1019,6 +1079,7 @@ export class BVTRecorder {
|
|
|
1019
1079
|
..._s,
|
|
1020
1080
|
keyword: step.keyword.trim(),
|
|
1021
1081
|
};
|
|
1082
|
+
parseRouteFiles(this.projectDir, _step);
|
|
1022
1083
|
steps.push(_step);
|
|
1023
1084
|
}
|
|
1024
1085
|
return {
|
|
@@ -1058,7 +1119,7 @@ export class BVTRecorder {
|
|
|
1058
1119
|
}
|
|
1059
1120
|
return result;
|
|
1060
1121
|
}
|
|
1061
|
-
async cleanup() {
|
|
1122
|
+
async cleanup({ tags }) {
|
|
1062
1123
|
const noopStep = {
|
|
1063
1124
|
text: "Noop",
|
|
1064
1125
|
isImplemented: true,
|
|
@@ -1072,6 +1133,7 @@ export class BVTRecorder {
|
|
|
1072
1133
|
{
|
|
1073
1134
|
step: noopStep,
|
|
1074
1135
|
parametersMap: {},
|
|
1136
|
+
tags: tags || [],
|
|
1075
1137
|
},
|
|
1076
1138
|
{
|
|
1077
1139
|
skipAfter: false,
|
|
@@ -1110,20 +1172,68 @@ export class BVTRecorder {
|
|
|
1110
1172
|
return false;
|
|
1111
1173
|
}
|
|
1112
1174
|
}
|
|
1113
|
-
}
|
|
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
|
+
}
|
|
1114
1216
|
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
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 {};
|
|
1127
1232
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1233
|
+
|
|
1234
|
+
stopRecordingNetwork(input) {
|
|
1235
|
+
if (this.bvtContext) {
|
|
1236
|
+
this.bvtContext.STORE_DETAILED_NETWORK_DATA = false;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
@@ -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);
|