@dev-blinq/cucumber_client 1.0.1379-dev → 1.0.1379-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 +107 -107
- package/bin/assets/preload/css_gen.js +10 -10
- 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 +13 -23
- package/bin/assets/scripts/snapshot_capturer.js +147 -147
- package/bin/assets/scripts/unique_locators.js +163 -44
- package/bin/assets/scripts/yaml.js +796 -783
- package/bin/assets/templates/_hooks_template.txt +41 -0
- package/bin/assets/templates/utils_template.txt +2 -45
- package/bin/client/apiTest/apiTest.js +6 -0
- 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 +107 -2
- package/bin/client/code_gen/function_signature.js +4 -0
- package/bin/client/code_gen/page_reflection.js +846 -906
- package/bin/client/code_gen/playwright_codeget.js +27 -3
- package/bin/client/cucumber/feature.js +4 -0
- package/bin/client/cucumber/feature_data.js +2 -2
- package/bin/client/cucumber/project_to_document.js +8 -2
- package/bin/client/cucumber/steps_definitions.js +6 -3
- package/bin/client/cucumber_selector.js +17 -1
- package/bin/client/local_agent.js +3 -2
- 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_init.js +325 -0
- package/bin/client/recorderv3/bvt_recorder.js +301 -99
- package/bin/client/recorderv3/implemented_steps.js +8 -0
- package/bin/client/recorderv3/index.js +4 -303
- package/bin/client/recorderv3/scriptTest.js +1 -1
- package/bin/client/recorderv3/services.js +431 -154
- package/bin/client/recorderv3/step_runner.js +315 -206
- package/bin/client/recorderv3/step_utils.js +479 -25
- package/bin/client/recorderv3/update_feature.js +9 -5
- package/bin/client/recorderv3/wbr_entry.js +61 -0
- package/bin/client/recording.js +1 -0
- package/bin/client/upload-service.js +3 -2
- package/bin/client/utils/socket_logger.js +132 -0
- package/bin/index.js +4 -1
- 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 -10
|
@@ -4,7 +4,7 @@ import { existsSync, readdirSync, readFileSync, rmSync } from "fs";
|
|
|
4
4
|
import path from "path";
|
|
5
5
|
import url from "url";
|
|
6
6
|
import { getImplementedSteps, parseRouteFiles } from "./implemented_steps.js";
|
|
7
|
-
import { NamesService } from "./services.js";
|
|
7
|
+
import { NamesService, PublishService, RemoteBrowserService } from "./services.js";
|
|
8
8
|
import { BVTStepRunner } from "./step_runner.js";
|
|
9
9
|
import { readFile, writeFile } from "fs/promises";
|
|
10
10
|
import { updateStepDefinitions, loadStepDefinitions, getCommandsForImplementedStep } from "./step_utils.js";
|
|
@@ -12,11 +12,12 @@ import { updateFeatureFile } from "./update_feature.js";
|
|
|
12
12
|
import { parseStepTextParameters } from "../cucumber/utils.js";
|
|
13
13
|
import { AstBuilder, GherkinClassicTokenMatcher, Parser } from "@cucumber/gherkin";
|
|
14
14
|
import chokidar from "chokidar";
|
|
15
|
-
import logger from "../../logger.js";
|
|
16
15
|
import { unEscapeNonPrintables } from "../cucumber/utils.js";
|
|
17
16
|
import { findAvailablePort } from "../utils/index.js";
|
|
18
|
-
import
|
|
19
|
-
|
|
17
|
+
import socketLogger from "../utils/socket_logger.js";
|
|
18
|
+
import { tmpdir } from "os";
|
|
19
|
+
import { faker } from "@faker-js/faker/locale/en_US";
|
|
20
|
+
import { chromium } from "playwright-core";
|
|
20
21
|
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
21
22
|
|
|
22
23
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -45,7 +46,6 @@ async function evaluate(frame, script) {
|
|
|
45
46
|
async function findNestedFrameSelector(frame, obj) {
|
|
46
47
|
try {
|
|
47
48
|
const parent = frame.parentFrame();
|
|
48
|
-
if (parent) console.log(`Parent frame: ${JSON.stringify(parent)}`);
|
|
49
49
|
if (!parent) return { children: obj };
|
|
50
50
|
const frameElement = await frame.frameElement();
|
|
51
51
|
if (!frameElement) return;
|
|
@@ -54,6 +54,7 @@ async function findNestedFrameSelector(frame, obj) {
|
|
|
54
54
|
}, frameElement);
|
|
55
55
|
return findNestedFrameSelector(parent, { children: obj, selectors });
|
|
56
56
|
} catch (e) {
|
|
57
|
+
socketLogger.error(`Error in findNestedFrameSelector: ${e}`);
|
|
57
58
|
console.error(e);
|
|
58
59
|
}
|
|
59
60
|
}
|
|
@@ -150,17 +151,30 @@ const transformAction = (action, el, isVerify, isPopupCloseClick, isInHoverMode,
|
|
|
150
151
|
};
|
|
151
152
|
}
|
|
152
153
|
default: {
|
|
154
|
+
socketLogger.error(`Action not supported: ${action.name}`);
|
|
153
155
|
console.log("action not supported", action);
|
|
154
156
|
throw new Error("action not supported");
|
|
155
157
|
}
|
|
156
158
|
}
|
|
157
159
|
};
|
|
160
|
+
const diffPaths = (currentPath, newPath) => {
|
|
161
|
+
const currentDomain = new URL(currentPath).hostname;
|
|
162
|
+
const newDomain = new URL(newPath).hostname;
|
|
163
|
+
if (currentDomain !== newDomain) {
|
|
164
|
+
return true;
|
|
165
|
+
} else {
|
|
166
|
+
const currentRoute = new URL(currentPath).pathname;
|
|
167
|
+
const newRoute = new URL(newPath).pathname;
|
|
168
|
+
return currentRoute !== newRoute;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
158
171
|
/**
|
|
159
172
|
* @typedef {Object} BVTRecorderInput
|
|
160
173
|
* @property {string} envName
|
|
161
174
|
* @property {string} projectDir
|
|
162
175
|
* @property {string} TOKEN
|
|
163
176
|
* @property {(name:string, data:any)=> void} sendEvent
|
|
177
|
+
* @property {Object} logger
|
|
164
178
|
*/
|
|
165
179
|
export class BVTRecorder {
|
|
166
180
|
#currentURL = "";
|
|
@@ -174,7 +188,6 @@ export class BVTRecorder {
|
|
|
174
188
|
*/
|
|
175
189
|
constructor(initialState) {
|
|
176
190
|
Object.assign(this, initialState);
|
|
177
|
-
this.logger = logger;
|
|
178
191
|
this.screenshotMap = new Map();
|
|
179
192
|
this.snapshotMap = new Map();
|
|
180
193
|
this.scenariosStepsMap = new Map();
|
|
@@ -184,40 +197,20 @@ export class BVTRecorder {
|
|
|
184
197
|
projectDir: this.projectDir,
|
|
185
198
|
logger: this.logger,
|
|
186
199
|
});
|
|
187
|
-
this.
|
|
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);
|
|
194
|
-
this.sendEvent(this.events.cmdExecutionStart, data);
|
|
195
|
-
break;
|
|
196
|
-
case "cmdExecutionSuccess":
|
|
197
|
-
console.log("Sending cmdExecutionSuccess event for cmdId:", data);
|
|
198
|
-
this.sendEvent(this.events.cmdExecutionSuccess, data);
|
|
199
|
-
break;
|
|
200
|
-
case "cmdExecutionError":
|
|
201
|
-
console.log("Sending cmdExecutionError event for cmdId:", data);
|
|
202
|
-
this.sendEvent(this.events.cmdExecutionError, data);
|
|
203
|
-
break;
|
|
204
|
-
case "interceptResults":
|
|
205
|
-
console.log("Sending interceptResults event");
|
|
206
|
-
this.sendEvent(this.events.interceptResults, data);
|
|
207
|
-
break;
|
|
208
|
-
default:
|
|
209
|
-
console.warn("Unknown command execution status type:", data.type);
|
|
210
|
-
break;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
},
|
|
214
|
-
});
|
|
200
|
+
this.workspaceService = new PublishService(this.TOKEN);
|
|
215
201
|
this.pageSet = new Set();
|
|
202
|
+
this.pageMetaDataSet = new Set();
|
|
216
203
|
this.lastKnownUrlPath = "";
|
|
217
|
-
// TODO: what is world?
|
|
218
204
|
this.world = { attach: () => {} };
|
|
219
205
|
this.shouldTakeScreenshot = true;
|
|
220
206
|
this.watcher = null;
|
|
207
|
+
this.networkEventsFolder = path.join(tmpdir(), "blinq_network_events");
|
|
208
|
+
this.tempProjectFolder = `${tmpdir()}/bvt_temp_project_${Math.floor(Math.random() * 1000000)}`;
|
|
209
|
+
this.tempSnapshotsFolder = path.join(this.tempProjectFolder, "data/snapshots");
|
|
210
|
+
|
|
211
|
+
if (existsSync(this.networkEventsFolder)) {
|
|
212
|
+
rmSync(this.networkEventsFolder, { recursive: true, force: true });
|
|
213
|
+
}
|
|
221
214
|
}
|
|
222
215
|
events = {
|
|
223
216
|
onFrameNavigate: "BVTRecorder.onFrameNavigate",
|
|
@@ -232,12 +225,16 @@ export class BVTRecorder {
|
|
|
232
225
|
cmdExecutionSuccess: "BVTRecorder.cmdExecutionSuccess",
|
|
233
226
|
cmdExecutionError: "BVTRecorder.cmdExecutionError",
|
|
234
227
|
interceptResults: "BVTRecorder.interceptResults",
|
|
228
|
+
onDebugURLChange: "BVTRecorder.onDebugURLChange",
|
|
229
|
+
updateCommand: "BVTRecorder.updateCommand",
|
|
230
|
+
browserStateSync: "BrowserService.stateSync",
|
|
231
|
+
browserStateError: "BrowserService.stateError",
|
|
235
232
|
};
|
|
236
233
|
bindings = {
|
|
237
234
|
__bvt_recordCommand: async ({ frame, page, context }, event) => {
|
|
238
235
|
this.#activeFrame = frame;
|
|
239
236
|
const nestFrmLoc = await findNestedFrameSelector(frame);
|
|
240
|
-
|
|
237
|
+
this.logger.info(`Time taken for action: ${event.statistics.time}`);
|
|
241
238
|
await this.onAction({ ...event, nestFrmLoc });
|
|
242
239
|
},
|
|
243
240
|
__bvt_getMode: async () => {
|
|
@@ -256,8 +253,7 @@ export class BVTRecorder {
|
|
|
256
253
|
await this.onClosePopup();
|
|
257
254
|
},
|
|
258
255
|
__bvt_log: async (src, message) => {
|
|
259
|
-
|
|
260
|
-
console.log(`Inside Browser: ${message}`);
|
|
256
|
+
this.logger.info(`Inside Browser: ${message}`);
|
|
261
257
|
},
|
|
262
258
|
__bvt_getObject: (_src, obj) => {
|
|
263
259
|
this.processObject(obj);
|
|
@@ -319,10 +315,11 @@ export class BVTRecorder {
|
|
|
319
315
|
}
|
|
320
316
|
|
|
321
317
|
async _initBrowser({ url }) {
|
|
318
|
+
socketLogger.info("Only present in 1.0.1293-stage");
|
|
322
319
|
this.#remoteDebuggerPort = await findAvailablePort();
|
|
323
320
|
process.env.CDP_LISTEN_PORT = this.#remoteDebuggerPort;
|
|
324
321
|
|
|
325
|
-
this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
|
|
322
|
+
// this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
|
|
326
323
|
this.world = { attach: () => {} };
|
|
327
324
|
|
|
328
325
|
const ai_config_file = path.join(this.projectDir, "ai_config.json");
|
|
@@ -331,7 +328,7 @@ export class BVTRecorder {
|
|
|
331
328
|
try {
|
|
332
329
|
ai_config = JSON.parse(readFileSync(ai_config_file, "utf8"));
|
|
333
330
|
} catch (error) {
|
|
334
|
-
|
|
331
|
+
this.logger.error("Error reading ai_config.json", error);
|
|
335
332
|
}
|
|
336
333
|
}
|
|
337
334
|
this.config = ai_config;
|
|
@@ -343,18 +340,47 @@ export class BVTRecorder {
|
|
|
343
340
|
],
|
|
344
341
|
};
|
|
345
342
|
|
|
346
|
-
let startTime = Date.now();
|
|
347
343
|
const bvtContext = await initContext(url, false, false, this.world, 450, initScripts, this.envName);
|
|
348
|
-
let stopTime = Date.now();
|
|
349
|
-
this.logger.info(`Browser launched in ${(stopTime - startTime) / 1000} s`);
|
|
350
344
|
this.bvtContext = bvtContext;
|
|
351
|
-
|
|
352
|
-
|
|
345
|
+
this.stepRunner = new BVTStepRunner({
|
|
346
|
+
projectDir: this.projectDir,
|
|
347
|
+
sendExecutionStatus: (data) => {
|
|
348
|
+
if (data && data.type) {
|
|
349
|
+
switch (data.type) {
|
|
350
|
+
case "cmdExecutionStart":
|
|
351
|
+
this.sendEvent(this.events.cmdExecutionStart, data);
|
|
352
|
+
break;
|
|
353
|
+
case "cmdExecutionSuccess":
|
|
354
|
+
this.sendEvent(this.events.cmdExecutionSuccess, data);
|
|
355
|
+
break;
|
|
356
|
+
case "cmdExecutionError":
|
|
357
|
+
this.sendEvent(this.events.cmdExecutionError, data);
|
|
358
|
+
break;
|
|
359
|
+
case "interceptResults":
|
|
360
|
+
this.sendEvent(this.events.interceptResults, data);
|
|
361
|
+
break;
|
|
362
|
+
default:
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
bvtContext: this.bvtContext,
|
|
368
|
+
});
|
|
369
|
+
this.context = bvtContext.playContext;
|
|
353
370
|
this.web = bvtContext.stable || bvtContext.web;
|
|
354
371
|
this.web.tryAllStrategies = true;
|
|
355
372
|
this.page = bvtContext.page;
|
|
356
|
-
|
|
357
373
|
this.pageSet.add(this.page);
|
|
374
|
+
if (process.env.REMOTE_RECORDER === "true") {
|
|
375
|
+
this.browserEmitter = new RemoteBrowserService({
|
|
376
|
+
CDP_CONNECT_URL: `http://localhost:${this.#remoteDebuggerPort}`,
|
|
377
|
+
context: this.context,
|
|
378
|
+
});
|
|
379
|
+
this.browserEmitter.on(this.events.browserStateSync, (state) => {
|
|
380
|
+
this.page = this.browserEmitter.getSelectedPage();
|
|
381
|
+
this.sendEvent(this.events.browserStateSync, state);
|
|
382
|
+
});
|
|
383
|
+
}
|
|
358
384
|
this.lastKnownUrlPath = this._updateUrlPath();
|
|
359
385
|
const browser = await this.context.browser();
|
|
360
386
|
this.browser = browser;
|
|
@@ -367,6 +393,14 @@ export class BVTRecorder {
|
|
|
367
393
|
this.web.onRestoreSaveState = (url) => {
|
|
368
394
|
this._initBrowser({ url });
|
|
369
395
|
};
|
|
396
|
+
|
|
397
|
+
// create a second browser for locator generation
|
|
398
|
+
this.backgroundBrowser = await chromium.launch({
|
|
399
|
+
headless: true,
|
|
400
|
+
});
|
|
401
|
+
this.backgroundContext = await this.backgroundBrowser.newContext({});
|
|
402
|
+
await this.backgroundContext.addInitScript({ content: this.getInitScripts(this.config) });
|
|
403
|
+
await this.backgroundContext.newPage();
|
|
370
404
|
}
|
|
371
405
|
async onClosePopup() {
|
|
372
406
|
// console.log("close popups");
|
|
@@ -381,13 +415,15 @@ export class BVTRecorder {
|
|
|
381
415
|
}
|
|
382
416
|
return;
|
|
383
417
|
} catch (error) {
|
|
384
|
-
console.error("Error evaluting in context:", error);
|
|
418
|
+
// console.error("Error evaluting in context:", error);
|
|
419
|
+
this.logger.error("Error evaluating in context:", error);
|
|
385
420
|
}
|
|
386
421
|
}
|
|
387
422
|
}
|
|
388
423
|
|
|
389
424
|
getMode() {
|
|
390
|
-
console.log("getMode", this.#mode);
|
|
425
|
+
// console.log("getMode", this.#mode);
|
|
426
|
+
this.logger.info("Current mode:", this.#mode);
|
|
391
427
|
return this.#mode;
|
|
392
428
|
}
|
|
393
429
|
|
|
@@ -412,7 +448,7 @@ export class BVTRecorder {
|
|
|
412
448
|
|
|
413
449
|
// eval init script on current tab
|
|
414
450
|
// await this._initPage(this.page);
|
|
415
|
-
this.#currentURL =
|
|
451
|
+
this.#currentURL = url;
|
|
416
452
|
|
|
417
453
|
await this.page.dispatchEvent("html", "scroll");
|
|
418
454
|
await delay(1000);
|
|
@@ -429,6 +465,8 @@ export class BVTRecorder {
|
|
|
429
465
|
this.sendEvent(this.events.onBrowserClose);
|
|
430
466
|
}
|
|
431
467
|
} catch (error) {
|
|
468
|
+
this.logger.error("Error in page close event");
|
|
469
|
+
this.logger.error(error);
|
|
432
470
|
console.error("Error in page close event");
|
|
433
471
|
console.error(error);
|
|
434
472
|
}
|
|
@@ -439,8 +477,10 @@ export class BVTRecorder {
|
|
|
439
477
|
if (frame !== page.mainFrame()) return;
|
|
440
478
|
this.handlePageTransition();
|
|
441
479
|
} catch (error) {
|
|
480
|
+
this.logger.error("Error in handlePageTransition event");
|
|
481
|
+
this.logger.error(error);
|
|
442
482
|
console.error("Error in handlePageTransition event");
|
|
443
|
-
|
|
483
|
+
console.error(error);
|
|
444
484
|
}
|
|
445
485
|
try {
|
|
446
486
|
if (frame !== this.#activeFrame) return;
|
|
@@ -450,15 +490,18 @@ export class BVTRecorder {
|
|
|
450
490
|
element: { inputID: "frame" },
|
|
451
491
|
});
|
|
452
492
|
|
|
453
|
-
const
|
|
493
|
+
const newUrl = frame.url();
|
|
494
|
+
const newPath = new URL(newUrl).pathname;
|
|
454
495
|
const newTitle = await frame.title();
|
|
455
|
-
|
|
496
|
+
const changed = diffPaths(this.#currentURL, newUrl);
|
|
497
|
+
|
|
498
|
+
if (changed) {
|
|
456
499
|
this.sendEvent(this.events.onFrameNavigate, { url: newPath, title: newTitle });
|
|
457
|
-
this.#currentURL =
|
|
500
|
+
this.#currentURL = newUrl;
|
|
458
501
|
}
|
|
459
|
-
// await this._setRecordingMode(frame);
|
|
460
|
-
// await this._initPage(page);
|
|
461
502
|
} catch (error) {
|
|
503
|
+
this.logger.error("Error in frame navigate event");
|
|
504
|
+
this.logger.error(error);
|
|
462
505
|
console.error("Error in frame navigate event");
|
|
463
506
|
// console.error(error);
|
|
464
507
|
}
|
|
@@ -541,13 +584,9 @@ export class BVTRecorder {
|
|
|
541
584
|
|
|
542
585
|
try {
|
|
543
586
|
const result = await client.send("Page.getNavigationHistory");
|
|
544
|
-
// console.log("Navigation History:", result);
|
|
545
587
|
const entries = result.entries;
|
|
546
588
|
const currentIndex = result.currentIndex;
|
|
547
589
|
|
|
548
|
-
// ignore if currentIndex is not the last entry
|
|
549
|
-
// if (currentIndex !== entries.length - 1) return;
|
|
550
|
-
|
|
551
590
|
const currentEntry = entries[currentIndex];
|
|
552
591
|
const transitionInfo = this.analyzeTransitionType(entries, currentIndex, currentEntry);
|
|
553
592
|
this.previousIndex = currentIndex;
|
|
@@ -560,6 +599,8 @@ export class BVTRecorder {
|
|
|
560
599
|
navigationAction: transitionInfo.action,
|
|
561
600
|
};
|
|
562
601
|
} catch (error) {
|
|
602
|
+
this.logger.error("Error in getCurrentTransition event");
|
|
603
|
+
this.logger.error(error);
|
|
563
604
|
console.error("Error in getTransistionType event", error);
|
|
564
605
|
} finally {
|
|
565
606
|
await client.detach();
|
|
@@ -628,6 +669,8 @@ export class BVTRecorder {
|
|
|
628
669
|
// add listener for frame navigation on new tab
|
|
629
670
|
this._addFrameNavigateListener(page);
|
|
630
671
|
} catch (error) {
|
|
672
|
+
this.logger.error("Error in page event");
|
|
673
|
+
this.logger.error(error);
|
|
631
674
|
console.error("Error in page event");
|
|
632
675
|
console.error(error);
|
|
633
676
|
}
|
|
@@ -669,6 +712,7 @@ export class BVTRecorder {
|
|
|
669
712
|
const { data } = await client.send("Page.captureScreenshot", { format: "png" });
|
|
670
713
|
return data;
|
|
671
714
|
} catch (error) {
|
|
715
|
+
this.logger.error("Error in taking browser screenshot");
|
|
672
716
|
console.error("Error in taking browser screenshot", error);
|
|
673
717
|
} finally {
|
|
674
718
|
await client.detach();
|
|
@@ -684,6 +728,52 @@ export class BVTRecorder {
|
|
|
684
728
|
console.error("Error in saving screenshot: ", error);
|
|
685
729
|
}
|
|
686
730
|
}
|
|
731
|
+
async generateLocators(event) {
|
|
732
|
+
const snapshotDetails = event.snapshotDetails;
|
|
733
|
+
if (!snapshotDetails) {
|
|
734
|
+
throw new Error("No snapshot details found");
|
|
735
|
+
}
|
|
736
|
+
const mode = event.mode;
|
|
737
|
+
const inputID = event.element.inputID;
|
|
738
|
+
|
|
739
|
+
const { id, contextId, doc } = snapshotDetails;
|
|
740
|
+
// const selector = `[data-blinq-id="${id}"]`;
|
|
741
|
+
const newPage = await this.backgroundContext.newPage();
|
|
742
|
+
await newPage.setContent(doc, { waitUntil: "domcontentloaded" });
|
|
743
|
+
const locatorsObj = await newPage.evaluate(
|
|
744
|
+
([id, contextId, mode]) => {
|
|
745
|
+
const recorder = window.__bvt_Recorder;
|
|
746
|
+
const contextElement = document.querySelector(`[data-blinq-context-id="${contextId}"]`);
|
|
747
|
+
const el = document.querySelector(`[data-blinq-id="${id}"]`);
|
|
748
|
+
if (contextElement) {
|
|
749
|
+
const result = recorder.locatorGenerator.toContextLocators(el, contextElement);
|
|
750
|
+
return result;
|
|
751
|
+
}
|
|
752
|
+
const isRecordingText = mode === "recordingText";
|
|
753
|
+
return recorder.locatorGenerator.getElementLocators(el, {
|
|
754
|
+
excludeText: isRecordingText,
|
|
755
|
+
});
|
|
756
|
+
},
|
|
757
|
+
[id, contextId, mode]
|
|
758
|
+
);
|
|
759
|
+
|
|
760
|
+
console.log(`Generated locators: for ${inputID}: `, JSON.stringify(locatorsObj));
|
|
761
|
+
await newPage.close();
|
|
762
|
+
if (event.nestFrmLoc?.children) {
|
|
763
|
+
locatorsObj.nestFrmLoc = event.nestFrmLoc.children;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
this.sendEvent(this.events.updateCommand, {
|
|
767
|
+
locators: {
|
|
768
|
+
locators: locatorsObj.locators,
|
|
769
|
+
nestFrmLoc: locatorsObj.nestFrmLoc,
|
|
770
|
+
iframe_src: !event.frame.isTop ? event.frame.url : undefined,
|
|
771
|
+
},
|
|
772
|
+
allStrategyLocators: locatorsObj.allStrategyLocators,
|
|
773
|
+
inputID,
|
|
774
|
+
});
|
|
775
|
+
// const
|
|
776
|
+
}
|
|
687
777
|
async onAction(event) {
|
|
688
778
|
this._updateUrlPath();
|
|
689
779
|
// const locators = this.overlayLocators(event);
|
|
@@ -697,25 +787,26 @@ export class BVTRecorder {
|
|
|
697
787
|
event.mode === "recordingHover",
|
|
698
788
|
event.mode === "multiInspecting"
|
|
699
789
|
),
|
|
700
|
-
locators: {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
},
|
|
704
|
-
allStrategyLocators: event.allStrategyLocators,
|
|
790
|
+
// locators: {
|
|
791
|
+
// locators: event.locators,
|
|
792
|
+
// iframe_src: !event.frame.isTop ? event.frame.url : undefined,
|
|
793
|
+
// },
|
|
794
|
+
// allStrategyLocators: event.allStrategyLocators,
|
|
705
795
|
url: event.frame.url,
|
|
706
796
|
title: event.frame.title,
|
|
707
797
|
extract: {},
|
|
708
798
|
lastKnownUrlPath: this.lastKnownUrlPath,
|
|
709
799
|
};
|
|
710
|
-
if (event.nestFrmLoc?.children) {
|
|
711
|
-
|
|
712
|
-
}
|
|
800
|
+
// if (event.nestFrmLoc?.children) {
|
|
801
|
+
// cmdEvent.locators.nestFrmLoc = event.nestFrmLoc.children;
|
|
802
|
+
// }
|
|
713
803
|
// this.logger.info({ event });
|
|
714
804
|
if (this.shouldTakeScreenshot) {
|
|
715
805
|
await this.storeScreenshot(event);
|
|
716
806
|
}
|
|
717
807
|
this.sendEvent(this.events.onNewCommand, cmdEvent);
|
|
718
808
|
this._updateUrlPath();
|
|
809
|
+
await this.generateLocators(event);
|
|
719
810
|
}
|
|
720
811
|
_updateUrlPath() {
|
|
721
812
|
try {
|
|
@@ -737,7 +828,6 @@ export class BVTRecorder {
|
|
|
737
828
|
this.previousHistoryLength = null;
|
|
738
829
|
this.previousUrl = null;
|
|
739
830
|
this.previousEntries = null;
|
|
740
|
-
|
|
741
831
|
await closeContext();
|
|
742
832
|
this.pageSet.clear();
|
|
743
833
|
}
|
|
@@ -789,7 +879,6 @@ export class BVTRecorder {
|
|
|
789
879
|
}
|
|
790
880
|
|
|
791
881
|
async startRecordingInput() {
|
|
792
|
-
console.log("startRecordingInput");
|
|
793
882
|
await this.setMode("recordingInput");
|
|
794
883
|
}
|
|
795
884
|
async stopRecordingInput() {
|
|
@@ -813,9 +902,17 @@ export class BVTRecorder {
|
|
|
813
902
|
}
|
|
814
903
|
|
|
815
904
|
async abortExecution() {
|
|
816
|
-
this.bvtContext.web.abortedExecution = true;
|
|
817
905
|
await this.stepRunner.abortExecution();
|
|
818
906
|
}
|
|
907
|
+
|
|
908
|
+
async pauseExecution({ cmdId }) {
|
|
909
|
+
await this.stepRunner.pauseExecution(cmdId);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
async resumeExecution({ cmdId }) {
|
|
913
|
+
await this.stepRunner.resumeExecution(cmdId);
|
|
914
|
+
}
|
|
915
|
+
|
|
819
916
|
async dealyedRevertMode() {
|
|
820
917
|
const timerId = setTimeout(async () => {
|
|
821
918
|
await this.revertMode();
|
|
@@ -824,18 +921,24 @@ export class BVTRecorder {
|
|
|
824
921
|
}
|
|
825
922
|
async runStep({ step, parametersMap, tags, isFirstStep, listenNetwork }, options) {
|
|
826
923
|
const { skipAfter = true, skipBefore = !isFirstStep } = options || {};
|
|
924
|
+
|
|
925
|
+
const env = path.basename(this.envName, ".json");
|
|
827
926
|
const _env = {
|
|
828
927
|
TOKEN: this.TOKEN,
|
|
829
928
|
TEMP_RUN: true,
|
|
830
929
|
REPORT_FOLDER: this.bvtContext.reportFolder,
|
|
831
930
|
BLINQ_ENV: this.envName,
|
|
832
|
-
|
|
833
|
-
|
|
931
|
+
DEBUG: "blinq:route",
|
|
932
|
+
BVT_TEMP_SNAPSHOTS_FOLDER: path.join(this.tempSnapshotsFolder, env),
|
|
834
933
|
};
|
|
835
934
|
|
|
836
935
|
this.bvtContext.navigate = true;
|
|
837
936
|
this.bvtContext.loadedRoutes = null;
|
|
838
|
-
|
|
937
|
+
if (listenNetwork) {
|
|
938
|
+
this.bvtContext.STORE_DETAILED_NETWORK_DATA = true;
|
|
939
|
+
} else {
|
|
940
|
+
this.bvtContext.STORE_DETAILED_NETWORK_DATA = false;
|
|
941
|
+
}
|
|
839
942
|
for (const [key, value] of Object.entries(_env)) {
|
|
840
943
|
process.env[key] = value;
|
|
841
944
|
}
|
|
@@ -871,13 +974,26 @@ export class BVTRecorder {
|
|
|
871
974
|
delete process.env[key];
|
|
872
975
|
}
|
|
873
976
|
this.bvtContext.navigate = false;
|
|
874
|
-
this.bvtContext.web.abortedExecution = false;
|
|
875
977
|
}
|
|
876
978
|
}
|
|
877
|
-
async saveScenario({ scenario, featureName, override, isSingleStep }) {
|
|
878
|
-
await updateStepDefinitions({ scenario, featureName, projectDir: this.projectDir }); // updates mjs files
|
|
879
|
-
if (!isSingleStep) await updateFeatureFile({ featureName, scenario, override, projectDir: this.projectDir }); // updates gherkin files
|
|
880
|
-
await this.
|
|
979
|
+
async saveScenario({ scenario, featureName, override, isSingleStep, branch, isEditing, env }) {
|
|
980
|
+
// await updateStepDefinitions({ scenario, featureName, projectDir: this.projectDir }); // updates mjs files
|
|
981
|
+
// if (!isSingleStep) await updateFeatureFile({ featureName, scenario, override, projectDir: this.projectDir }); // updates gherkin files
|
|
982
|
+
const res = await this.workspaceService.saveScenario({
|
|
983
|
+
scenario,
|
|
984
|
+
featureName,
|
|
985
|
+
override,
|
|
986
|
+
isSingleStep,
|
|
987
|
+
branch,
|
|
988
|
+
isEditing,
|
|
989
|
+
projectId: path.basename(this.projectDir),
|
|
990
|
+
env,
|
|
991
|
+
});
|
|
992
|
+
if (res.success) {
|
|
993
|
+
await this.cleanup({ tags: scenario.tags });
|
|
994
|
+
} else {
|
|
995
|
+
throw new Error(res.message || "Error saving scenario");
|
|
996
|
+
}
|
|
881
997
|
}
|
|
882
998
|
async getImplementedSteps() {
|
|
883
999
|
const stepsAndScenarios = await getImplementedSteps(this.projectDir);
|
|
@@ -961,10 +1077,11 @@ export class BVTRecorder {
|
|
|
961
1077
|
if (existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
|
|
962
1078
|
try {
|
|
963
1079
|
const testData = JSON.parse(readFileSync(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
|
|
964
|
-
this.logger.info("Test data", testData);
|
|
1080
|
+
// this.logger.info("Test data", testData);
|
|
965
1081
|
this.sendEvent(this.events.getTestData, testData);
|
|
966
1082
|
} catch (e) {
|
|
967
|
-
this.logger.error("Error reading test data file", e);
|
|
1083
|
+
// this.logger.error("Error reading test data file", e);
|
|
1084
|
+
console.log("Error reading test data file", e);
|
|
968
1085
|
}
|
|
969
1086
|
}
|
|
970
1087
|
|
|
@@ -973,10 +1090,12 @@ export class BVTRecorder {
|
|
|
973
1090
|
this.watcher.on("all", async (event, path) => {
|
|
974
1091
|
try {
|
|
975
1092
|
const testData = JSON.parse(await readFile(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
|
|
976
|
-
this.logger.info("Test data", testData);
|
|
1093
|
+
// this.logger.info("Test data", testData);
|
|
1094
|
+
console.log("Test data changed", testData);
|
|
977
1095
|
this.sendEvent(this.events.getTestData, testData);
|
|
978
1096
|
} catch (e) {
|
|
979
|
-
this.logger.error("Error reading test data file", e);
|
|
1097
|
+
// this.logger.error("Error reading test data file", e);
|
|
1098
|
+
console.log("Error reading test data file", e);
|
|
980
1099
|
}
|
|
981
1100
|
});
|
|
982
1101
|
}
|
|
@@ -1009,7 +1128,7 @@ export class BVTRecorder {
|
|
|
1009
1128
|
.filter((file) => file.endsWith(".feature"))
|
|
1010
1129
|
.map((file) => path.join(this.projectDir, "features", file));
|
|
1011
1130
|
try {
|
|
1012
|
-
const parsedFiles = featureFiles.map((file) => parseFeatureFile(file));
|
|
1131
|
+
const parsedFiles = featureFiles.map((file) => this.parseFeatureFile(file));
|
|
1013
1132
|
const output = {};
|
|
1014
1133
|
parsedFiles.forEach((file) => {
|
|
1015
1134
|
if (!file.feature) return;
|
|
@@ -1037,7 +1156,7 @@ export class BVTRecorder {
|
|
|
1037
1156
|
loadExistingScenario({ featureName, scenarioName }) {
|
|
1038
1157
|
const step_definitions = loadStepDefinitions(this.projectDir);
|
|
1039
1158
|
const featureFilePath = path.join(this.projectDir, "features", featureName);
|
|
1040
|
-
const gherkinDoc = parseFeatureFile(featureFilePath);
|
|
1159
|
+
const gherkinDoc = this.parseFeatureFile(featureFilePath);
|
|
1041
1160
|
const scenario = gherkinDoc.feature.children.find((child) => child.scenario.name === scenarioName)?.scenario;
|
|
1042
1161
|
|
|
1043
1162
|
const steps = [];
|
|
@@ -1196,20 +1315,103 @@ export class BVTRecorder {
|
|
|
1196
1315
|
await this.cleanupExecution({ tags });
|
|
1197
1316
|
await this.initExecution({ tags });
|
|
1198
1317
|
}
|
|
1199
|
-
}
|
|
1200
1318
|
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1319
|
+
parseFeatureFile(featureFilePath) {
|
|
1320
|
+
try {
|
|
1321
|
+
let id = 0;
|
|
1322
|
+
const uuidFn = () => (++id).toString(16);
|
|
1323
|
+
const builder = new AstBuilder(uuidFn);
|
|
1324
|
+
const matcher = new GherkinClassicTokenMatcher();
|
|
1325
|
+
const parser = new Parser(builder, matcher);
|
|
1326
|
+
const source = readFileSync(featureFilePath, "utf8");
|
|
1327
|
+
const gherkinDocument = parser.parse(source);
|
|
1328
|
+
return gherkinDocument;
|
|
1329
|
+
} catch (e) {
|
|
1330
|
+
this.logger.error(`Error parsing feature file: ${featureFilePath}`);
|
|
1331
|
+
console.log(e);
|
|
1332
|
+
}
|
|
1333
|
+
return {};
|
|
1213
1334
|
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1335
|
+
|
|
1336
|
+
stopRecordingNetwork(input) {
|
|
1337
|
+
if (this.bvtContext) {
|
|
1338
|
+
this.bvtContext.STORE_DETAILED_NETWORK_DATA = false;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
async fakeParams(params) {
|
|
1343
|
+
const newFakeParams = {};
|
|
1344
|
+
Object.keys(params).forEach((key) => {
|
|
1345
|
+
if (!params[key].startsWith("{{") || !params[key].endsWith("}}")) {
|
|
1346
|
+
newFakeParams[key] = params[key];
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
try {
|
|
1351
|
+
const value = params[key].substring(2, params[key].length - 2).trim();
|
|
1352
|
+
const faking = value.split("(")[0].split(".");
|
|
1353
|
+
let argument = value.substring(value.indexOf("(") + 1, value.lastIndexOf(")"));
|
|
1354
|
+
argument = isNaN(Number(argument)) || argument === "" ? argument : Number(argument);
|
|
1355
|
+
let fakeFunc = faker;
|
|
1356
|
+
faking.forEach((f) => {
|
|
1357
|
+
fakeFunc = fakeFunc[f];
|
|
1358
|
+
});
|
|
1359
|
+
const newValue = fakeFunc(argument);
|
|
1360
|
+
newFakeParams[key] = newValue;
|
|
1361
|
+
} catch (error) {
|
|
1362
|
+
newFakeParams[key] = params[key];
|
|
1363
|
+
}
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1366
|
+
return newFakeParams;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
async getBrowserState() {
|
|
1370
|
+
try {
|
|
1371
|
+
const state = await this.browserEmitter?.getState();
|
|
1372
|
+
this.sendEvent(this.events.browserStateSync, state);
|
|
1373
|
+
} catch (error) {
|
|
1374
|
+
this.logger.error("Error getting browser state:", error);
|
|
1375
|
+
this.sendEvent(this.events.browserStateError, {
|
|
1376
|
+
message: "Error getting browser state",
|
|
1377
|
+
code: "GET_STATE_ERROR",
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
async createTab({ url }) {
|
|
1383
|
+
try {
|
|
1384
|
+
await this.browserEmitter?.createTab(url);
|
|
1385
|
+
} catch (error) {
|
|
1386
|
+
this.logger.error("Error creating tab:", error);
|
|
1387
|
+
this.sendEvent(this.events.browserStateError, {
|
|
1388
|
+
message: "Error creating tab",
|
|
1389
|
+
code: "CREATE_TAB_ERROR",
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
async closeTab({ pageId }) {
|
|
1395
|
+
try {
|
|
1396
|
+
await this.browserEmitter?.closeTab(pageId);
|
|
1397
|
+
} catch (error) {
|
|
1398
|
+
this.logger.error("Error closing tab:", error);
|
|
1399
|
+
this.sendEvent(this.events.browserStateError, {
|
|
1400
|
+
message: "Error closing tab",
|
|
1401
|
+
code: "CLOSE_TAB_ERROR",
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
async selectTab({ pageId }) {
|
|
1407
|
+
try {
|
|
1408
|
+
await this.browserEmitter?.selectTab(pageId);
|
|
1409
|
+
} catch (error) {
|
|
1410
|
+
this.logger.error("Error selecting tab:", error);
|
|
1411
|
+
this.sendEvent(this.events.browserStateError, {
|
|
1412
|
+
message: "Error selecting tab",
|
|
1413
|
+
code: "SELECT_TAB_ERROR",
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|