@playq/core 0.2.77
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/README.md +41 -0
- package/bin/playq.js +175 -0
- package/cucumber.js +10 -0
- package/dist/exec/featureFileCache.d.ts +21 -0
- package/dist/exec/featureFileCache.js +124 -0
- package/dist/exec/featureFilePreProcess.d.ts +12 -0
- package/dist/exec/featureFilePreProcess.js +208 -0
- package/dist/exec/preLoader.d.ts +1 -0
- package/dist/exec/preLoader.js +72 -0
- package/dist/exec/preProcessEntry.d.ts +1 -0
- package/dist/exec/preProcessEntry.js +83 -0
- package/dist/exec/preProcess_old_todelete.d.ts +1 -0
- package/dist/exec/preProcess_old_todelete.js +258 -0
- package/dist/exec/runner.d.ts +1 -0
- package/dist/exec/runner.js +221 -0
- package/dist/exec/runner_orchestrator.d.ts +1 -0
- package/dist/exec/runner_orchestrator.js +85 -0
- package/dist/exec/sgGenerator.d.ts +11 -0
- package/dist/exec/sgGenerator.js +310 -0
- package/dist/global.d.ts +15 -0
- package/dist/global.js +185 -0
- package/dist/helper/actions/api/apiRequestActions.d.ts +117 -0
- package/dist/helper/actions/api/apiRequestActions.js +374 -0
- package/dist/helper/actions/api/apiValidationActions.d.ts +119 -0
- package/dist/helper/actions/api/apiValidationActions.js +615 -0
- package/dist/helper/actions/apiActions.d.ts +18 -0
- package/dist/helper/actions/apiActions.js +34 -0
- package/dist/helper/actions/apiStepDefs.d.ts +1 -0
- package/dist/helper/actions/apiStepDefs.js +64 -0
- package/dist/helper/actions/comm/commonActions.d.ts +58 -0
- package/dist/helper/actions/comm/commonActions.js +198 -0
- package/dist/helper/actions/comm/utilityActions.d.ts +131 -0
- package/dist/helper/actions/comm/utilityActions.js +351 -0
- package/dist/helper/actions/commActions.d.ts +18 -0
- package/dist/helper/actions/commActions.js +34 -0
- package/dist/helper/actions/commStepDefs.d.ts +1 -0
- package/dist/helper/actions/commStepDefs.js +57 -0
- package/dist/helper/actions/stepGroupStepDefs.d.ts +1 -0
- package/dist/helper/actions/stepGroupStepDefs.js +15 -0
- package/dist/helper/actions/web/alertActions.d.ts +61 -0
- package/dist/helper/actions/web/alertActions.js +224 -0
- package/dist/helper/actions/web/cookieActions.d.ts +45 -0
- package/dist/helper/actions/web/cookieActions.js +186 -0
- package/dist/helper/actions/web/downloadActions.d.ts +40 -0
- package/dist/helper/actions/web/downloadActions.js +153 -0
- package/dist/helper/actions/web/elementReaderActions.d.ts +95 -0
- package/dist/helper/actions/web/elementReaderActions.js +326 -0
- package/dist/helper/actions/web/formActions.d.ts +122 -0
- package/dist/helper/actions/web/formActions.js +423 -0
- package/dist/helper/actions/web/iframeActions.d.ts +23 -0
- package/dist/helper/actions/web/iframeActions.js +108 -0
- package/dist/helper/actions/web/javascriptActions.d.ts +14 -0
- package/dist/helper/actions/web/javascriptActions.js +77 -0
- package/dist/helper/actions/web/keyboardActions.d.ts +35 -0
- package/dist/helper/actions/web/keyboardActions.js +118 -0
- package/dist/helper/actions/web/localStorageActions.d.ts +51 -0
- package/dist/helper/actions/web/localStorageActions.js +163 -0
- package/dist/helper/actions/web/mouseActions.d.ts +240 -0
- package/dist/helper/actions/web/mouseActions.js +609 -0
- package/dist/helper/actions/web/reportingActions.d.ts +34 -0
- package/dist/helper/actions/web/reportingActions.js +58 -0
- package/dist/helper/actions/web/screenshotActions.d.ts +34 -0
- package/dist/helper/actions/web/screenshotActions.js +151 -0
- package/dist/helper/actions/web/testDataActions.d.ts +21 -0
- package/dist/helper/actions/web/testDataActions.js +211 -0
- package/dist/helper/actions/web/validationActions.d.ts +547 -0
- package/dist/helper/actions/web/validationActions.js +1754 -0
- package/dist/helper/actions/web/waitActions.d.ts +191 -0
- package/dist/helper/actions/web/waitActions.js +589 -0
- package/dist/helper/actions/web/webNavigation.d.ts +104 -0
- package/dist/helper/actions/web/webNavigation.js +288 -0
- package/dist/helper/actions/webActions.d.ts +32 -0
- package/dist/helper/actions/webActions.js +48 -0
- package/dist/helper/actions/webStepDefs.d.ts +1 -0
- package/dist/helper/actions/webStepDefs.js +455 -0
- package/dist/helper/browsers/browserManager.d.ts +1 -0
- package/dist/helper/browsers/browserManager.js +56 -0
- package/dist/helper/bundle/defaultEntries.d.ts +6 -0
- package/dist/helper/bundle/defaultEntries.js +200 -0
- package/dist/helper/bundle/env.d.ts +1 -0
- package/dist/helper/bundle/env.js +157 -0
- package/dist/helper/bundle/vars.d.ts +9 -0
- package/dist/helper/bundle/vars.js +375 -0
- package/dist/helper/faker/customFaker.d.ts +55 -0
- package/dist/helper/faker/customFaker.js +45 -0
- package/dist/helper/faker/modules/data/postcodes_valid_sg.json +17 -0
- package/dist/helper/faker/modules/dateTime.d.ts +18 -0
- package/dist/helper/faker/modules/dateTime.js +106 -0
- package/dist/helper/faker/modules/mobile.d.ts +4 -0
- package/dist/helper/faker/modules/mobile.js +59 -0
- package/dist/helper/faker/modules/nric.d.ts +32 -0
- package/dist/helper/faker/modules/nric.js +84 -0
- package/dist/helper/faker/modules/passport.d.ts +3 -0
- package/dist/helper/faker/modules/passport.js +36 -0
- package/dist/helper/faker/modules/person.d.ts +14 -0
- package/dist/helper/faker/modules/person.js +73 -0
- package/dist/helper/faker/modules/postcode.d.ts +6 -0
- package/dist/helper/faker/modules/postcode.js +47 -0
- package/dist/helper/fixtures/locAggregate.d.ts +7 -0
- package/dist/helper/fixtures/locAggregate.js +94 -0
- package/dist/helper/fixtures/logFixture.d.ts +8 -0
- package/dist/helper/fixtures/logFixture.js +56 -0
- package/dist/helper/fixtures/webFixture.d.ts +19 -0
- package/dist/helper/fixtures/webFixture.js +186 -0
- package/dist/helper/fixtures/webLocFixture.d.ts +2 -0
- package/dist/helper/fixtures/webLocFixture.js +144 -0
- package/dist/helper/report/allureStepLogger.d.ts +0 -0
- package/dist/helper/report/allureStepLogger.js +25 -0
- package/dist/helper/report/customiseReport.d.ts +1 -0
- package/dist/helper/report/customiseReport.js +55 -0
- package/dist/helper/report/init.d.ts +1 -0
- package/dist/helper/report/init.js +14 -0
- package/dist/helper/report/report.d.ts +1 -0
- package/dist/helper/report/report.js +102 -0
- package/dist/helper/util/dataLoader.d.ts +10 -0
- package/dist/helper/util/dataLoader.js +73 -0
- package/dist/helper/util/logger.d.ts +4 -0
- package/dist/helper/util/logger.js +61 -0
- package/dist/helper/util/session/sessionUtil.d.ts +26 -0
- package/dist/helper/util/session/sessionUtil.js +729 -0
- package/dist/helper/util/stepHelpers.d.ts +2 -0
- package/dist/helper/util/stepHelpers.js +16 -0
- package/dist/helper/util/test-data/dataLoader.d.ts +7 -0
- package/dist/helper/util/test-data/dataLoader.js +145 -0
- package/dist/helper/util/test-data/dataTest.d.ts +10 -0
- package/dist/helper/util/test-data/dataTest.js +216 -0
- package/dist/helper/util/totp/totpHelper.d.ts +38 -0
- package/dist/helper/util/totp/totpHelper.js +117 -0
- package/dist/helper/util/utilities/cryptoUtil.d.ts +2 -0
- package/dist/helper/util/utilities/cryptoUtil.js +53 -0
- package/dist/helper/util/utilities/schemaGeneratorUtil.d.ts +2 -0
- package/dist/helper/util/utilities/schemaGeneratorUtil.js +129 -0
- package/dist/helper/util/utils.d.ts +2 -0
- package/dist/helper/util/utils.js +22 -0
- package/dist/helper/wrapper/PlaywrightWrappers.d.ts +8 -0
- package/dist/helper/wrapper/PlaywrightWrappers.js +26 -0
- package/dist/helper/wrapper/assert.d.ts +9 -0
- package/dist/helper/wrapper/assert.js +23 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +57 -0
- package/dist/scripts/get-versions.d.ts +1 -0
- package/dist/scripts/get-versions.js +98 -0
- package/dist/scripts/posttest.d.ts +1 -0
- package/dist/scripts/posttest.js +29 -0
- package/dist/scripts/pretest.d.ts +1 -0
- package/dist/scripts/pretest.js +57 -0
- package/dist/scripts/util.d.ts +1 -0
- package/dist/scripts/util.js +376 -0
- package/package.json +68 -0
- package/src/exec/featureFileCache.ts +80 -0
- package/src/exec/featureFilePreProcess.ts +239 -0
- package/src/exec/preLoader.ts +72 -0
- package/src/exec/preProcessEntry.ts +59 -0
- package/src/exec/preProcess_old_todelete.ts +289 -0
- package/src/exec/runner.ts +241 -0
- package/src/exec/runnerCuke.js +90 -0
- package/src/exec/runner_orchestrator.ts +91 -0
- package/src/exec/sgGenerator.ts +373 -0
- package/src/global.ts +130 -0
- package/src/helper/actions/api/apiRequestActions.ts +362 -0
- package/src/helper/actions/api/apiValidationActions.ts +594 -0
- package/src/helper/actions/apiActions.ts +18 -0
- package/src/helper/actions/apiStepDefs.ts +80 -0
- package/src/helper/actions/comm/commonActions.ts +165 -0
- package/src/helper/actions/comm/utilityActions.ts +344 -0
- package/src/helper/actions/commActions.ts +18 -0
- package/src/helper/actions/commStepDefs.ts +72 -0
- package/src/helper/actions/stepGroupStepDefs.ts +17 -0
- package/src/helper/actions/web/alertActions.ts +179 -0
- package/src/helper/actions/web/cookieActions.ts +124 -0
- package/src/helper/actions/web/downloadActions.ts +129 -0
- package/src/helper/actions/web/elementReaderActions.ts +323 -0
- package/src/helper/actions/web/formActions.ts +469 -0
- package/src/helper/actions/web/iframeActions.ts +67 -0
- package/src/helper/actions/web/javascriptActions.ts +38 -0
- package/src/helper/actions/web/keyboardActions.ts +101 -0
- package/src/helper/actions/web/localStorageActions.ts +109 -0
- package/src/helper/actions/web/mouseActions.ts +864 -0
- package/src/helper/actions/web/reportingActions.ts +53 -0
- package/src/helper/actions/web/screenshotActions.ts +124 -0
- package/src/helper/actions/web/testDataActions.ts +162 -0
- package/src/helper/actions/web/validationActions.ts +2287 -0
- package/src/helper/actions/web/waitActions.ts +757 -0
- package/src/helper/actions/web/webNavigation.ts +313 -0
- package/src/helper/actions/webActions.ts +33 -0
- package/src/helper/actions/webStepDefs.ts +505 -0
- package/src/helper/browsers/browserManager.ts +23 -0
- package/src/helper/bundle/defaultEntries.ts +208 -0
- package/src/helper/bundle/env.ts +119 -0
- package/src/helper/bundle/vars.ts +368 -0
- package/src/helper/faker/customFaker.ts +107 -0
- package/src/helper/faker/modules/data/postcodes_valid_sg.json +17 -0
- package/src/helper/faker/modules/dateTime.ts +121 -0
- package/src/helper/faker/modules/mobile.ts +58 -0
- package/src/helper/faker/modules/nric.ts +109 -0
- package/src/helper/faker/modules/passport.ts +34 -0
- package/src/helper/faker/modules/person.ts +93 -0
- package/src/helper/faker/modules/postcode.ts +57 -0
- package/src/helper/fixtures/locAggregate.ts +61 -0
- package/src/helper/fixtures/logFixture.ts +57 -0
- package/src/helper/fixtures/webFixture.ts +206 -0
- package/src/helper/fixtures/webLocFixture.ts +143 -0
- package/src/helper/report/allureStepLogger.ts +26 -0
- package/src/helper/report/customiseReport.ts +61 -0
- package/src/helper/report/init.ts +18 -0
- package/src/helper/report/report.ts +72 -0
- package/src/helper/util/dataLoader.ts +42 -0
- package/src/helper/util/logger.ts +32 -0
- package/src/helper/util/session/sessionUtil.ts +839 -0
- package/src/helper/util/stepHelpers.ts +14 -0
- package/src/helper/util/test-data/dataLoader.ts +108 -0
- package/src/helper/util/test-data/dataTest.ts +191 -0
- package/src/helper/util/test-data/registerUser.json +7 -0
- package/src/helper/util/totp/totpHelper.ts +102 -0
- package/src/helper/util/utilities/cryptoUtil.ts +53 -0
- package/src/helper/util/utilities/schemaGeneratorUtil.ts +143 -0
- package/src/helper/util/utils.ts +28 -0
- package/src/helper/wrapper/PlaywrightWrappers.ts +28 -0
- package/src/helper/wrapper/assert.ts +25 -0
- package/src/index.ts +17 -0
- package/src/scripts/get-versions.ts +68 -0
- package/src/scripts/posttest.ts +32 -0
- package/src/scripts/pretest.ts +48 -0
- package/src/scripts/util.ts +406 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// Replace legacy '@playq' alias with local global exports
|
|
2
|
+
import { config, vars } from "../global";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import xlsx from "@e965/xlsx";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Preprocesses a feature file using all transformation steps:
|
|
9
|
+
* - Variable replacement
|
|
10
|
+
* - Step group expansion
|
|
11
|
+
* - SmartIQ data injection
|
|
12
|
+
* - Tag injection
|
|
13
|
+
*
|
|
14
|
+
* @param srcFeaturePath Path to the source .feature file
|
|
15
|
+
* @returns Path to the preprocessed file (under _Temp/execution)
|
|
16
|
+
*/
|
|
17
|
+
export function preprocessFeatureFile(
|
|
18
|
+
srcFeaturePath: string
|
|
19
|
+
): string | undefined {
|
|
20
|
+
try {
|
|
21
|
+
const rawContent = fs.readFileSync(srcFeaturePath, "utf-8");
|
|
22
|
+
|
|
23
|
+
// Step 1: Replace variables like ${url}, ${user}
|
|
24
|
+
let processedContent = replaceVariablesInString(rawContent);
|
|
25
|
+
|
|
26
|
+
// Step 2: Replacing examples with data file and filter
|
|
27
|
+
processedContent = processExamplesWithFilter(processedContent); // <-- Add this
|
|
28
|
+
|
|
29
|
+
// Step 3: Expand Step Groups
|
|
30
|
+
processedContent = expandStepGroups(processedContent);
|
|
31
|
+
|
|
32
|
+
// Step 4: Inject SmartIQ data or resolve special steps
|
|
33
|
+
processedContent = processSmartData(processedContent);
|
|
34
|
+
|
|
35
|
+
// Step 5: Inject scenario-level tags if needed
|
|
36
|
+
processedContent = injectScenarioTag(processedContent, srcFeaturePath);
|
|
37
|
+
|
|
38
|
+
// Write processed file
|
|
39
|
+
const outputDir = path.join("_Temp/execution");
|
|
40
|
+
const relativePath = path.relative("tests/bdd/scenarios", srcFeaturePath);
|
|
41
|
+
const outputPath = path.join(outputDir, relativePath);
|
|
42
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
43
|
+
fs.writeFileSync(outputPath, processedContent, "utf-8");
|
|
44
|
+
if (config?.cucumber?.featureFileCache || vars.getConfigValue('cucumber.featureFileCache')) {
|
|
45
|
+
const { updateFeatureCacheMeta } = require("./featureFileCache");
|
|
46
|
+
updateFeatureCacheMeta(srcFeaturePath, outputPath);
|
|
47
|
+
}
|
|
48
|
+
return outputPath;
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.error(`❌ Error preprocessing feature file: ${err}`);
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 🔁 Placeholder replacement
|
|
56
|
+
function replaceVariablesInString(content: string): string {
|
|
57
|
+
return content.replace(/Examples\s*:\s*({[^}]*})/g, (match, jsonPart) => {
|
|
58
|
+
const replaced = jsonPart.replace(/\${env\.([\w]+)}/g, (_, key) => {
|
|
59
|
+
const val = process.env[key] || "";
|
|
60
|
+
console.log(`🔄 Replacing variable env.${key} -> ${val}`);
|
|
61
|
+
return val;
|
|
62
|
+
});
|
|
63
|
+
return `Examples:${replaced}`;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 🔁 Expand Step Groups
|
|
68
|
+
|
|
69
|
+
export function expandStepGroups(featureText: string): string {
|
|
70
|
+
const cachePath = path.join("_Temp", ".cache", "stepGroup_cache.json");
|
|
71
|
+
if (!fs.existsSync(cachePath)) {
|
|
72
|
+
console.warn(`⚠️ Step group cache not found at ${cachePath}`);
|
|
73
|
+
return featureText;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const stepGroupCache = JSON.parse(fs.readFileSync(cachePath, "utf8"));
|
|
77
|
+
|
|
78
|
+
const stepGroupRegex = /^\s*\*\s*Step\s*Group:\s*-(.+?)-\s*-(.+?)-\s*$/gm;
|
|
79
|
+
|
|
80
|
+
const updatedText = featureText.replace(
|
|
81
|
+
stepGroupRegex,
|
|
82
|
+
(_match, groupIdRaw, groupDescRaw) => {
|
|
83
|
+
const groupId = groupIdRaw.trim();
|
|
84
|
+
const groupDesc = groupDescRaw.trim();
|
|
85
|
+
|
|
86
|
+
const cachedGroup = stepGroupCache[groupId];
|
|
87
|
+
|
|
88
|
+
if (!cachedGroup || !Array.isArray(cachedGroup.steps)) {
|
|
89
|
+
console.warn(`❌ Step group "${groupId}" not found or steps invalid`);
|
|
90
|
+
return _match;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const steps = cachedGroup.steps.join("\n");
|
|
94
|
+
const replacement = [
|
|
95
|
+
`\n* - Step Group - START: "${groupId}" Desc: "${groupDesc}"`,
|
|
96
|
+
steps,
|
|
97
|
+
`* - Step Group - END: "${groupId}"`,
|
|
98
|
+
].join("\n");
|
|
99
|
+
return replacement;
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
return updatedText;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 🔁 Process Smart Data (e.g., inject data-driven values or SmartIQ rules)
|
|
106
|
+
function processSmartData(content: string): string {
|
|
107
|
+
// Example: Replace [[SMART:...]] with resolved steps
|
|
108
|
+
return content.replace(/\[\[SMART:(.*?)\]\]/g, (_, expr) => {
|
|
109
|
+
console.log(`🧠 Processing SMART: ${expr}`);
|
|
110
|
+
return `# Processed SMART step: ${expr}`;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 🔁 Inject scenario-level tags
|
|
115
|
+
function injectScenarioTag(content: string, filePath: string): string {
|
|
116
|
+
const tag = `@file(${path.basename(filePath)})`;
|
|
117
|
+
const lines = content.split("\n");
|
|
118
|
+
|
|
119
|
+
const processedLines = lines.map((line, i) => {
|
|
120
|
+
if (
|
|
121
|
+
line.trim().startsWith("Scenario") &&
|
|
122
|
+
(i === 0 || !lines[i - 1].trim().startsWith("@"))
|
|
123
|
+
) {
|
|
124
|
+
return `${tag}\n${line}`;
|
|
125
|
+
}
|
|
126
|
+
return line;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return processedLines.join("\n");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function processExamplesWithFilter(
|
|
133
|
+
content: string,
|
|
134
|
+
dataDir = "test-data"
|
|
135
|
+
): string {
|
|
136
|
+
return content.replace(
|
|
137
|
+
/(^|\n)\s*Examples\s*:?\s*({[^}]*})/g,
|
|
138
|
+
(match, prefix, jsonStr) => {
|
|
139
|
+
console.log(`🔄 FOUND Examples block: [${jsonStr}]`);
|
|
140
|
+
|
|
141
|
+
console.log(`🔄 Preprocessing feature == MATCH == file: ${match}`);
|
|
142
|
+
console.log(`🔄 Preprocessing feature == MATCH END == file:`);
|
|
143
|
+
console.log(`🔄 Preprocessing feature == PREFIX == file: ${prefix}`);
|
|
144
|
+
console.log(`🔄 Preprocessing feature == jsonStr == file: ${jsonStr}`);
|
|
145
|
+
|
|
146
|
+
const obj = JSON.parse(jsonStr.replace(/'/g, '"'));
|
|
147
|
+
const normalized: Record<string, any> = {};
|
|
148
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
149
|
+
normalized[k.toLowerCase()] = v;
|
|
150
|
+
}
|
|
151
|
+
const dataFile = normalized["datafile"];
|
|
152
|
+
const filter = normalized["filter"];
|
|
153
|
+
const sheetName = normalized["sheetname"];
|
|
154
|
+
console.log(`🔄 Preprocessing feature == dataFile == file: ${dataFile}`);
|
|
155
|
+
console.log(`🔄 Preprocessing feature == filter == file: ${filter}`);
|
|
156
|
+
console.log(
|
|
157
|
+
`🔄 Preprocessing feature == sheetName == file: ${sheetName}`
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const fullPath = path.join(dataDir, path.basename(dataFile || ""));
|
|
161
|
+
const ext = path.extname(fullPath).toLowerCase();
|
|
162
|
+
console.log(`🔄 Preprocessing feature == FULL PATH == file: ${fullPath}`);
|
|
163
|
+
console.log(`🔄 Preprocessing feature == EXT == file: ${ext}`);
|
|
164
|
+
|
|
165
|
+
if (!fs.existsSync(fullPath)) return match;
|
|
166
|
+
|
|
167
|
+
const isNumeric = (val: any) =>
|
|
168
|
+
typeof val === "string" && val.trim() !== "" && !isNaN(Number(val));
|
|
169
|
+
const substituteFilter = (filter: string, row: Record<string, any>) =>
|
|
170
|
+
filter.replace(/\b[_a-zA-Z][_a-zA-Z0-9]*\b/g, (key) => {
|
|
171
|
+
const raw = row[key] ?? row[`_${key}`];
|
|
172
|
+
if (raw === undefined) return key;
|
|
173
|
+
return isNumeric(raw) ? raw.trim() : JSON.stringify(raw);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
let rows: Record<string, any>[] = [];
|
|
177
|
+
console.log(`🔍 Checking file existence: fullPath='${fullPath}'`);
|
|
178
|
+
if (!fs.existsSync(fullPath)) {
|
|
179
|
+
console.error(`❌ File not found: ${fullPath}`);
|
|
180
|
+
return match;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (ext === ".xlsx") {
|
|
184
|
+
console.log(`✔️ File found: ${fullPath}, now reading with xlsx`);
|
|
185
|
+
const workbook = xlsx.readFile(fullPath);
|
|
186
|
+
console.log(`📄 Workbook sheets: ${workbook.SheetNames.join(", ")}`);
|
|
187
|
+
const sheet = sheetName || workbook.SheetNames[0];
|
|
188
|
+
console.log(`📋 Using sheet: ${sheet}`);
|
|
189
|
+
const sheetData = xlsx.utils.sheet_to_json(workbook.Sheets[sheet]);
|
|
190
|
+
console.log(`🔢 Read ${sheetData.length} rows from sheet`);
|
|
191
|
+
rows = sheetData.filter((row) => {
|
|
192
|
+
try {
|
|
193
|
+
console.log("🔍 Row raw:", row);
|
|
194
|
+
return eval(substituteFilter(filter, row));
|
|
195
|
+
} catch {
|
|
196
|
+
console.warn("⚠️ Row filter error, skipping.");
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
} else if (ext === ".csv") {
|
|
201
|
+
console.log(`📋 ENTER CSV:`);
|
|
202
|
+
const csvData = fs.readFileSync(fullPath, "utf-8").split("\n");
|
|
203
|
+
console.log(`📋 Using csvData: ${csvData}`);
|
|
204
|
+
const headers = csvData[0].split(",").map((h) => h.trim());
|
|
205
|
+
console.log(`📋 Using headers: ${headers}`);
|
|
206
|
+
for (let i = 1; i < csvData.length; i++) {
|
|
207
|
+
const values = csvData[i].split(",");
|
|
208
|
+
if (values.length !== headers.length) continue;
|
|
209
|
+
const row: Record<string, any> = {};
|
|
210
|
+
headers.forEach((h, j) => {
|
|
211
|
+
row[h] = values[j]?.trim();
|
|
212
|
+
});
|
|
213
|
+
try {
|
|
214
|
+
if (eval(substituteFilter(filter, row))) rows.push(row);
|
|
215
|
+
} catch {}
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
return match; // unsupported file
|
|
219
|
+
}
|
|
220
|
+
console.log(`🔍 Filtered row count: ${rows.length}`);
|
|
221
|
+
if (!rows.length) {
|
|
222
|
+
console.error(
|
|
223
|
+
`❌ No matching rows found. Returning fallback Examples block.`
|
|
224
|
+
);
|
|
225
|
+
throw new Error(
|
|
226
|
+
`❌ No matching rows found. Returning fallback Examples block.`
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const headers = Object.keys(rows[0]);
|
|
231
|
+
const lines = ["\n\nExamples:", ` | ${headers.join(" | ")} |`];
|
|
232
|
+
for (const r of rows) {
|
|
233
|
+
const rowLine = ` | ${headers.map((h) => r[h] || "").join(" | ")} |`;
|
|
234
|
+
lines.push(rowLine);
|
|
235
|
+
}
|
|
236
|
+
return lines.join("\n");
|
|
237
|
+
}
|
|
238
|
+
);
|
|
239
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// // Replaced legacy '@playq' alias import with direct relative import from public globals
|
|
2
|
+
// import { config, vars } from "../global";
|
|
3
|
+
|
|
4
|
+
// // Loading D365 CRM pattern
|
|
5
|
+
// const d365CrmEnable = config?.addons?.d365Crm?.enable || vars.getConfigValue('addons.d365Crm.enable').toLowerCase().trim() === 'true';
|
|
6
|
+
// const d365CrmVersion = config?.addons?.d365Crm?.version || vars.getConfigValue('addons.d365Crm.version').toLowerCase().trim();
|
|
7
|
+
// // Updated: Look for D365 pattern in playq/addons/d365Crm/pattern/ (new path)
|
|
8
|
+
// if (d365CrmEnable && d365CrmVersion.startsWith("v")) vars.loadFileEntries(`playq/addons/d365Crm/pattern/d365CrmPattern_${d365CrmVersion}.ts`, "d365CrmLocPatterns", "pattern.d365crm");
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
import { config, vars } from "../global";
|
|
13
|
+
import path from "path";
|
|
14
|
+
import fs from "fs";
|
|
15
|
+
|
|
16
|
+
// Use minimal addons.json for discovery, config for logic
|
|
17
|
+
const projectRoot = process.env.PLAYQ_PROJECT_ROOT || process.cwd();
|
|
18
|
+
const addonsJsonPath = path.join(projectRoot, "playq", "addons", "addons.json");
|
|
19
|
+
let addonList: Array<{ name: string; description: string }> = [];
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const raw = fs.readFileSync(addonsJsonPath, "utf-8");
|
|
23
|
+
addonList = JSON.parse(raw);
|
|
24
|
+
} catch (e) {
|
|
25
|
+
console.warn("No addons.json found or failed to load:", e);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
for (const addon of addonList) {
|
|
29
|
+
console.log("[PLAYQ DEBUG] Processing addon:", addon.name);
|
|
30
|
+
const addonKey = addon.name;
|
|
31
|
+
let configEntry = config?.addons?.[addonKey];
|
|
32
|
+
let enabled = false;
|
|
33
|
+
let version = "";
|
|
34
|
+
// Debug: print configEntry and its type
|
|
35
|
+
console.log(`[PLAYQ DEBUG] configEntry for ${addonKey}:`, configEntry, 'type:', typeof configEntry);
|
|
36
|
+
console.log("[PLAYQ DEBUG] config object:", config);
|
|
37
|
+
if (configEntry && typeof configEntry === 'object') {
|
|
38
|
+
enabled = configEntry.enable === true || String(configEntry.enable).toLowerCase() === "true";
|
|
39
|
+
version = configEntry.version || "";
|
|
40
|
+
console.log(`[PLAYQ DEBUG] (object) enabled:`, enabled, 'version:', version);
|
|
41
|
+
} else {
|
|
42
|
+
// Use vars.getConfigValue for enable/version as string
|
|
43
|
+
const enableStr = String(vars.getConfigValue(`addons.${addonKey}.enable`)).toLowerCase().trim();
|
|
44
|
+
const versionStr = String(vars.getConfigValue(`addons.${addonKey}.version`)).toLowerCase().trim();
|
|
45
|
+
enabled = enableStr === 'true';
|
|
46
|
+
version = versionStr;
|
|
47
|
+
console.log(`[PLAYQ DEBUG] (vars) enableStr:`, enableStr, 'versionStr:', versionStr);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log("[PLAYQ DEBUG] Before if condition addon:", enabled , " >> ", version);
|
|
51
|
+
if (enabled && version.startsWith("v")) {
|
|
52
|
+
console.log("[PLAYQ DEBUG] After if condition addon:", enabled , " >> ", version);
|
|
53
|
+
try {
|
|
54
|
+
vars.loadFileEntries(`playq/addons/${addonKey}/pattern/${addonKey}Pattern_${version}.ts`, `${addonKey}LocPatterns`, `pattern.${addonKey.toLowerCase()}`);
|
|
55
|
+
} catch (e) {
|
|
56
|
+
console.warn(`Pattern file for ${addonKey} not found or failed to load:`, e);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Prefer .js if exists, else .ts
|
|
60
|
+
// const basePatternPath = path.join(projectRoot, "playq", "addons", addonKey, "pattern", `${addonKey}Pattern_${version}`);
|
|
61
|
+
// let patternPath = basePatternPath + ".js";
|
|
62
|
+
// if (!fs.existsSync(patternPath)) {
|
|
63
|
+
// patternPath = basePatternPath + ".ts";
|
|
64
|
+
// }
|
|
65
|
+
// try {
|
|
66
|
+
// vars.loadFileEntries(patternPath, `${addonKey}LocPatterns`, `pattern.${addonKey.toLowerCase()}`);
|
|
67
|
+
// } catch (e) {
|
|
68
|
+
// console.warn(`Pattern file for ${addonKey} not found or failed to load:`, e);
|
|
69
|
+
// }
|
|
70
|
+
// Optionally, perform other addon-specific initialization here
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { sync } from 'glob';
|
|
4
|
+
import * as vars from '../helper/bundle/vars';
|
|
5
|
+
const config: any = {};
|
|
6
|
+
|
|
7
|
+
import { generateStepGroupsIfNeeded } from './sgGenerator';
|
|
8
|
+
import {
|
|
9
|
+
getCachedFeatureFilePath,
|
|
10
|
+
shouldUseCachedFeature,
|
|
11
|
+
updateFeatureCacheMeta
|
|
12
|
+
} from './featureFileCache';
|
|
13
|
+
import { preprocessFeatureFile } from './featureFilePreProcess';
|
|
14
|
+
|
|
15
|
+
const featureFileCache = config?.cucumber?.featureFileCache || (vars.getConfigValue('cucumber.featureFileCache')|| false);
|
|
16
|
+
const isForce = process.argv.includes('--force');
|
|
17
|
+
|
|
18
|
+
console.log('🚀 Running preProcessEntry.ts...');
|
|
19
|
+
console.log(`⚙️ featureFileCache enabled: ${featureFileCache}`);
|
|
20
|
+
console.log(`⚙️ Force flag: ${isForce}`);
|
|
21
|
+
|
|
22
|
+
generateStepGroupsIfNeeded(isForce);
|
|
23
|
+
|
|
24
|
+
const featureFiles = sync('test/features/**/*.feature');
|
|
25
|
+
if (!featureFiles.length) {
|
|
26
|
+
console.warn('⚠️ No feature files found under features/**/*.feature');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Clean up execution folder before generating new feature files
|
|
30
|
+
const executionDir = path.join('_Temp', 'execution');
|
|
31
|
+
if (fs.existsSync(executionDir)) {
|
|
32
|
+
fs.rmSync(executionDir, { recursive: true, force: true });
|
|
33
|
+
console.log(`🧹 Cleaned up execution folder: ${executionDir}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const originalPath of featureFiles) {
|
|
37
|
+
console.log(`🔧 Processing: ${originalPath}`);
|
|
38
|
+
const cachedPath = getCachedFeatureFilePath(originalPath);
|
|
39
|
+
console.log(`📄 Cached path: ${cachedPath}`);
|
|
40
|
+
|
|
41
|
+
if (featureFileCache && !isForce && shouldUseCachedFeature(originalPath, cachedPath)) {
|
|
42
|
+
console.log(`✅ Using cached feature file: ${cachedPath}`);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const updatedContent = preprocessFeatureFile(originalPath);
|
|
47
|
+
if (!updatedContent || !updatedContent.trim().startsWith('Feature')) {
|
|
48
|
+
console.warn(`❌ Skipping cache write for ${originalPath}: Invalid content. Preview:\n${(updatedContent || '').substring(0, 100)}`);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
fs.mkdirSync(path.dirname(cachedPath), { recursive: true });
|
|
53
|
+
fs.writeFileSync(cachedPath, updatedContent, 'utf-8');
|
|
54
|
+
console.log(`📥 Updated cached feature file: ${cachedPath}`);
|
|
55
|
+
|
|
56
|
+
if (featureFileCache) {
|
|
57
|
+
updateFeatureCacheMeta(originalPath, cachedPath);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import csv from 'csv-parser';
|
|
4
|
+
// import xlsx from 'xlsx';
|
|
5
|
+
import xlsx from '@e965/xlsx';
|
|
6
|
+
import crypto from 'crypto';
|
|
7
|
+
import { generateStepGroups } from './sgGenerator';
|
|
8
|
+
const config: any = {};
|
|
9
|
+
|
|
10
|
+
function loadStepGroupCache(): Record<string, { description: string; steps: string[] }> {
|
|
11
|
+
const cachePath = path.resolve('_Temp/.cache/stepGroup_cache.json');
|
|
12
|
+
if (!fs.existsSync(cachePath)) {
|
|
13
|
+
console.error(`❌ Step group JSON cache not found. Please run sgGenerator.ts first.`);
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return JSON.parse(fs.readFileSync(cachePath, 'utf-8'));
|
|
18
|
+
}
|
|
19
|
+
const args = process.argv.slice(2);
|
|
20
|
+
const isForced = args.includes('--force');
|
|
21
|
+
|
|
22
|
+
function sha256(content: string): string {
|
|
23
|
+
return crypto.createHash('sha256').update(content).digest('hex');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const featureCachePath = path.resolve('_Temp/.cache/featureMeta.json');
|
|
27
|
+
const featureCache: Record<string, string> = fs.existsSync(featureCachePath)
|
|
28
|
+
? JSON.parse(fs.readFileSync(featureCachePath, 'utf8'))
|
|
29
|
+
: {};
|
|
30
|
+
|
|
31
|
+
const sourceDir = path.resolve('test/features');
|
|
32
|
+
const outputDir = path.resolve('_Temp/execution');
|
|
33
|
+
const dataDir = path.resolve('test-data');
|
|
34
|
+
|
|
35
|
+
function findFeatureFiles(dir: string): string[] {
|
|
36
|
+
let results: string[] = [];
|
|
37
|
+
fs.readdirSync(dir).forEach(file => {
|
|
38
|
+
const fullPath = path.join(dir, file);
|
|
39
|
+
const stat = fs.statSync(fullPath);
|
|
40
|
+
if (stat.isDirectory()) {
|
|
41
|
+
results = results.concat(findFeatureFiles(fullPath));
|
|
42
|
+
} else if (file.endsWith('.feature')) {
|
|
43
|
+
results.push(fullPath);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return results;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function ensureOutputPath(featurePath: string): string {
|
|
50
|
+
const relativePath = path.relative(sourceDir, featurePath);
|
|
51
|
+
const targetPath = path.join(outputDir, relativePath);
|
|
52
|
+
const dir = path.dirname(targetPath);
|
|
53
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
54
|
+
return targetPath;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isNumeric(val: any): boolean {
|
|
58
|
+
return typeof val === 'string' && val.trim() !== '' && !isNaN(Number(val));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function validateIdentifiers(filter: string, inputPath: string): boolean {
|
|
62
|
+
const invalidIdentifiers = Array.from(filter.matchAll(/_([A-Z0-9_\-\.]+)/gi))
|
|
63
|
+
.map(match => match[1])
|
|
64
|
+
.filter(id => !/^[_a-zA-Z][_a-zA-Z0-9]*$/.test(`_${id}`));
|
|
65
|
+
|
|
66
|
+
if (invalidIdentifiers.length > 0) {
|
|
67
|
+
console.error(`❌ Invalid identifiers in filter: ${invalidIdentifiers.map(i => `"_${i}"`).join(', ')}`);
|
|
68
|
+
console.error(` 👉 Use only letters, numbers, and underscores (e.g., _SUB_STATUS)`);
|
|
69
|
+
console.error(` ✋ Please fix this in feature file: ${inputPath}`);
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function substituteFilter(filter: string, row: Record<string, any>): string {
|
|
76
|
+
const allKeys = Object.keys(row);
|
|
77
|
+
const pattern = /\b[_a-zA-Z][_a-zA-Z0-9]*\b/g;
|
|
78
|
+
return filter.replace(pattern, (key) => {
|
|
79
|
+
const raw = row[key] ?? row[`_${key}`];
|
|
80
|
+
if (raw === undefined) return key; // leave as-is if not found
|
|
81
|
+
return isNumeric(raw) ? raw.trim() : JSON.stringify(raw);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function preprocessFeatureFile(
|
|
86
|
+
inputPath: string,
|
|
87
|
+
outputPath: string,
|
|
88
|
+
stepGroupCache: Record<string, { description: string; steps: string[] }>,
|
|
89
|
+
stepGroupCacheHash: string
|
|
90
|
+
) {
|
|
91
|
+
const relativePath = path.relative(sourceDir, inputPath);
|
|
92
|
+
let featureHashInput = fs.readFileSync(inputPath, 'utf-8') + stepGroupCacheHash;
|
|
93
|
+
const featureText = fs.readFileSync(inputPath, 'utf-8');
|
|
94
|
+
const lines = featureText.split('\n');
|
|
95
|
+
|
|
96
|
+
let dataFile = '';
|
|
97
|
+
let filter = '';
|
|
98
|
+
let sheetName = '';
|
|
99
|
+
let exampleLineIndex = -1;
|
|
100
|
+
|
|
101
|
+
lines.forEach((line, index) => {
|
|
102
|
+
const exampleMatch = line.trim().startsWith('Examples:') && line.includes('{');
|
|
103
|
+
if (exampleMatch) {
|
|
104
|
+
exampleLineIndex = index;
|
|
105
|
+
const jsonStr = line.replace(/^Examples:\s*/, '').trim();
|
|
106
|
+
try {
|
|
107
|
+
const parsed = JSON.parse(jsonStr.replace(/'/g, '"'));
|
|
108
|
+
dataFile = parsed.dataFile;
|
|
109
|
+
filter = parsed.filter;
|
|
110
|
+
sheetName = parsed.sheetName;
|
|
111
|
+
|
|
112
|
+
if (!validateIdentifiers(filter, inputPath)) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// Add to hash input: options for Examples
|
|
116
|
+
featureHashInput += JSON.stringify({ filter, sheetName, dataFile });
|
|
117
|
+
} catch (err) {
|
|
118
|
+
console.error(`❌ Failed to parse Examples JSON in ${inputPath}:\n`, err);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// If there is a data file, append its content to the hash input (if it exists)
|
|
125
|
+
let fullPath = '';
|
|
126
|
+
if (dataFile) {
|
|
127
|
+
const ext = path.extname(dataFile).toLowerCase();
|
|
128
|
+
fullPath = path.join(dataDir, path.basename(dataFile));
|
|
129
|
+
if (fs.existsSync(fullPath)) {
|
|
130
|
+
// For CSV, read as utf-8; for XLSX, hash the file buffer.
|
|
131
|
+
if (ext === '.csv') {
|
|
132
|
+
featureHashInput += fs.readFileSync(fullPath, 'utf-8');
|
|
133
|
+
} else if (ext === '.xlsx') {
|
|
134
|
+
featureHashInput += fs.readFileSync(fullPath).toString('base64');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Compute current hash
|
|
140
|
+
const currentHash = sha256(featureHashInput);
|
|
141
|
+
if (!isForced && featureCache[relativePath] && featureCache[relativePath] === currentHash) {
|
|
142
|
+
console.log(`⏩ Skipped (unchanged): ${relativePath}`);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!dataFile || !filter || exampleLineIndex === -1) {
|
|
147
|
+
// No Examples, just expand step groups and write output
|
|
148
|
+
console.log("📣 No Examples detected. Proceeding with step group expansion only...");
|
|
149
|
+
const expandedLines = expandStepGroups(lines, inputPath, stepGroupCache);
|
|
150
|
+
const outputContent = expandedLines.join('\n');
|
|
151
|
+
fs.writeFileSync(outputPath, outputContent, 'utf-8');
|
|
152
|
+
featureCache[relativePath] = currentHash;
|
|
153
|
+
console.log(`📄 Preprocessed (no Examples): ${outputPath}`);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Has Examples with data
|
|
158
|
+
console.log("✔ Should generate examples (this line will NOT show if the block above returns)");
|
|
159
|
+
|
|
160
|
+
const ext = path.extname(dataFile).toLowerCase();
|
|
161
|
+
const rows: Record<string, any>[] = [];
|
|
162
|
+
|
|
163
|
+
const buildExamplesFile = () => {
|
|
164
|
+
if (rows.length === 0) {
|
|
165
|
+
console.log(`❌ No matching rows found in ${dataFile}`);
|
|
166
|
+
fs.writeFileSync(outputPath, featureText, 'utf-8');
|
|
167
|
+
featureCache[relativePath] = currentHash;
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const headers = Object.keys(rows[0]); // Include all headers including _ENV, _STATUS etc.
|
|
172
|
+
const examples = ['Examples:', ` | ${headers.join(' | ')} |`]
|
|
173
|
+
.concat(rows.map(r => {
|
|
174
|
+
const rowData = headers.map(h => r[h] || '');
|
|
175
|
+
return ` | ${rowData.join(' | ')} |`;
|
|
176
|
+
}));
|
|
177
|
+
|
|
178
|
+
const outputLines = [
|
|
179
|
+
...lines.slice(0, exampleLineIndex),
|
|
180
|
+
...examples,
|
|
181
|
+
...lines.slice(exampleLineIndex + 1),
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
// Expand Step Groups before processing Examples
|
|
185
|
+
const expandedLines = expandStepGroups(outputLines, inputPath, stepGroupCache);
|
|
186
|
+
|
|
187
|
+
// Rebuild Examples only after step groups are expanded
|
|
188
|
+
const finalLines = expandedLines.join('\n');
|
|
189
|
+
|
|
190
|
+
fs.writeFileSync(outputPath, finalLines, 'utf-8');
|
|
191
|
+
featureCache[relativePath] = currentHash;
|
|
192
|
+
console.log(`✅ Processed: ${outputPath}`);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
if (ext === '.csv') {
|
|
196
|
+
fs.createReadStream(fullPath)
|
|
197
|
+
.pipe(csv())
|
|
198
|
+
.on('data', (row) => {
|
|
199
|
+
try {
|
|
200
|
+
if (eval(substituteFilter(filter, row))) {
|
|
201
|
+
rows.push(row);
|
|
202
|
+
}
|
|
203
|
+
} catch (e) {
|
|
204
|
+
console.warn(`⚠️ Skipping row due to filter error: ${e.message}`);
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
.on('end', buildExamplesFile);
|
|
208
|
+
} else if (ext === '.xlsx') {
|
|
209
|
+
const workbook = xlsx.readFile(fullPath);
|
|
210
|
+
const sheet = sheetName || workbook.SheetNames[0];
|
|
211
|
+
const sheetData = xlsx.utils.sheet_to_json(workbook.Sheets[sheet]);
|
|
212
|
+
sheetData.forEach((row: any) => {
|
|
213
|
+
try {
|
|
214
|
+
if (eval(substituteFilter(filter, row))) {
|
|
215
|
+
rows.push(row);
|
|
216
|
+
}
|
|
217
|
+
} catch (e) {
|
|
218
|
+
console.warn(`⚠️ Skipping row due to filter error: ${e.message}`);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
buildExamplesFile();
|
|
222
|
+
} else {
|
|
223
|
+
console.error(`❌ Unsupported file type: ${ext}`);
|
|
224
|
+
fs.writeFileSync(outputPath, featureText, 'utf-8');
|
|
225
|
+
featureCache[relativePath] = currentHash;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function run() {
|
|
230
|
+
console.log("🔄 Generating step group definitions from sgGenerator...");
|
|
231
|
+
generateStepGroups({ force: isForced }); // Regenerate stepGroup_steps.ts before preprocessing
|
|
232
|
+
|
|
233
|
+
// if (fs.existsSync(outputDir)) {
|
|
234
|
+
// fs.rmSync(outputDir, { recursive: true, force: true });
|
|
235
|
+
// }
|
|
236
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
237
|
+
|
|
238
|
+
// Load step group cache once
|
|
239
|
+
const stepGroupCache = loadStepGroupCache();
|
|
240
|
+
const stepGroupCacheHash = sha256(JSON.stringify(stepGroupCache));
|
|
241
|
+
|
|
242
|
+
const features = findFeatureFiles(sourceDir);
|
|
243
|
+
if (!features.length) {
|
|
244
|
+
console.log('No feature files found.');
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
features.forEach(featurePath => {
|
|
249
|
+
// Skip the step group directory
|
|
250
|
+
if (featurePath.includes('_step_group') || featurePath.includes('step_group')) {
|
|
251
|
+
console.log(`🛑 Skipping step group directory: ${featurePath}`);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const outPath = ensureOutputPath(featurePath);
|
|
256
|
+
preprocessFeatureFile(featurePath, outPath, stepGroupCache, stepGroupCacheHash);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Save the feature cache at the end
|
|
260
|
+
fs.mkdirSync(path.dirname(featureCachePath), { recursive: true });
|
|
261
|
+
fs.writeFileSync(featureCachePath, JSON.stringify(featureCache, null, 2), 'utf8');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function expandStepGroups(
|
|
265
|
+
lines: string[],
|
|
266
|
+
inputPath: string,
|
|
267
|
+
stepGroupCache: Record<string, { description: string; steps: string[] }>
|
|
268
|
+
): string[] {
|
|
269
|
+
return lines.flatMap(line => {
|
|
270
|
+
// const match = line.trim().match(/^\* Step Group: -([a-zA-Z0-9_.]+)-/);
|
|
271
|
+
const match = line.trim().match(/^(?:\*|Given|When|Then|And|But) Step Group: -([a-zA-Z0-9_.]+)-/);
|
|
272
|
+
if (match) {
|
|
273
|
+
const groupName = match[1];
|
|
274
|
+
const group = stepGroupCache[groupName];
|
|
275
|
+
if (!group) {
|
|
276
|
+
console.error(`❌ Step Group not found: ${groupName} (in ${inputPath})`);
|
|
277
|
+
process.exit(1);
|
|
278
|
+
}
|
|
279
|
+
return [
|
|
280
|
+
`* - Step Group - START: "${groupName}" Desc: "${group.description}"`,
|
|
281
|
+
...group.steps.map(step => ` ${step}`),
|
|
282
|
+
`* - Step Group - END: "${groupName}"`
|
|
283
|
+
];
|
|
284
|
+
}
|
|
285
|
+
return [line];
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
run();
|