@dev-blinq/cucumber_client 1.0.1690-dev → 1.0.1692-dev

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.
@@ -1,22 +1,33 @@
1
1
  // define the jsdoc type for the input
2
2
  import { closeContext, initContext, _getDataFile, resetTestData } from "automation_model";
3
3
  import { existsSync, readdirSync, readFileSync, rmSync } from "fs";
4
+ import { rm } from "fs/promises";
4
5
  import path from "path";
5
6
  import url from "url";
6
7
  import { getImplementedSteps, parseRouteFiles } from "./implemented_steps.js";
7
8
  import { NamesService, RemoteBrowserService, PublishService } from "./services.js";
8
9
  import { BVTStepRunner } from "./step_runner.js";
9
10
  import { readFile, writeFile } from "fs/promises";
10
- import { loadStepDefinitions, getCommandsForImplementedStep } from "./step_utils.js";
11
+ import {
12
+ loadStepDefinitions,
13
+ getCommandsForImplementedStep,
14
+ getCodePage,
15
+ getCucumberStep,
16
+ _toRecordingStep,
17
+ toMethodName,
18
+ } from "./step_utils.js";
11
19
  import { parseStepTextParameters } from "../cucumber/utils.js";
12
20
  import { AstBuilder, GherkinClassicTokenMatcher, Parser } from "@cucumber/gherkin";
13
21
  import chokidar from "chokidar";
14
22
  import { unEscapeNonPrintables } from "../cucumber/utils.js";
15
- import { findAvailablePort } from "../utils/index.js";
16
- import socketLogger from "../utils/socket_logger.js";
23
+ import { findAvailablePort, getRunsServiceBaseURL } from "../utils/index.js";
24
+ import socketLogger, { getErrorMessage } from "../utils/socket_logger.js";
17
25
  import { tmpdir } from "os";
18
26
  import { faker } from "@faker-js/faker/locale/en_US";
19
27
  import { chromium } from "playwright-core";
28
+ import { axiosClient } from "../utils/axiosClient.js";
29
+ import { _generateCodeFromCommand } from "../code_gen/playwright_codeget.js";
30
+ import { Recording } from "../recording.js";
20
31
 
21
32
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
22
33
 
@@ -361,8 +372,7 @@ async function findNestedFrameSelector(frame, obj) {
361
372
  }, frameElement);
362
373
  return findNestedFrameSelector(parent, { children: obj, selectors });
363
374
  } catch (e) {
364
- socketLogger.error(`Error in findNestedFrameSelector: ${e}`);
365
- console.error(e);
375
+ socketLogger.error(`Error in script evaluation: ${getErrorMessage(e)}`, undefined, "findNestedFrameSelector");
366
376
  }
367
377
  }
