@dev-blinq/cucumber_client 1.0.1292-dev → 1.0.1292-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 -902
- 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 +173 -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 +577 -7
- 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);
|
|
@@ -293,8 +297,8 @@ export class BVTRecorder {
|
|
|
293
297
|
this.#remoteDebuggerPort = await findAvailablePort();
|
|
294
298
|
process.env.CDP_LISTEN_PORT = this.#remoteDebuggerPort;
|
|
295
299
|
|
|
296
|
-
this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
|
|
297
|
-
this.world = { attach: () => {
|
|
300
|
+
// this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
|
|
301
|
+
this.world = { attach: () => {} };
|
|
298
302
|
|
|
299
303
|
const ai_config_file = path.join(this.projectDir, "ai_config.json");
|
|
300
304
|
let ai_config = {};
|
|
@@ -302,9 +306,10 @@ export class BVTRecorder {
|
|
|
302
306
|
try {
|
|
303
307
|
ai_config = JSON.parse(readFileSync(ai_config_file, "utf8"));
|
|
304
308
|
} catch (error) {
|
|
305
|
-
|
|
309
|
+
this.logger.error("Error reading ai_config.json", error);
|
|
306
310
|
}
|
|
307
311
|
}
|
|
312
|
+
this.config = ai_config;
|
|
308
313
|
const initScripts = {
|
|
309
314
|
// recorderCjs: injectedScriptSource,
|
|
310
315
|
scripts: [
|
|
@@ -313,16 +318,37 @@ export class BVTRecorder {
|
|
|
313
318
|
],
|
|
314
319
|
};
|
|
315
320
|
|
|
316
|
-
let startTime = Date.now();
|
|
317
321
|
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
322
|
this.bvtContext = bvtContext;
|
|
323
|
+
this.stepRunner = new BVTStepRunner({
|
|
324
|
+
projectDir: this.projectDir,
|
|
325
|
+
sendExecutionStatus: (data) => {
|
|
326
|
+
if (data && data.type) {
|
|
327
|
+
switch (data.type) {
|
|
328
|
+
case "cmdExecutionStart":
|
|
329
|
+
this.sendEvent(this.events.cmdExecutionStart, data);
|
|
330
|
+
break;
|
|
331
|
+
case "cmdExecutionSuccess":
|
|
332
|
+
this.sendEvent(this.events.cmdExecutionSuccess, data);
|
|
333
|
+
break;
|
|
334
|
+
case "cmdExecutionError":
|
|
335
|
+
this.sendEvent(this.events.cmdExecutionError, data);
|
|
336
|
+
break;
|
|
337
|
+
case "interceptResults":
|
|
338
|
+
this.sendEvent(this.events.interceptResults, data);
|
|
339
|
+
break;
|
|
340
|
+
default:
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
bvtContext: this.bvtContext,
|
|
346
|
+
});
|
|
321
347
|
const context = bvtContext.playContext;
|
|
322
348
|
this.context = context;
|
|
323
349
|
this.web = bvtContext.stable || bvtContext.web;
|
|
350
|
+
this.web.tryAllStrategies = true;
|
|
324
351
|
this.page = bvtContext.page;
|
|
325
|
-
|
|
326
352
|
this.pageSet.add(this.page);
|
|
327
353
|
this.lastKnownUrlPath = this._updateUrlPath();
|
|
328
354
|
const browser = await this.context.browser();
|
|
@@ -350,13 +376,15 @@ export class BVTRecorder {
|
|
|
350
376
|
}
|
|
351
377
|
return;
|
|
352
378
|
} catch (error) {
|
|
353
|
-
console.error("Error evaluting in context:", error);
|
|
379
|
+
// console.error("Error evaluting in context:", error);
|
|
380
|
+
this.logger.error("Error evaluating in context:", error);
|
|
354
381
|
}
|
|
355
382
|
}
|
|
356
383
|
}
|
|
357
384
|
|
|
358
385
|
getMode() {
|
|
359
|
-
console.log("getMode", this.#mode);
|
|
386
|
+
// console.log("getMode", this.#mode);
|
|
387
|
+
this.logger.info("Current mode:", this.#mode);
|
|
360
388
|
return this.#mode;
|
|
361
389
|
}
|
|
362
390
|
|
|
@@ -398,6 +426,8 @@ export class BVTRecorder {
|
|
|
398
426
|
this.sendEvent(this.events.onBrowserClose);
|
|
399
427
|
}
|
|
400
428
|
} catch (error) {
|
|
429
|
+
this.logger.error("Error in page close event");
|
|
430
|
+
this.logger.error(error);
|
|
401
431
|
console.error("Error in page close event");
|
|
402
432
|
console.error(error);
|
|
403
433
|
}
|
|
@@ -408,8 +438,10 @@ export class BVTRecorder {
|
|
|
408
438
|
if (frame !== page.mainFrame()) return;
|
|
409
439
|
this.handlePageTransition();
|
|
410
440
|
} catch (error) {
|
|
441
|
+
this.logger.error("Error in handlePageTransition event");
|
|
442
|
+
this.logger.error(error);
|
|
411
443
|
console.error("Error in handlePageTransition event");
|
|
412
|
-
|
|
444
|
+
console.error(error);
|
|
413
445
|
}
|
|
414
446
|
try {
|
|
415
447
|
if (frame !== this.#activeFrame) return;
|
|
@@ -428,6 +460,8 @@ export class BVTRecorder {
|
|
|
428
460
|
// await this._setRecordingMode(frame);
|
|
429
461
|
// await this._initPage(page);
|
|
430
462
|
} catch (error) {
|
|
463
|
+
this.logger.error("Error in frame navigate event");
|
|
464
|
+
this.logger.error(error);
|
|
431
465
|
console.error("Error in frame navigate event");
|
|
432
466
|
// console.error(error);
|
|
433
467
|
}
|
|
@@ -510,13 +544,9 @@ export class BVTRecorder {
|
|
|
510
544
|
|
|
511
545
|
try {
|
|
512
546
|
const result = await client.send("Page.getNavigationHistory");
|
|
513
|
-
// console.log("Navigation History:", result);
|
|
514
547
|
const entries = result.entries;
|
|
515
548
|
const currentIndex = result.currentIndex;
|
|
516
549
|
|
|
517
|
-
// ignore if currentIndex is not the last entry
|
|
518
|
-
// if (currentIndex !== entries.length - 1) return;
|
|
519
|
-
|
|
520
550
|
const currentEntry = entries[currentIndex];
|
|
521
551
|
const transitionInfo = this.analyzeTransitionType(entries, currentIndex, currentEntry);
|
|
522
552
|
this.previousIndex = currentIndex;
|
|
@@ -529,6 +559,8 @@ export class BVTRecorder {
|
|
|
529
559
|
navigationAction: transitionInfo.action,
|
|
530
560
|
};
|
|
531
561
|
} catch (error) {
|
|
562
|
+
this.logger.error("Error in getCurrentTransition event");
|
|
563
|
+
this.logger.error(error);
|
|
532
564
|
console.error("Error in getTransistionType event", error);
|
|
533
565
|
} finally {
|
|
534
566
|
await client.detach();
|
|
@@ -597,6 +629,8 @@ export class BVTRecorder {
|
|
|
597
629
|
// add listener for frame navigation on new tab
|
|
598
630
|
this._addFrameNavigateListener(page);
|
|
599
631
|
} catch (error) {
|
|
632
|
+
this.logger.error("Error in page event");
|
|
633
|
+
this.logger.error(error);
|
|
600
634
|
console.error("Error in page event");
|
|
601
635
|
console.error(error);
|
|
602
636
|
}
|
|
@@ -638,6 +672,7 @@ export class BVTRecorder {
|
|
|
638
672
|
const { data } = await client.send("Page.captureScreenshot", { format: "png" });
|
|
639
673
|
return data;
|
|
640
674
|
} catch (error) {
|
|
675
|
+
this.logger.error("Error in taking browser screenshot");
|
|
641
676
|
console.error("Error in taking browser screenshot", error);
|
|
642
677
|
} finally {
|
|
643
678
|
await client.detach();
|
|
@@ -700,7 +735,7 @@ export class BVTRecorder {
|
|
|
700
735
|
}
|
|
701
736
|
async closeBrowser() {
|
|
702
737
|
delete process.env.TEMP_RUN;
|
|
703
|
-
await this.watcher.close().then(() => {
|
|
738
|
+
await this.watcher.close().then(() => {});
|
|
704
739
|
this.watcher = null;
|
|
705
740
|
this.previousIndex = null;
|
|
706
741
|
this.previousHistoryLength = null;
|
|
@@ -783,42 +818,65 @@ export class BVTRecorder {
|
|
|
783
818
|
async abortExecution() {
|
|
784
819
|
await this.stepRunner.abortExecution();
|
|
785
820
|
}
|
|
821
|
+
|
|
822
|
+
async pauseExecution({ cmdId }) {
|
|
823
|
+
await this.stepRunner.pauseExecution(cmdId);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
async resumeExecution({ cmdId }) {
|
|
827
|
+
await this.stepRunner.resumeExecution(cmdId);
|
|
828
|
+
}
|
|
829
|
+
|
|
786
830
|
async dealyedRevertMode() {
|
|
787
831
|
const timerId = setTimeout(async () => {
|
|
788
832
|
await this.revertMode();
|
|
789
833
|
}, 100);
|
|
790
834
|
this.timerId = timerId;
|
|
791
835
|
}
|
|
792
|
-
async runStep({ step, parametersMap, tags }, options) {
|
|
836
|
+
async runStep({ step, parametersMap, tags, isFirstStep, listenNetwork }, options) {
|
|
837
|
+
const { skipAfter = true, skipBefore = !isFirstStep } = options || {};
|
|
793
838
|
const _env = {
|
|
794
839
|
TOKEN: this.TOKEN,
|
|
795
840
|
TEMP_RUN: true,
|
|
796
841
|
REPORT_FOLDER: this.bvtContext.reportFolder,
|
|
797
842
|
BLINQ_ENV: this.envName,
|
|
843
|
+
DEBUG: "blinq:route",
|
|
798
844
|
};
|
|
799
845
|
|
|
800
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
|
+
}
|
|
801
853
|
for (const [key, value] of Object.entries(_env)) {
|
|
802
854
|
process.env[key] = value;
|
|
803
855
|
}
|
|
856
|
+
|
|
804
857
|
if (this.timerId) {
|
|
805
858
|
clearTimeout(this.timerId);
|
|
806
859
|
this.timerId = null;
|
|
807
860
|
}
|
|
808
861
|
await this.setMode("running");
|
|
862
|
+
|
|
809
863
|
try {
|
|
810
864
|
const { result, info } = await this.stepRunner.runStep(
|
|
811
865
|
{
|
|
812
866
|
step,
|
|
813
867
|
parametersMap,
|
|
814
868
|
envPath: this.envName,
|
|
815
|
-
tags
|
|
869
|
+
tags,
|
|
870
|
+
config: this.config,
|
|
816
871
|
},
|
|
817
872
|
this.bvtContext,
|
|
818
|
-
|
|
873
|
+
{
|
|
874
|
+
skipAfter,
|
|
875
|
+
skipBefore,
|
|
876
|
+
}
|
|
819
877
|
);
|
|
820
878
|
await this.revertMode();
|
|
821
|
-
return {
|
|
879
|
+
return { info };
|
|
822
880
|
} catch (error) {
|
|
823
881
|
await this.revertMode();
|
|
824
882
|
throw error;
|
|
@@ -829,15 +887,10 @@ export class BVTRecorder {
|
|
|
829
887
|
this.bvtContext.navigate = false;
|
|
830
888
|
}
|
|
831
889
|
}
|
|
832
|
-
async runScenario({ steps, parametersMap, tags }) {
|
|
833
|
-
for (const step of steps) {
|
|
834
|
-
await this.runStep({ step, parametersMap, tags });
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
890
|
async saveScenario({ scenario, featureName, override, isSingleStep }) {
|
|
838
891
|
await updateStepDefinitions({ scenario, featureName, projectDir: this.projectDir }); // updates mjs files
|
|
839
892
|
if (!isSingleStep) await updateFeatureFile({ featureName, scenario, override, projectDir: this.projectDir }); // updates gherkin files
|
|
840
|
-
await this.cleanup();
|
|
893
|
+
await this.cleanup({ tags: scenario.tags });
|
|
841
894
|
}
|
|
842
895
|
async getImplementedSteps() {
|
|
843
896
|
const stepsAndScenarios = await getImplementedSteps(this.projectDir);
|
|
@@ -861,6 +914,7 @@ export class BVTRecorder {
|
|
|
861
914
|
step.commands = [];
|
|
862
915
|
}
|
|
863
916
|
}
|
|
917
|
+
return steps;
|
|
864
918
|
// return getStepsAndCommandsForScenario({
|
|
865
919
|
// name,
|
|
866
920
|
// featureName,
|
|
@@ -868,6 +922,7 @@ export class BVTRecorder {
|
|
|
868
922
|
// map: this.scenariosStepsMap,
|
|
869
923
|
// });
|
|
870
924
|
}
|
|
925
|
+
|
|
871
926
|
async generateStepName({ commands, stepsNames, parameters, map }) {
|
|
872
927
|
return await this.namesService.generateStepName({ commands, stepsNames, parameters, map });
|
|
873
928
|
}
|
|
@@ -919,10 +974,11 @@ export class BVTRecorder {
|
|
|
919
974
|
if (existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
|
|
920
975
|
try {
|
|
921
976
|
const testData = JSON.parse(readFileSync(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
|
|
922
|
-
this.logger.info("Test data", testData);
|
|
977
|
+
// this.logger.info("Test data", testData);
|
|
923
978
|
this.sendEvent(this.events.getTestData, testData);
|
|
924
979
|
} catch (e) {
|
|
925
|
-
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);
|
|
926
982
|
}
|
|
927
983
|
}
|
|
928
984
|
|
|
@@ -931,10 +987,12 @@ export class BVTRecorder {
|
|
|
931
987
|
this.watcher.on("all", async (event, path) => {
|
|
932
988
|
try {
|
|
933
989
|
const testData = JSON.parse(await readFile(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
|
|
934
|
-
this.logger.info("Test data", testData);
|
|
990
|
+
// this.logger.info("Test data", testData);
|
|
991
|
+
console.log("Test data changed", testData);
|
|
935
992
|
this.sendEvent(this.events.getTestData, testData);
|
|
936
993
|
} catch (e) {
|
|
937
|
-
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);
|
|
938
996
|
}
|
|
939
997
|
});
|
|
940
998
|
}
|
|
@@ -950,9 +1008,9 @@ export class BVTRecorder {
|
|
|
950
1008
|
}
|
|
951
1009
|
}
|
|
952
1010
|
|
|
953
|
-
async discardTestData() {
|
|
1011
|
+
async discardTestData({ tags }) {
|
|
954
1012
|
resetTestData(this.envName, this.world);
|
|
955
|
-
await this.cleanup();
|
|
1013
|
+
await this.cleanup({ tags });
|
|
956
1014
|
}
|
|
957
1015
|
async addToTestData(obj) {
|
|
958
1016
|
if (!existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
|
|
@@ -967,7 +1025,7 @@ export class BVTRecorder {
|
|
|
967
1025
|
.filter((file) => file.endsWith(".feature"))
|
|
968
1026
|
.map((file) => path.join(this.projectDir, "features", file));
|
|
969
1027
|
try {
|
|
970
|
-
const parsedFiles = featureFiles.map((file) => parseFeatureFile(file));
|
|
1028
|
+
const parsedFiles = featureFiles.map((file) => this.parseFeatureFile(file));
|
|
971
1029
|
const output = {};
|
|
972
1030
|
parsedFiles.forEach((file) => {
|
|
973
1031
|
if (!file.feature) return;
|
|
@@ -991,10 +1049,11 @@ export class BVTRecorder {
|
|
|
991
1049
|
const stepParams = parseStepTextParameters(stepName);
|
|
992
1050
|
return getCommandsForImplementedStep(stepName, step_definitions, stepParams).commands;
|
|
993
1051
|
}
|
|
1052
|
+
|
|
994
1053
|
loadExistingScenario({ featureName, scenarioName }) {
|
|
995
1054
|
const step_definitions = loadStepDefinitions(this.projectDir);
|
|
996
1055
|
const featureFilePath = path.join(this.projectDir, "features", featureName);
|
|
997
|
-
const gherkinDoc = parseFeatureFile(featureFilePath);
|
|
1056
|
+
const gherkinDoc = this.parseFeatureFile(featureFilePath);
|
|
998
1057
|
const scenario = gherkinDoc.feature.children.find((child) => child.scenario.name === scenarioName)?.scenario;
|
|
999
1058
|
|
|
1000
1059
|
const steps = [];
|
|
@@ -1019,6 +1078,7 @@ export class BVTRecorder {
|
|
|
1019
1078
|
..._s,
|
|
1020
1079
|
keyword: step.keyword.trim(),
|
|
1021
1080
|
};
|
|
1081
|
+
parseRouteFiles(this.projectDir, _step);
|
|
1022
1082
|
steps.push(_step);
|
|
1023
1083
|
}
|
|
1024
1084
|
return {
|
|
@@ -1058,7 +1118,7 @@ export class BVTRecorder {
|
|
|
1058
1118
|
}
|
|
1059
1119
|
return result;
|
|
1060
1120
|
}
|
|
1061
|
-
async cleanup() {
|
|
1121
|
+
async cleanup({ tags }) {
|
|
1062
1122
|
const noopStep = {
|
|
1063
1123
|
text: "Noop",
|
|
1064
1124
|
isImplemented: true,
|
|
@@ -1072,6 +1132,7 @@ export class BVTRecorder {
|
|
|
1072
1132
|
{
|
|
1073
1133
|
step: noopStep,
|
|
1074
1134
|
parametersMap: {},
|
|
1135
|
+
tags: tags || [],
|
|
1075
1136
|
},
|
|
1076
1137
|
{
|
|
1077
1138
|
skipAfter: false,
|
|
@@ -1110,20 +1171,68 @@ export class BVTRecorder {
|
|
|
1110
1171
|
return false;
|
|
1111
1172
|
}
|
|
1112
1173
|
}
|
|
1113
|
-
}
|
|
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
|
+
}
|
|
1114
1215
|
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
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 {};
|
|
1127
1231
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
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);
|