@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.
- package/bin/client/code_cleanup/utils.js +16 -7
- package/bin/client/code_gen/duplication_analysis.js +2 -1
- package/bin/client/code_gen/page_reflection.js +40 -0
- package/bin/client/code_gen/playwright_codeget.js +19 -10
- package/bin/client/cucumber/feature.js +4 -17
- package/bin/client/recorderv3/bvt_init.js +40 -83
- package/bin/client/recorderv3/bvt_recorder.js +289 -28
- package/bin/client/recorderv3/services.js +2 -1
- package/bin/client/recorderv3/step_runner.js +17 -5
- package/bin/client/recorderv3/step_utils.js +31 -38
- package/bin/client/recorderv3/update_feature.js +45 -28
- package/bin/client/utils/app_dir.js +21 -0
- package/bin/client/utils/socket_logger.js +38 -14
- package/package.json +6 -2
- package/bin/client/recorderv3/app_dir.js +0 -23
- package/bin/client/recorderv3/network.js +0 -299
- package/bin/client/recorderv3/scriptTest.js +0 -5
- package/bin/client/recorderv3/ws_server.js +0 -72
|
@@ -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 {
|
|
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
|
|
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
|
-
|
|
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);
|