368
378
  const transformFillAction = (action, el) => {
@@ -507,7 +517,7 @@ export class BVTRecorder {
507
517
  this.workspaceService = new PublishService(this.TOKEN);
508
518
  this.pageSet = new Set();
509
519
  this.lastKnownUrlPath = "";
510
- this.world = { attach: () => { } };
520
+ this.world = { attach: () => {} };
511
521
  this.shouldTakeScreenshot = true;
512
522
  this.watcher = null;
513
523
  this.networkEventsFolder = path.join(tmpdir(), "blinq_network_events");
@@ -650,7 +660,7 @@ export class BVTRecorder {
650
660
  }
651
661
 
652
662
  // this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
653
- this.world = { attach: () => { } };
663
+ this.world = { attach: () => {} };
654
664
 
655
665
  const ai_config_file = path.join(this.projectDir, "ai_config.json");
656
666
  let ai_config = {};
@@ -702,17 +712,6 @@ export class BVTRecorder {
702
712
  this.web.tryAllStrategies = true;
703
713
  this.page = bvtContext.page;
704
714
  this.pageSet.add(this.page);
705
- if (process.env.REMOTE_RECORDER === "true") {
706
- this.browserEmitter = new RemoteBrowserService({
707
- CDP_CONNECT_URL: `http://localhost:${this.#remoteDebuggerPort}`,
708
- context: this.context,
709
- });
710
- this.browserEmitter.on(this.events.browserStateSync, (state) => {
711
- this.page = this.browserEmitter.getSelectedPage();
712
- this.sendEvent(this.events.browserStateSync, state);
713
- });
714
- }
715
-
716
715
  this.lastKnownUrlPath = this._updateUrlPath();
717
716
  const browser = await this.context.browser();
718
717
  this.browser = browser;
@@ -722,8 +721,10 @@ export class BVTRecorder {
722
721
  await this.context.exposeBinding(name, handler);
723
722
  }
724
723
  this._watchTestData();
725
- this.web.onRestoreSaveState = (url) => {
726
- this._initBrowser({ url });
724
+ this.web.onRestoreSaveState = async (url) => {
725
+ await this._initBrowser({ url });
726
+ this._addPagelisteners(this.context);
727
+ this._addFrameNavigateListener(this.page);
727
728
  };
728
729
 
729
730
  // create a second browser for locator generation
@@ -1154,7 +1155,7 @@ export class BVTRecorder {
1154
1155
  }
1155
1156
  async closeBrowser() {
1156
1157
  delete process.env.TEMP_RUN;
1157
- await this.watcher.close().then(() => { });
1158
+ await this.watcher.close().then(() => {});
1158
1159
  this.watcher = null;
1159
1160
  this.previousIndex = null;
1160
1161
  this.previousHistoryLength = null;
@@ -1252,7 +1253,7 @@ export class BVTRecorder {
1252
1253
  }, 100);
1253
1254
  this.timerId = timerId;
1254
1255
  }
1255
- async runStep({ step, parametersMap, tags, isFirstStep, listenNetwork }, options) {
1256
+ async runStep({ step, parametersMap, tags, isFirstStep, listenNetwork, AICode }, options) {
1256
1257
  const { skipAfter = true, skipBefore = !isFirstStep } = options || {};
1257
1258
 
1258
1259
  const env = path.basename(this.envName, ".json");
@@ -1294,6 +1295,7 @@ export class BVTRecorder {
1294
1295
  envPath: this.envName,
1295
1296
  tags,
1296
1297
  config: this.config,
1298
+ AICode,
1297
1299
  },
1298
1300
  this.bvtContext,
1299
1301
  {
@@ -1313,9 +1315,7 @@ export class BVTRecorder {
1313
1315
  this.bvtContext.navigate = false;
1314
1316
  }
1315
1317
  }
1316
- async saveScenario({ scenario, featureName, override, isSingleStep, branch, isEditing, env }) {
1317
- // await updateStepDefinitions({ scenario, featureName, projectDir: this.projectDir }); // updates mjs files
1318
- // if (!isSingleStep) await updateFeatureFile({ featureName, scenario, override, projectDir: this.projectDir }); // updates gherkin files
1318
+ async saveScenario({ scenario, featureName, override, isSingleStep, branch, isEditing, env, AICode }) {
1319
1319
  const res = await this.workspaceService.saveScenario({
1320
1320
  scenario,
1321
1321
  featureName,
@@ -1325,6 +1325,7 @@ export class BVTRecorder {
1325
1325
  isEditing,
1326
1326
  projectId: path.basename(this.projectDir),
1327
1327
  env: env ?? this.envName,
1328
+ AICode,
1328
1329
  });
1329
1330
  if (res.success) {
1330
1331
  await this.cleanup({ tags: scenario.tags });
@@ -1499,7 +1500,7 @@ export class BVTRecorder {
1499
1500
 
1500
1501
  const steps = [];
1501
1502
  const parameters = [];
1502
- const datasets = []
1503
+ const datasets = [];
1503
1504
  if (scenario.examples && scenario.examples.length > 0) {
1504
1505
  const example = scenario.examples[0];
1505
1506
  example?.tableHeader?.cells.forEach((cell, index) => {
@@ -1507,7 +1508,7 @@ export class BVTRecorder {
1507
1508
  key: cell.value,
1508
1509
  value: unEscapeNonPrintables(example.tableBody[0].cells[index].value),
1509
1510
  });
1510
- // datasets.push({
1511
+ // datasets.push({
1511
1512
  // data: example.tableBody[]
1512
1513
  // })
1513
1514
  });
@@ -1580,6 +1581,266 @@ export class BVTRecorder {
1580
1581
  }
1581
1582
  return result;
1582
1583
  }
1584
+ async setStepCodeByScenario({
1585
+ function_name,
1586
+ mjs_file_content,
1587
+ user_request,
1588
+ selectedTarget,
1589
+ page_context,
1590
+ AIMemory,
1591
+ }) {
1592
+ const runsURL = getRunsServiceBaseURL();
1593
+ const url = `${runsURL}/process-user-request/generate-code-with-context`;
1594
+ try {
1595
+ const result = await axiosClient({
1596
+ url,
1597
+ method: "POST",
1598
+ data: {
1599
+ function_name,
1600
+ mjs_file_content,
1601
+ user_request,
1602
+ selectedTarget,
1603
+ page_context,
1604
+ AIMemory,
1605
+ },
1606
+ headers: {
1607
+ Authorization: `Bearer ${this.TOKEN}`,
1608
+ "X-Source": "recorder",
1609
+ },
1610
+ });
1611
+ if (result.status !== 200) {
1612
+ return { success: false, message: "Error while fetching code changes" };
1613
+ }
1614
+ return { success: true, data: result.data };
1615
+ } catch (error) {
1616
+ // @ts-ignore
1617
+ const reason = error?.response?.data?.error || "";
1618
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1619
+ throw new Error(`Failed to fetch code changes: ${errorMessage} \n ${reason}`);
1620
+ }
1621
+ }
1622
+
1623
+ async getStepCodeByScenario({ featureName, scenarioName, projectId, branch }) {
1624
+ try {
1625
+ const runsURL = getRunsServiceBaseURL();
1626
+ const ssoURL = runsURL.replace("/runs", "/auth");
1627
+ const privateRepoURL = `${ssoURL}/isRepoPrivate?project_id=${projectId}`;
1628
+
1629
+ const isPrivateRepoReq = await axiosClient({
1630
+ url: privateRepoURL,
1631
+ method: "GET",
1632
+ headers: {
1633
+ Authorization: `Bearer ${this.TOKEN}`,
1634
+ "X-Source": "recorder",
1635
+ },
1636
+ });
1637
+
1638
+ if (isPrivateRepoReq.status !== 200) {
1639
+ return { success: false, message: "Error while checking repo privacy" };
1640
+ }
1641
+
1642
+ const isPrivateRepo = isPrivateRepoReq.data.isPrivate ? isPrivateRepoReq.data.isPrivate : false;
1643
+
1644
+ const workspaceURL = runsURL.replace("/runs", "/workspace");
1645
+ const url = `${workspaceURL}/get-step-code-by-scenario`;
1646
+
1647
+ const result = await axiosClient({
1648
+ url,
1649
+ method: "POST",
1650
+ data: {
1651
+ scenarioName,
1652
+ featureName,
1653
+ projectId,
1654
+ isPrivateRepo,
1655
+ branch,
1656
+ },
1657
+ headers: {
1658
+ Authorization: `Bearer ${this.TOKEN}`,
1659
+ "X-Source": "recorder",
1660
+ },
1661
+ });
1662
+ if (result.status !== 200) {
1663
+ return { success: false, message: "Error while getting step code" };
1664
+ }
1665
+ return { success: true, data: result.data.stepInfo };
1666
+ } catch (error) {
1667
+ const reason = error?.response?.data?.error || "";
1668
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1669
+ throw new Error(`Failed to get step code: ${errorMessage} \n ${reason}`);
1670
+ }
1671
+ }
1672
+ async getContext() {
1673
+ return await this.page.evaluate(() => {
1674
+ return document.documentElement.outerHTML;
1675
+ });
1676
+ }
1677
+ async deleteCommandFromStepCode({ scenario, AICode, command }) {
1678
+ if (!AICode || AICode.length === 0) {
1679
+ console.log("No AI code available to delete.");
1680
+ return;
1681
+ }
1682
+
1683
+ const __temp_features_FolderName = "__temp_features" + Math.random().toString(36).substring(2, 7);
1684
+ const tempFolderPath = path.join(this.projectDir, __temp_features_FolderName);
1685
+ process.env.tempFeaturesFolderPath = __temp_features_FolderName;
1686
+ process.env.TESTCASE_REPORT_FOLDER_PATH = tempFolderPath;
1687
+
1688
+ try {
1689
+ await this.stepRunner.copyCodetoTempFolder({ tempFolderPath, AICode });
1690
+ await this.stepRunner.writeWrapperCode(tempFolderPath);
1691
+ const codeView = AICode.find((f) => f.stepName === scenario.step.text);
1692
+
1693
+ if (!codeView) {
1694
+ throw new Error("Step code not found for step: " + scenario.step.text);
1695
+ }
1696
+
1697
+ const functionName = codeView.functionName;
1698
+ const mjsPath = path
1699
+ .normalize(codeView.mjsFile)
1700
+ .split(path.sep)
1701
+ .filter((part) => part !== "features")
1702
+ .join(path.sep);
1703
+ const codePath = path.join(tempFolderPath, mjsPath);
1704
+
1705
+ if (!existsSync(codePath)) {
1706
+ throw new Error("Step code file not found: " + codePath);
1707
+ }
1708
+
1709
+ const codePage = getCodePage(codePath);
1710
+
1711
+ const elements = codePage.getVariableDeclarationAsObject("elements");
1712
+
1713
+ const cucumberStep = getCucumberStep({ step: scenario.step });
1714
+ cucumberStep.text = scenario.step.text;
1715
+ const stepCommands = scenario.step.commands;
1716
+ const cmd = _toRecordingStep(command, scenario.step.name);
1717
+
1718
+ const recording = new Recording();
1719
+ recording.loadFromObject({ steps: stepCommands, step: cucumberStep });
1720
+ const step = { ...recording.steps[0], ...cmd };
1721
+ const result = _generateCodeFromCommand(step, elements, {});
1722
+
1723
+ codePage._removeCommands(functionName, result.codeLines);
1724
+ codePage.removeUnusedElements();
1725
+ codePage.save();
1726
+
1727
+ await rm(tempFolderPath, { recursive: true, force: true });
1728
+
1729
+ return { code: codePage.fileContent, mjsFile: codeView.mjsFile };
1730
+ } catch (error) {
1731
+ await rm(tempFolderPath, { recursive: true, force: true });
1732
+ throw error;
1733
+ }
1734
+ }
1735
+ async addCommandToStepCode({ scenario, AICode }) {
1736
+ if (!AICode || AICode.length === 0) {
1737
+ console.log("No AI code available to add.");
1738
+ return;
1739
+ }
1740
+
1741
+ const __temp_features_FolderName = "__temp_features" + Math.random().toString(36).substring(2, 7);
1742
+ const tempFolderPath = path.join(this.projectDir, __temp_features_FolderName);
1743
+ process.env.tempFeaturesFolderPath = __temp_features_FolderName;
1744
+ process.env.TESTCASE_REPORT_FOLDER_PATH = tempFolderPath;
1745
+
1746
+ try {
1747
+ await this.stepRunner.copyCodetoTempFolder({ tempFolderPath, AICode });
1748
+ await this.stepRunner.writeWrapperCode(tempFolderPath);
1749
+
1750
+ let codeView = AICode.find((f) => f.stepName === scenario.step.text);
1751
+
1752
+ if (codeView) {
1753
+ scenario.step.commands = [scenario.step.commands.pop()];
1754
+ const functionName = codeView.functionName;
1755
+ const mjsPath = path
1756
+ .normalize(codeView.mjsFile)
1757
+ .split(path.sep)
1758
+ .filter((part) => part !== "features")
1759
+ .join(path.sep);
1760
+ const codePath = path.join(tempFolderPath, mjsPath);
1761
+
1762
+ if (!existsSync(codePath)) {
1763
+ throw new Error("Step code file not found: " + codePath);
1764
+ }
1765
+
1766
+ const codePage = getCodePage(codePath);
1767
+ const elements = codePage.getVariableDeclarationAsObject("elements");
1768
+
1769
+ const cucumberStep = getCucumberStep({ step: scenario.step });
1770
+ cucumberStep.text = scenario.step.text;
1771
+ const stepCommands = scenario.step.commands;
1772
+ const cmd = _toRecordingStep(scenario.step.commands[0], scenario.step.name);
1773
+
1774
+ const recording = new Recording();
1775
+ recording.loadFromObject({ steps: stepCommands, step: cucumberStep });
1776
+ const step = { ...recording.steps[0], ...cmd };
1777
+
1778
+ const result = _generateCodeFromCommand(step, elements, {});
1779
+ codePage.insertElements(result.elements);
1780
+
1781
+ codePage._injectOneCommand(functionName, result.codeLines.join("\n"));
1782
+ codePage.save();
1783
+
1784
+ await rm(tempFolderPath, { recursive: true, force: true });
1785
+
1786
+ return { code: codePage.fileContent, newStep: false, mjsFile: codeView.mjsFile };
1787
+ }
1788
+ console.log("Step code not found for step: ", scenario.step.text);
1789
+
1790
+ codeView = AICode[0];
1791
+ const functionName = toMethodName(scenario.step.text);
1792
+ const codeLines = [];
1793
+ const mjsPath = path
1794
+ .normalize(codeView.mjsFile)
1795
+ .split(path.sep)
1796
+ .filter((part) => part !== "features")
1797
+ .join(path.sep);
1798
+ const codePath = path.join(tempFolderPath, mjsPath);
1799
+
1800
+ if (!existsSync(codePath)) {
1801
+ throw new Error("Step code file not found: " + codePath);
1802
+ }
1803
+
1804
+ const codePage = getCodePage(codePath);
1805
+ const elements = codePage.getVariableDeclarationAsObject("elements");
1806
+ let newElements = { ...elements };
1807
+
1808
+ const cucumberStep = getCucumberStep({ step: scenario.step });
1809
+ cucumberStep.text = scenario.step.text;
1810
+ const stepCommands = scenario.step.commands;
1811
+ stepCommands.forEach((command) => {
1812
+ const cmd = _toRecordingStep(command, scenario.step.name);
1813
+
1814
+ const recording = new Recording();
1815
+ recording.loadFromObject({ steps: stepCommands, step: cucumberStep });
1816
+ const step = { ...recording.steps[0], ...cmd };
1817
+ const result = _generateCodeFromCommand(step, elements, {});
1818
+ newElements = { ...result.elements };
1819
+ codeLines.push(...result.codeLines);
1820
+ });
1821
+
1822
+ codePage.insertElements(newElements);
1823
+ codePage.addInfraCommand(
1824
+ functionName,
1825
+ cucumberStep.text,
1826
+ cucumberStep.getVariablesList(),
1827
+ codeLines,
1828
+ false,
1829
+ "recorder"
1830
+ );
1831
+
1832
+ const keyword = (cucumberStep.keywordAlias ?? cucumberStep.keyword).trim();
1833
+ codePage.addCucumberStep(keyword, cucumberStep.getTemplate(), functionName, stepCommands.length);
1834
+ codePage.save();
1835
+
1836
+ await rm(tempFolderPath, { recursive: true, force: true });
1837
+
1838
+ return { code: codePage.fileContent, newStep: true, functionName, mjsFile: codeView.mjsFile };
1839
+ } catch (error) {
1840
+ await rm(tempFolderPath, { recursive: true, force: true });
1841
+ throw error;
1842
+ }
1843
+ }
1583
1844
  async cleanup({ tags }) {
1584
1845
  const noopStep = {
1585
1846
  text: "Noop",
@@ -1951,7 +2212,7 @@ export class BVTRecorder {
1951
2212
  await page
1952
2213
  .context()
1953
2214
  .grantPermissions(["clipboard-read", "clipboard-write"])
1954
- .catch(() => { });
2215
+ .catch(() => {});
1955
2216
  await page.evaluate(async (clipboardPayload) => {
1956
2217
  console.log("Injecting clipboard payload", clipboardPayload);
1957
2218
  const toArrayBuffer = (base64) => {
@@ -153,7 +153,7 @@ export class PublishService {
153
153
  constructor(TOKEN) {
154
154
  this.TOKEN = TOKEN;
155
155
  }
156
- async saveScenario({ scenario, featureName, override, branch, isEditing, projectId, env }) {
156
+ async saveScenario({ scenario, featureName, override, branch, isEditing, projectId, env, AICode }) {
157
157
  const runsURL = getRunsServiceBaseURL();
158
158
  const workspaceURL = runsURL.replace("/runs", "/workspace");
159
159
  const url = `${workspaceURL}/publish-recording`;
@@ -166,6 +166,7 @@ export class PublishService {
166
166
  featureName,
167
167
  override,
168
168
  env,
169
+ AICode,
169
170
  },
170
171
  params: {
171
172
  branch,
@@ -67,13 +67,12 @@ export class BVTStepRunner {
67
67
  resolve();
68
68
  }
69
69
  } else {
70
- console.warn(`No paused command found for cmdId: ${cmdId}`);
71
- socketLogger.error(`No paused command found for cmdId: ${cmdId}`);
70
+ socketLogger.error(`No paused command found for cmdId: ${cmdId}`, undefined, "BVTStepRunner.resumeExecution");
72
71
  }
73
72
  }
74
73
  }
75
74
 
76
- async copyCodetoTempFolder({ step, parametersMap, tempFolderPath }) {
75
+ async copyCodetoTempFolder({ step, parametersMap, tempFolderPath, AICode }) {
77
76
  if (!fs.existsSync(tempFolderPath)) {
78
77
  fs.mkdirSync(tempFolderPath);
79
78
  }
@@ -84,6 +83,18 @@ export class BVTStepRunner {
84
83
  overwrite: true,
85
84
  recursive: true,
86
85
  });
86
+
87
+ // If AICode is provided, save it as well
88
+ if (AICode) {
89
+ for (const { mjsFileContent, mjsFile } of AICode) {
90
+ const mjsPath = path
91
+ .normalize(mjsFile)
92
+ .split(path.sep)
93
+ .filter((part) => part !== "features")
94
+ .join(path.sep);
95
+ writeFileSync(path.join(tempFolderPath, mjsPath), mjsFileContent);
96
+ }
97
+ }
87
98
  }
88
99
 
89
100
  async writeTempFeatureFile({ step, parametersMap, tempFolderPath, tags }) {
@@ -250,7 +261,7 @@ export class BVTStepRunner {
250
261
  return { result, info };
251
262
  }
252
263
 
253
- async runStep({ step, parametersMap, envPath, tags, config }, bvtContext, options) {
264
+ async runStep({ step, parametersMap, envPath, tags, config, AICode }, bvtContext, options) {
254
265
  // Create a new AbortController for this specific step execution
255
266
  this.#currentStepController = new AbortController();
256
267
  const { signal } = this.#currentStepController;
@@ -304,7 +315,7 @@ export class BVTStepRunner {
304
315
  process.env.tempFeaturesFolderPath = __temp_features_FolderName;
305
316
  process.env.TESTCASE_REPORT_FOLDER_PATH = tempFolderPath;
306
317
 
307
- await this.copyCodetoTempFolder({ step, parametersMap, tempFolderPath });
318
+ await this.copyCodetoTempFolder({ step, parametersMap, tempFolderPath, AICode });
308
319
 
309
320
  // Write abort wrapper code with this step's signal
310
321
  await this.writeWrapperCode(tempFolderPath, signal);
@@ -335,6 +346,7 @@ export class BVTStepRunner {
335
346
  projectDir: this.projectDir,
336
347
  stepsDefinitions,
337
348
  parametersMap,
349
+ logger: socketLogger,
338
350
  });
339
351
  if (codePage) {
340
352
  await codePage.save(stepDefsFilePath);