k6-cucumber-steps 2.0.5 → 2.0.7
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/dist/cli.js +17 -8
- package/dist/cli.js.map +1 -1
- package/dist/generators/k6-script.generator.d.ts.map +1 -1
- package/dist/generators/k6-script.generator.js +53 -47
- package/dist/generators/k6-script.generator.js.map +1 -1
- package/dist/generators/project.generator.d.ts.map +1 -1
- package/dist/generators/project.generator.js +345 -252
- package/dist/generators/project.generator.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -30,6 +30,7 @@ commander_1.program
|
|
|
30
30
|
.description("Generate k6 scripts from feature files")
|
|
31
31
|
.option("-f, --features <path>", "Path to feature files", "./features")
|
|
32
32
|
.option("-o, --output <path>", "Output path for generated scripts", "./generated")
|
|
33
|
+
.option("--exclude-tags <tags>", "Exclude scenarios by tags (comma-separated)")
|
|
33
34
|
.option("-l, --lang <language>", "Output language (js or ts)", "ts")
|
|
34
35
|
.option("--tags <tags>", "Filter scenarios by tags")
|
|
35
36
|
.action(async (options) => {
|
|
@@ -38,12 +39,19 @@ commander_1.program
|
|
|
38
39
|
async function generateK6Scripts(options) {
|
|
39
40
|
console.log("Generating k6 scripts from feature files...");
|
|
40
41
|
const parser = new feature_parser_1.FeatureParser();
|
|
41
|
-
const features = await parser.loadAndParseFeatures(options.features);
|
|
42
|
-
//
|
|
42
|
+
const features = await parser.loadAndParseFeatures(options.features || "./features");
|
|
43
|
+
// Include by tags
|
|
43
44
|
if (options.tags) {
|
|
44
|
-
const
|
|
45
|
+
const includeTags = options.tags.split(",").map(t => t.trim()).filter(t => t);
|
|
45
46
|
features.forEach((feature) => {
|
|
46
|
-
feature.scenarios = feature.scenarios.filter((scenario) =>
|
|
47
|
+
feature.scenarios = feature.scenarios.filter((scenario) => includeTags.some(tag => scenario.tags.includes(tag)));
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
// Exclude by tags
|
|
51
|
+
if (options.excludeTags) {
|
|
52
|
+
const excludeTags = options.excludeTags.split(",").map(t => t.trim()).filter(t => t);
|
|
53
|
+
features.forEach((feature) => {
|
|
54
|
+
feature.scenarios = feature.scenarios.filter((scenario) => !excludeTags.some(tag => scenario.tags.includes(tag)));
|
|
47
55
|
});
|
|
48
56
|
}
|
|
49
57
|
// Flatten all scenarios
|
|
@@ -52,16 +60,17 @@ async function generateK6Scripts(options) {
|
|
|
52
60
|
const generator = new k6_script_generator_1.K6ScriptGenerator();
|
|
53
61
|
const config = {
|
|
54
62
|
language: options.lang,
|
|
55
|
-
featuresDir: options.features,
|
|
56
|
-
outputDir: options.output,
|
|
63
|
+
featuresDir: options.features || "./features",
|
|
64
|
+
outputDir: options.output || "./generated",
|
|
57
65
|
includeHtmlReporter: true,
|
|
58
66
|
author: "Enyimiri Chetachi Paschal (qaPaschalE)",
|
|
59
67
|
version: packageJson.version,
|
|
60
68
|
};
|
|
61
69
|
const k6Script = generator.generateK6File(allScenarios, metadata, config);
|
|
70
|
+
const outputDir = options.output || "./generated";
|
|
62
71
|
// Ensure output directory exists
|
|
63
|
-
if (!fs_1.default.existsSync(
|
|
64
|
-
fs_1.default.mkdirSync(
|
|
72
|
+
if (!fs_1.default.existsSync(outputDir)) {
|
|
73
|
+
fs_1.default.mkdirSync(outputDir, { recursive: true });
|
|
65
74
|
}
|
|
66
75
|
const outputFile = `${options.output}/test.generated.${options.lang}`;
|
|
67
76
|
fs_1.default.writeFileSync(outputFile, k6Script);
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;;;;;AACA,aAAa;AACb,yCAAoC;AACpC,0DAAsD;AACtD,gEAA4D;AAC5D,0EAAqE;AAErE,4CAAoB;AAEpB,eAAe;AACf,MAAM,WAAW,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAE/C,mBAAO;KACJ,IAAI,CAAC,mBAAmB,CAAC;KACzB,WAAW,CAAC,sDAAsD,CAAC;KACnE,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;AAEhC,mBAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,sCAAsC,CAAC;KACnD,MAAM,CAAC,uBAAuB,EAAE,iCAAiC,EAAE,IAAI,CAAC;KACxE,QAAQ,CAAC,QAAQ,EAAE,uBAAuB,EAAE,GAAG,CAAC;KAChD,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;IAC9B,MAAM,OAAO,GAAG,IAAI,0BAAW,EAAE,CAAC;IAClC,MAAM,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,IAAmB,CAAC,CAAC;AAC3D,CAAC,CAAC,CAAC;AAEL,mBAAO;KACJ,OAAO,CAAC,UAAU,CAAC;KACnB,WAAW,CAAC,wCAAwC,CAAC;KACrD,MAAM,CAAC,uBAAuB,EAAE,uBAAuB,EAAE,YAAY,CAAC;KACtE,MAAM,CACL,qBAAqB,EACrB,mCAAmC,EACnC,aAAa,CACd;KACA,MAAM,CAAC,uBAAuB,EAAE,4BAA4B,EAAE,IAAI,CAAC;KACnE,MAAM,CAAC,eAAe,EAAE,0BAA0B,CAAC;KACnD,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,MAAM,iBAAiB,CAAC,OAAO,CAAC,CAAC;AACnC,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"k6-script.generator.d.ts","sourceRoot":"","sources":["../../src/generators/k6-script.generator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"k6-script.generator.d.ts","sourceRoot":"","sources":["../../src/generators/k6-script.generator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACrE,qBAAa,iBAAiB;IAC5B,cAAc,CACZ,SAAS,EAAE,QAAQ,EAAE,EACrB,QAAQ,EAAE,gBAAgB,EAAE,EAC5B,MAAM,EAAE,aAAa,GACpB,MAAM;IAiGT,OAAO,CAAC,eAAe;IA6BvB,OAAO,CAAC,eAAe;IA8FvB,OAAO,CAAC,eAAe;IAuBvB,OAAO,CAAC,iBAAiB;IAezB,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,oBAAoB;CAuG7B"}
|
|
@@ -12,18 +12,16 @@ globalThis.lastResponse = globalThis.lastResponse || {};
|
|
|
12
12
|
`;
|
|
13
13
|
const imports = this.generateImports(config, metadata);
|
|
14
14
|
const options = this.generateOptions(metadata);
|
|
15
|
-
const testFunction = this.generateTestFunction(scenarios, metadata);
|
|
16
|
-
// ✅
|
|
15
|
+
const testFunction = this.generateTestFunction(scenarios, metadata, config);
|
|
16
|
+
// ✅ Simplified teardown: just return the tokens (no globalThis needed)
|
|
17
17
|
const teardownFn = config.language === "ts"
|
|
18
18
|
? `export function teardown(tokensFromDefault: Record<string, any>) {
|
|
19
|
-
|
|
20
|
-
globalThis.exportedTokens = tokensFromDefault;
|
|
19
|
+
return tokensFromDefault;
|
|
21
20
|
}`
|
|
22
21
|
: `export function teardown(tokensFromDefault) {
|
|
23
|
-
|
|
24
|
-
globalThis.exportedTokens = tokensFromDefault;
|
|
22
|
+
return tokensFromDefault;
|
|
25
23
|
}`;
|
|
26
|
-
// ✅
|
|
24
|
+
// ✅ handleSummary already uses data.teardown_data — no change needed
|
|
27
25
|
const handleSummaryFn = config.language === "ts"
|
|
28
26
|
? `export function handleSummary(data: any): Record<string, any> {
|
|
29
27
|
const reports: Record<string, any> = {
|
|
@@ -34,18 +32,18 @@ globalThis.lastResponse = globalThis.lastResponse || {};
|
|
|
34
32
|
|
|
35
33
|
console.log('--- Summary File Write Audit ---');
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
if (data.setup_data && !Object.keys(tokens).length) {
|
|
39
|
-
tokens = data.setup_data;
|
|
40
|
-
}
|
|
41
|
-
|
|
35
|
+
const tokens = data.teardown_data || {};
|
|
42
36
|
const keys = Object.keys(tokens).filter(k => k.endsWith('.json'));
|
|
43
37
|
console.log('Tokens found:', keys.length > 0 ? keys.join(', ') : 'None');
|
|
44
38
|
|
|
45
39
|
for (const [path, tokenValue] of Object.entries(tokens)) {
|
|
46
40
|
if (path.endsWith('.json')) {
|
|
47
41
|
const fullPath = path.startsWith('./') ? path : \`./\${path}\`;
|
|
48
|
-
reports[fullPath] = JSON.stringify(
|
|
42
|
+
reports[fullPath] = JSON.stringify(
|
|
43
|
+
typeof tokenValue === 'string' ? { access_token: tokenValue } : tokenValue,
|
|
44
|
+
null,
|
|
45
|
+
2
|
|
46
|
+
);
|
|
49
47
|
console.log(\`✅ Writing: \${fullPath}\`);
|
|
50
48
|
}
|
|
51
49
|
}
|
|
@@ -61,25 +59,24 @@ globalThis.lastResponse = globalThis.lastResponse || {};
|
|
|
61
59
|
|
|
62
60
|
console.log('--- Summary File Write Audit ---');
|
|
63
61
|
|
|
64
|
-
|
|
65
|
-
if (data.setup_data && !Object.keys(tokens).length) {
|
|
66
|
-
tokens = data.setup_data;
|
|
67
|
-
}
|
|
68
|
-
|
|
62
|
+
const tokens = data["root_group::teardown"] || {};
|
|
69
63
|
const keys = Object.keys(tokens).filter(k => k.endsWith('.json'));
|
|
70
64
|
console.log('Tokens found:', keys.length > 0 ? keys.join(', ') : 'None');
|
|
71
65
|
|
|
72
66
|
for (const [path, tokenValue] of Object.entries(tokens)) {
|
|
73
67
|
if (path.endsWith('.json')) {
|
|
74
|
-
|
|
75
|
-
reports[
|
|
76
|
-
|
|
68
|
+
// ✅ Use path directly — no ./ prefix
|
|
69
|
+
reports[path] = JSON.stringify(
|
|
70
|
+
typeof tokenValue === 'string' ? { access_token: tokenValue } : tokenValue,
|
|
71
|
+
null,
|
|
72
|
+
2
|
|
73
|
+
);
|
|
74
|
+
console.log(\`✅ Writing: \${path}\`);
|
|
77
75
|
}
|
|
78
76
|
}
|
|
79
77
|
|
|
80
78
|
return reports;
|
|
81
79
|
}`;
|
|
82
|
-
// ✅ Now embed the resolved strings into the final output
|
|
83
80
|
return `
|
|
84
81
|
${header}
|
|
85
82
|
${imports}
|
|
@@ -87,7 +84,6 @@ ${imports}
|
|
|
87
84
|
${options}
|
|
88
85
|
|
|
89
86
|
export function setup() {
|
|
90
|
-
// We must return an object here to initialize the "data" channel
|
|
91
87
|
return { v: Date.now() };
|
|
92
88
|
}
|
|
93
89
|
|
|
@@ -98,19 +94,28 @@ ${teardownFn}
|
|
|
98
94
|
${handleSummaryFn}
|
|
99
95
|
`;
|
|
100
96
|
}
|
|
101
|
-
generateImports(config,
|
|
97
|
+
generateImports(config, meta) {
|
|
102
98
|
const ext = config.language === "ts" ? "ts" : "js";
|
|
103
|
-
const hasBrowser =
|
|
99
|
+
const hasBrowser = meta.some(m => m.tags?.includes("browser")); // ← now used!
|
|
104
100
|
const baseImports = [
|
|
105
101
|
'import http from "k6/http";',
|
|
106
102
|
'import { check, sleep, group } from "k6";',
|
|
107
|
-
'import { htmlReport } from "https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js";',
|
|
108
|
-
'import { textSummary } from "https://jslib.k6.io/k6-summary/0.1.0/index.js";',
|
|
109
103
|
];
|
|
104
|
+
// Add @ts-ignore for remote modules in TS mode
|
|
105
|
+
if (config.language === "ts") {
|
|
106
|
+
baseImports.push('// @ts-ignore: k6 resolves remote modules at runtime');
|
|
107
|
+
baseImports.push('import { htmlReport } from "https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js";');
|
|
108
|
+
baseImports.push('// @ts-ignore: k6 resolves remote modules at runtime');
|
|
109
|
+
baseImports.push('import { textSummary } from "https://jslib.k6.io/k6-summary/0.1.0/index.js";');
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
baseImports.push('import { htmlReport } from "https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js";');
|
|
113
|
+
baseImports.push('import { textSummary } from "https://jslib.k6.io/k6-summary/0.1.0/index.js";');
|
|
114
|
+
}
|
|
110
115
|
if (hasBrowser) {
|
|
111
116
|
baseImports.push('import { browser } from "k6/browser";');
|
|
112
117
|
}
|
|
113
|
-
baseImports.push(`import * as steps from "../steps/sample.steps.${ext}"
|
|
118
|
+
baseImports.push(`import * as steps from "../steps/sample.steps.${ext}";`);
|
|
114
119
|
return baseImports.join("\n");
|
|
115
120
|
}
|
|
116
121
|
generateOptions(metadata) {
|
|
@@ -225,19 +230,15 @@ ${handleSummaryFn}
|
|
|
225
230
|
return (validMetrics.includes(metricName) || /^[a-z][a-z0-9_]*$/.test(metricName));
|
|
226
231
|
}
|
|
227
232
|
normalizeStepText(text) {
|
|
228
|
-
//
|
|
229
|
-
const noQuotes = text.replace(/"[^"]*"|'[^']*'/g, "")
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
.map((word, i) => i === 0
|
|
238
|
-
? word.toLowerCase()
|
|
239
|
-
: word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
240
|
-
.join("");
|
|
233
|
+
// Remove quoted strings first
|
|
234
|
+
const noQuotes = text.replace(/"[^"]*"|'[^']*'/g, "");
|
|
235
|
+
// Normalize "with headers" -> treat as separate keyword
|
|
236
|
+
let clean = noQuotes
|
|
237
|
+
.replace(/\s+with\s+headers\s*$/, "") // remove trailing "with headers"
|
|
238
|
+
.replace(/[^a-zA-Z0-9\s]/g, " ")
|
|
239
|
+
.trim();
|
|
240
|
+
const words = clean.split(/\s+/).filter(w => w);
|
|
241
|
+
return words.map((w, i) => i === 0 ? w.toLowerCase() : w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
|
|
241
242
|
}
|
|
242
243
|
extractArguments(text) {
|
|
243
244
|
// Only extract quoted strings (double or single quotes)
|
|
@@ -246,7 +247,7 @@ ${handleSummaryFn}
|
|
|
246
247
|
return matches.join(", ");
|
|
247
248
|
}
|
|
248
249
|
// Inside K6ScriptGenerator.ts
|
|
249
|
-
generateTestFunction(scenarios, metadata) {
|
|
250
|
+
generateTestFunction(scenarios, metadata, config) {
|
|
250
251
|
const hasAnyBrowser = metadata.some((m) => m.tags?.includes("browser"));
|
|
251
252
|
const lines = [];
|
|
252
253
|
// Open browser if any scenario uses it
|
|
@@ -275,7 +276,7 @@ ${handleSummaryFn}
|
|
|
275
276
|
const args = argsStr ? argsStr.split(/,\s*/).filter(Boolean) : [];
|
|
276
277
|
const needsPage = [
|
|
277
278
|
"navigate", "click", "see", "fill", "type", "press", "waitfor", "wait", "locator",
|
|
278
|
-
"select", "title", "url", "element", "shouldsee", "shouldnotsee"
|
|
279
|
+
"select", "title", "url", "element", "shouldsee", "shouldnotsee", "button", "find"
|
|
279
280
|
].some(kw => name.toLowerCase().includes(kw)) &&
|
|
280
281
|
!["thebaseurlis", "thebaseurl"].some(kw => name.toLowerCase().includes(kw));
|
|
281
282
|
const params = [];
|
|
@@ -290,9 +291,12 @@ ${handleSummaryFn}
|
|
|
290
291
|
lines.push(` ${callPrefix}steps.${name}(${params.join(", ")});`);
|
|
291
292
|
});
|
|
292
293
|
lines.push(` } catch (err) {`);
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
294
|
+
const stackAccessCode = config.language === "ts"
|
|
295
|
+
? "(err as any)?.stack || 'No stack'"
|
|
296
|
+
: "err?.stack || 'No stack'";
|
|
297
|
+
lines.push(` console.error('Error in ${scenario.name}:', err);`);
|
|
298
|
+
lines.push(` console.error('Stack:', ${stackAccessCode});`);
|
|
299
|
+
lines.push(` }`);
|
|
296
300
|
}
|
|
297
301
|
else {
|
|
298
302
|
// Non-browser (HTTP/API) steps
|
|
@@ -329,7 +333,9 @@ ${handleSummaryFn}
|
|
|
329
333
|
return `
|
|
330
334
|
export default async function () {
|
|
331
335
|
${lines.join("\n")}
|
|
332
|
-
|
|
336
|
+
console.log('🔍 Final savedTokens:', JSON.stringify(globalThis.savedTokens));
|
|
337
|
+
|
|
338
|
+
return globalThis.savedTokens || {};
|
|
333
339
|
}`;
|
|
334
340
|
}
|
|
335
341
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"k6-script.generator.js","sourceRoot":"","sources":["../../src/generators/k6-script.generator.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"k6-script.generator.js","sourceRoot":"","sources":["../../src/generators/k6-script.generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.generator.d.ts","sourceRoot":"","sources":["../../src/generators/project.generator.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,qBAAa,gBAAgB;IAC3B,wBAAwB,CAAC,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IA+BzE,OAAO,CAAC,eAAe;
|
|
1
|
+
{"version":3,"file":"project.generator.d.ts","sourceRoot":"","sources":["../../src/generators/project.generator.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,qBAAa,gBAAgB;IAC3B,wBAAwB,CAAC,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IA+BzE,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,mBAAmB;IAc3B,OAAO,CAAC,mBAAmB;IA4D3B,OAAO,CAAC,cAAc;IAsDtB,OAAO,CAAC,yBAAyB;IAkCjC,OAAO,CAAC,qBAAqB;IAsD7B,OAAO,CAAC,4BAA4B;IAmHpC,OAAO,CAAC,mBAAmB;IAiuB3B,OAAO,CAAC,iBAAiB;IAgBzB,OAAO,CAAC,gBAAgB;CAsBzB"}
|
|
@@ -30,6 +30,7 @@ class ProjectGenerator {
|
|
|
30
30
|
this.generateSampleSteps(outputPath, config);
|
|
31
31
|
// Generate gitignore
|
|
32
32
|
this.generateGitignore(outputPath);
|
|
33
|
+
this.generateGlobalTypes(outputPath);
|
|
33
34
|
// Generate tsconfig if TypeScript
|
|
34
35
|
if (config.language === "ts") {
|
|
35
36
|
this.generateTsConfig(outputPath);
|
|
@@ -40,15 +41,18 @@ class ProjectGenerator {
|
|
|
40
41
|
fs_1.default.mkdirSync(dirPath, { recursive: true });
|
|
41
42
|
}
|
|
42
43
|
}
|
|
44
|
+
// This method is called during `init`
|
|
43
45
|
generateGlobalTypes(outputPath) {
|
|
44
|
-
const content = `// Auto-generated
|
|
46
|
+
const content = `// Auto-generated by k6-cucumber-steps
|
|
47
|
+
export {};
|
|
48
|
+
|
|
45
49
|
declare global {
|
|
46
50
|
var savedTokens: Record<string, any>;
|
|
47
51
|
var lastResponse: any;
|
|
48
52
|
var exportedTokens: Record<string, any>;
|
|
53
|
+
var baseUrl: string;
|
|
54
|
+
var lastPostData: string;
|
|
49
55
|
}
|
|
50
|
-
|
|
51
|
-
export {};
|
|
52
56
|
`;
|
|
53
57
|
fs_1.default.writeFileSync(path_1.default.join(outputPath, "global.types.d.ts"), content);
|
|
54
58
|
}
|
|
@@ -73,6 +77,7 @@ export {};
|
|
|
73
77
|
const newScripts = {
|
|
74
78
|
"test": `k6 run generated/test.generated.${lang}`,
|
|
75
79
|
"test:ui": `K6_BROWSER_ENABLED=true k6 run generated/test.generated.${lang}`,
|
|
80
|
+
"test:ui:headed": `K6_BROWSER_HEADLESS=false K6_BROWSER_ENABLED=true k6 run generated/test.generated.${lang}`,
|
|
76
81
|
"test:api": `K6_BROWSER_ENABLED=false k6 run generated/test.generated.${lang}`,
|
|
77
82
|
"generate": `k6-cucumber-steps generate -l ${lang}`,
|
|
78
83
|
"generate:ui": `k6-cucumber-steps generate -l ${lang} --tags browser`,
|
|
@@ -92,7 +97,7 @@ export {};
|
|
|
92
97
|
// Set top-level fields (only if not already set)
|
|
93
98
|
pkg.name = pkg.name || "k6-cucumber-test-project";
|
|
94
99
|
pkg.version = pkg.version || "1.0.0";
|
|
95
|
-
pkg.description = pkg.description || "
|
|
100
|
+
pkg.description = pkg.description || "k6 test project with Cucumber integration";
|
|
96
101
|
pkg.main = pkg.main || (config.language === "ts" ? "src/test.ts" : "test.js");
|
|
97
102
|
pkg.author = pkg.author || config.author;
|
|
98
103
|
pkg.license = pkg.license || "MIT";
|
|
@@ -170,7 +175,7 @@ Feature: Authentication Examples
|
|
|
170
175
|
|
|
171
176
|
Scenario: Authenticate via Client Credentials (OAuth2 Simulation)
|
|
172
177
|
# Added "as form" at the end to trigger urlencoding
|
|
173
|
-
When I authenticate with the following url and request body as "service_account"
|
|
178
|
+
When I authenticate with the following url and request body as "service_account" with "form":
|
|
174
179
|
| endpoint | client_id | client_secret | grant_type |
|
|
175
180
|
| /token | k6-cucumber-steps | 40d595cb79978e9c2cccc61e4fa972fd | client_credentials |
|
|
176
181
|
And I store "access_token" in "data/service_account.json"
|
|
@@ -189,14 +194,22 @@ Feature: Comprehensive API Testing
|
|
|
189
194
|
|
|
190
195
|
Background:
|
|
191
196
|
Given the base URL is "https://jsonplaceholder.typicode.com"
|
|
192
|
-
And I set the default headers:
|
|
193
|
-
|
|
194
|
-
|
|
197
|
+
#And I set the default headers:
|
|
198
|
+
# | Content-Type | Accept |
|
|
199
|
+
# | application/json | application/json |
|
|
200
|
+
|
|
201
|
+
Scenario: Get user with default headers
|
|
202
|
+
When I make a GET request to "/users/1"
|
|
195
203
|
|
|
196
|
-
|
|
204
|
+
Scenario: Get user with custom headers
|
|
205
|
+
When I make a GET request to "/users/1" with headers:
|
|
206
|
+
| Authorization | Content-Type |
|
|
207
|
+
| Bearer abc123 | application/json |
|
|
208
|
+
|
|
209
|
+
@group:user-api @threshold:http_req_duration=p(95)<1000
|
|
197
210
|
Scenario: Get specific user details
|
|
198
211
|
When I make a GET request to "/users/1"
|
|
199
|
-
Then the response status should be 200
|
|
212
|
+
Then the response status should be "200"
|
|
200
213
|
And the response should contain "name"
|
|
201
214
|
|
|
202
215
|
@group:load-test @stages:0s-0,20s-10,30s-10,10s-0
|
|
@@ -206,9 +219,9 @@ Feature: Comprehensive API Testing
|
|
|
206
219
|
|
|
207
220
|
Examples:
|
|
208
221
|
| userId | expectedStatus |
|
|
209
|
-
| 1 | 200 |
|
|
210
|
-
| 5 | 200 |
|
|
211
|
-
| 999 | 404 |
|
|
222
|
+
| 1 | "200" |
|
|
223
|
+
| 5 | "200" |
|
|
224
|
+
| 999 | "404" |
|
|
212
225
|
|
|
213
226
|
@group:post-api
|
|
214
227
|
Scenario: Create a post with bulk data
|
|
@@ -221,23 +234,25 @@ Feature: Comprehensive API Testing
|
|
|
221
234
|
}
|
|
222
235
|
"""
|
|
223
236
|
When I make a POST request to "/posts"
|
|
224
|
-
Then the response status should be 201
|
|
237
|
+
Then the response status should be "201"
|
|
225
238
|
`;
|
|
226
239
|
fs_1.default.writeFileSync(path_1.default.join(outputPath, "features", "sample.feature"), sampleFeature);
|
|
227
240
|
}
|
|
228
241
|
generateBrowserSampleFeature(outputPath) {
|
|
229
|
-
const content = `@iterations:1
|
|
242
|
+
const content = `@iterations:1 @browser
|
|
230
243
|
Feature: Comprehensive UI Automation on DemoQA
|
|
231
244
|
|
|
232
245
|
Background:
|
|
233
246
|
Given the base URL is "https://demoqa.com"
|
|
234
247
|
|
|
235
|
-
|
|
248
|
+
|
|
236
249
|
Scenario: Fill and submit the practice form successfully
|
|
237
250
|
When I navigate to the "/automation-practice-form" page
|
|
238
251
|
And I fill the field "#firstName" with "Paschal"
|
|
239
252
|
And I fill the field "#lastName" with "Enyimiri"
|
|
240
253
|
And I fill the field "#userEmail" with "paschal.cheps@example.com"
|
|
254
|
+
And I get element by selector "label[for='gender-radio-1']"
|
|
255
|
+
And I click
|
|
241
256
|
And I click on exact text "Male"
|
|
242
257
|
And I fill the field "#userNumber" with "0801234567"
|
|
243
258
|
And I wait "1" seconds
|
|
@@ -251,90 +266,86 @@ Feature: Comprehensive UI Automation on DemoQA
|
|
|
251
266
|
And I click on the element "#submit"
|
|
252
267
|
Then I should see the text "Thanks for submitting the form"
|
|
253
268
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
269
|
+
# Scenario: Interact using multiple locator strategies
|
|
270
|
+
# When I navigate to the "/automation-practice-form" page
|
|
271
|
+
# # By ID
|
|
272
|
+
# And I find element by Id "firstName"
|
|
273
|
+
# # By placeholder
|
|
274
|
+
# And I find input element by placeholder text "First Name"
|
|
275
|
+
# # By label/name (accessible)
|
|
276
|
+
# And I find element by name "First Name"
|
|
277
|
+
# # By role + name
|
|
278
|
+
# And I find element by role "textbox" "First Name"
|
|
279
|
+
# # By button text
|
|
280
|
+
# And I find button by text "Submit"
|
|
281
|
+
# # By value attribute
|
|
282
|
+
# And I find element by value "Submit"
|
|
283
|
+
# # Assertion
|
|
284
|
+
# And I should see the text "Student Registration Form"
|
|
285
|
+
|
|
286
|
+
# Scenario: Wait and validate dynamic content reliably
|
|
287
|
+
# When I navigate to the "/automation-practice-form" page
|
|
288
|
+
# And I wait "1" seconds
|
|
289
|
+
# And I should see the element "#firstName"
|
|
290
|
+
# And I should see the text "Practice Form"
|
|
291
|
+
# And I should not see the text "Dropped!"
|
|
292
|
+
|
|
293
|
+
# Scenario: Validate page metadata after navigation
|
|
294
|
+
# When I navigate to the "/automation-practice-form" page
|
|
295
|
+
# And the current URL should contain "automation-practice-form"
|
|
296
|
+
# And the page title should be "DEMOQA"
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
# Scenario: Find and interact using value / role
|
|
300
|
+
# Given the base URL is "https://demoqa.com"
|
|
301
|
+
# When I navigate to the "/automation-practice-form" page
|
|
302
|
+
|
|
303
|
+
# # Find by value attribute
|
|
304
|
+
# And I find element by value "Submit"
|
|
305
|
+
# And I click
|
|
306
|
+
|
|
307
|
+
# # Find by role (button with text)
|
|
308
|
+
# And I find element by role "button" "Submit"
|
|
309
|
+
# And I click
|
|
310
|
+
|
|
311
|
+
# # Find by role (heading)
|
|
312
|
+
# And I find element by role "heading" "Practice Form"
|
|
313
|
+
# And I should see the text "Practice Form"
|
|
314
|
+
|
|
315
|
+
# # Find by role (textbox)
|
|
316
|
+
# And I find element by role "textbox" "First Name"
|
|
317
|
+
# And I fill "Paschal"
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
# Scenario: Wait and find elements
|
|
321
|
+
# Given the base URL is "https://demoqa.com"
|
|
322
|
+
# When I navigate to the "/automation-practice-form" page
|
|
323
|
+
# And I wait "2" seconds
|
|
324
|
+
# And I find input element by placeholder text "First Name"
|
|
325
|
+
# And I find button by text "Submit"
|
|
326
|
+
# And I find element by Id "firstName"
|
|
327
|
+
# And I find elements by text "Name"
|
|
328
|
+
|
|
329
|
+
# Scenario: Interact with repeated elements
|
|
330
|
+
# When I navigate to the "/elements" page
|
|
331
|
+
# And I fill the field "#input-field" with "First" "1"
|
|
332
|
+
# And I fill the field "#input-field" with "Second" "2"
|
|
333
|
+
# And I click on the element "button[type='submit']" "2"
|
|
334
|
+
# # For single element (default = 1st)
|
|
335
|
+
# And I click on the element "button"
|
|
336
|
+
|
|
337
|
+
# # For nth element
|
|
338
|
+
# And I click on the element "button" "2"
|
|
339
|
+
# And I fill the field "#email" with "test@example.com" "1"
|
|
340
|
+
|
|
341
|
+
# Scenario: Handle alert confirmation
|
|
342
|
+
# When I navigate to the "/alerts" page
|
|
343
|
+
# And I click on the element "#alertButton"
|
|
344
|
+
# # Add step to accept alert if needed
|
|
345
|
+
# Then I should see the text "You clicked a button"
|
|
291
346
|
|
|
292
|
-
# Find by role (button with text)
|
|
293
|
-
And I find element by role "button" "Submit"
|
|
294
|
-
And I click
|
|
295
|
-
|
|
296
|
-
# Find by role (heading)
|
|
297
|
-
And I find element by role "heading" "Practice Form"
|
|
298
|
-
And I should see the text "Practice Form"
|
|
299
347
|
|
|
300
|
-
# Find by role (textbox)
|
|
301
|
-
And I find element by role "textbox" "First Name"
|
|
302
|
-
And I fill "Paschal"
|
|
303
348
|
|
|
304
|
-
|
|
305
|
-
Scenario: Wait and find elements
|
|
306
|
-
Given the base URL is "https://demoqa.com"
|
|
307
|
-
When I navigate to the "/automation-practice-form" page
|
|
308
|
-
And I wait "2" seconds
|
|
309
|
-
And I find input element by placeholder text "First Name"
|
|
310
|
-
And I find button by text "Submit"
|
|
311
|
-
And I find element by Id "firstName"
|
|
312
|
-
And I find elements by text "Name"
|
|
313
|
-
|
|
314
|
-
Scenario: Interact with repeated elements
|
|
315
|
-
When I navigate to the "/elements" page
|
|
316
|
-
And I fill the 1st "#input-field" with "First"
|
|
317
|
-
And I fill the 2nd "#input-field" with "Second"
|
|
318
|
-
And I click the 2nd "button[type='submit']"
|
|
319
|
-
Then I should see the 1st ".success-message"
|
|
320
|
-
# For single element (default = 1st)
|
|
321
|
-
And I click on the element "button"
|
|
322
|
-
|
|
323
|
-
# For nth element
|
|
324
|
-
And I click on the element "button" "2"
|
|
325
|
-
And I fill the field "#email" with "test@example.com" "1"
|
|
326
|
-
|
|
327
|
-
Scenario: Handle alert confirmation
|
|
328
|
-
When I navigate to the "/alerts" page
|
|
329
|
-
And I click on the element "#alertButton"
|
|
330
|
-
# Add step to accept alert if needed
|
|
331
|
-
Then I should see the text "You clicked a button"
|
|
332
|
-
|
|
333
|
-
Scenario: Drag and drop
|
|
334
|
-
When I navigate to the "/droppable" page
|
|
335
|
-
And I find element by Id "draggable"
|
|
336
|
-
And I drag to "#droppable"
|
|
337
|
-
Then I should see the text "Dropped!"
|
|
338
349
|
`;
|
|
339
350
|
fs_1.default.writeFileSync(path_1.default.join(outputPath, "features", "browserSample.feature"), content);
|
|
340
351
|
}
|
|
@@ -342,24 +353,27 @@ Feature: Comprehensive UI Automation on DemoQA
|
|
|
342
353
|
const isTS = config.language === "ts";
|
|
343
354
|
const stepExtension = isTS ? ".ts" : ".js";
|
|
344
355
|
// Type helpers
|
|
345
|
-
const
|
|
346
|
-
const anyType = isTS ? ": any[]" : "";
|
|
356
|
+
const anyType = isTS ? ": any" : "";
|
|
347
357
|
const stringType = isTS ? ": string" : "";
|
|
348
358
|
const mixedType = isTS ? ": string | number" : "";
|
|
359
|
+
const headersType = isTS ? " as Record<string, string>" : "";
|
|
360
|
+
const domElementType = isTS ? ": Element" : "";
|
|
361
|
+
const domElementArrayType = isTS ? ": Element[]" : "";
|
|
349
362
|
const sampleSteps = `import http from "k6/http";
|
|
350
363
|
import { check, sleep, group } from "k6";
|
|
351
364
|
|
|
352
365
|
let baseUrl = "";
|
|
353
|
-
let defaultHeaders
|
|
366
|
+
let defaultHeaders = {
|
|
354
367
|
'Content-Type': 'application/json'
|
|
355
|
-
};
|
|
368
|
+
}${isTS ? ' as Record<string, string>' : ''};
|
|
356
369
|
|
|
357
370
|
/* ===== HTTP / API STEPS ===== */
|
|
358
371
|
|
|
359
372
|
export function theBaseUrlIs(url${stringType}) {
|
|
360
373
|
baseUrl = url.trim();
|
|
361
374
|
}
|
|
362
|
-
export
|
|
375
|
+
// export const iAuthenticateWithTheFollowingUrlAndRequestBodyAs = iAuthenticateWithTheFollowingUrlAndRequestBodyAsWith;
|
|
376
|
+
export function iAuthenticateWithTheFollowingUrlAndRequestBodyAsWith(
|
|
363
377
|
context${stringType},
|
|
364
378
|
formatOrTable${anyType},
|
|
365
379
|
maybeTable${anyType}
|
|
@@ -381,9 +395,10 @@ export function iAuthenticateWithTheFollowingUrlAndRequestBodyAs(
|
|
|
381
395
|
const { endpoint, ...payload } = row;
|
|
382
396
|
const url = \`\${baseUrl}\${endpoint}\`;
|
|
383
397
|
|
|
384
|
-
let body;
|
|
385
|
-
let params = {
|
|
386
|
-
|
|
398
|
+
let body${anyType};
|
|
399
|
+
let params = {
|
|
400
|
+
headers: {}${headersType}
|
|
401
|
+
};
|
|
387
402
|
if (format === 'form') {
|
|
388
403
|
params.headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
389
404
|
body = Object.assign({}, payload);
|
|
@@ -399,30 +414,39 @@ export function iAuthenticateWithTheFollowingUrlAndRequestBodyAs(
|
|
|
399
414
|
});
|
|
400
415
|
|
|
401
416
|
if (success) {
|
|
417
|
+
globalThis.lastResponse = response;
|
|
402
418
|
try {
|
|
403
419
|
const parsed = response.json();
|
|
404
|
-
|
|
405
|
-
|
|
420
|
+
let keys${isTS ? ': string[]' : ''} = [];
|
|
421
|
+
if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
422
|
+
keys = Object.keys(parsed);
|
|
423
|
+
}
|
|
424
|
+
console.log(\`\✅ \${context} Response Captured. Keys: \${keys.join(', ')}\`);
|
|
406
425
|
} catch (e) {
|
|
407
|
-
console.error(
|
|
426
|
+
console.error(\`\❌ Failed to parse JSON response for \${context}: \${response.body}\`);
|
|
408
427
|
globalThis.lastResponse = null;
|
|
409
428
|
}
|
|
410
429
|
} else {
|
|
411
|
-
console.error(
|
|
430
|
+
console.error(\`\❌ Auth failed for \${context}. Status: \${response.status}\`);
|
|
412
431
|
globalThis.lastResponse = null;
|
|
413
432
|
}
|
|
414
433
|
}
|
|
415
434
|
|
|
416
|
-
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* When I authenticate with the following url and request body as {string} (as {string})
|
|
438
|
+
* Default format is JSON — no need to write "as json"
|
|
439
|
+
* Use "as form" to switch to x-www-form-urlencoded
|
|
440
|
+
*/
|
|
441
|
+
export function iAuthenticateWithTheFollowingUrlAndRequestBodyAs(
|
|
417
442
|
context${stringType},
|
|
418
443
|
formatOrTable${anyType},
|
|
419
|
-
maybeTable${
|
|
444
|
+
maybeTable${isTS ? '?: any' : ''}
|
|
420
445
|
) {
|
|
421
446
|
let format = 'json';
|
|
422
447
|
let dataTable;
|
|
423
448
|
|
|
424
449
|
if (maybeTable === undefined) {
|
|
425
|
-
format = 'json';
|
|
426
450
|
dataTable = formatOrTable;
|
|
427
451
|
} else {
|
|
428
452
|
format = formatOrTable;
|
|
@@ -433,10 +457,12 @@ export function iAuthenticateWithTheFollowingUrlAndRequestBodyAsAs(
|
|
|
433
457
|
|
|
434
458
|
const row = dataTable[0];
|
|
435
459
|
const { endpoint, ...payload } = row;
|
|
436
|
-
const url = \`\${baseUrl}\${endpoint}\`;
|
|
460
|
+
const url = \`\${ baseUrl }\${ endpoint }\`;
|
|
437
461
|
|
|
438
|
-
|
|
439
|
-
let params = {
|
|
462
|
+
let body${anyType};
|
|
463
|
+
let params = {
|
|
464
|
+
headers: {}${headersType}
|
|
465
|
+
};
|
|
440
466
|
|
|
441
467
|
if (format === 'form') {
|
|
442
468
|
params.headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
@@ -449,20 +475,24 @@ export function iAuthenticateWithTheFollowingUrlAndRequestBodyAsAs(
|
|
|
449
475
|
const response = http.post(url, body, params);
|
|
450
476
|
|
|
451
477
|
const success = check(response, {
|
|
452
|
-
[\`Auth successful
|
|
478
|
+
[\`Auth successful(\${ format })\`]: (r) => r.status === 200 || r.status === 201
|
|
453
479
|
});
|
|
454
480
|
|
|
455
481
|
if (success) {
|
|
482
|
+
globalThis.lastResponse = response;
|
|
456
483
|
try {
|
|
457
484
|
const parsed = response.json();
|
|
458
|
-
|
|
459
|
-
|
|
485
|
+
let keys${isTS ? ': string[]' : ''} = [];
|
|
486
|
+
if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
487
|
+
keys = Object.keys(parsed);
|
|
488
|
+
}
|
|
489
|
+
console.log(\`\✅ \${context} Response Captured. Keys: \${keys.join(', ')}\`);
|
|
460
490
|
} catch (e) {
|
|
461
|
-
console.error(
|
|
491
|
+
console.error(\`\❌ Failed to parse JSON response for \${context}: \${response.body}\`);
|
|
462
492
|
globalThis.lastResponse = null;
|
|
463
493
|
}
|
|
464
494
|
} else {
|
|
465
|
-
console.error(
|
|
495
|
+
console.error(\`\❌ Auth failed for \${context}. Status: \${response.status}\`);
|
|
466
496
|
globalThis.lastResponse = null;
|
|
467
497
|
}
|
|
468
498
|
}
|
|
@@ -471,7 +501,7 @@ export function iAuthenticateWithTheFollowingUrlAndRequestBodyAsAs(
|
|
|
471
501
|
* And I store "response.path" in "data/file.json"
|
|
472
502
|
* Example: And I store "access_token" in "data/user.json"
|
|
473
503
|
*/
|
|
474
|
-
export function iStoreIn(jsonPath, fileName) {
|
|
504
|
+
export function iStoreIn(jsonPath${anyType}, fileName${anyType}) {
|
|
475
505
|
const responseData = globalThis.lastResponse;
|
|
476
506
|
|
|
477
507
|
if (!responseData) {
|
|
@@ -479,13 +509,27 @@ export function iStoreIn(jsonPath, fileName) {
|
|
|
479
509
|
return;
|
|
480
510
|
}
|
|
481
511
|
|
|
482
|
-
|
|
483
|
-
|
|
512
|
+
let parsedBody;
|
|
513
|
+
try {
|
|
514
|
+
// If it's a k6 Response object, parse it
|
|
515
|
+
if (typeof responseData.json === 'function') {
|
|
516
|
+
parsedBody = responseData.json();
|
|
517
|
+
} else {
|
|
518
|
+
// Already parsed (e.g., from browser steps)
|
|
519
|
+
parsedBody = responseData;
|
|
520
|
+
}
|
|
521
|
+
} catch (e) {
|
|
522
|
+
console.error('❌ Failed to parse response as JSON:', e.message || e);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Navigate JSON path (e.g., "access_token" or "user.token")
|
|
527
|
+
const value = jsonPath.split('.').reduce((acc${anyType}, key${anyType}) => {
|
|
484
528
|
return acc && acc[key] !== undefined ? acc[key] : undefined;
|
|
485
|
-
},
|
|
529
|
+
}, parsedBody);
|
|
486
530
|
|
|
487
531
|
if (value === undefined) {
|
|
488
|
-
console.error(\`❌ Path "\${jsonPath}" not found in response. Keys:\`, Object.keys(
|
|
532
|
+
console.error(\`❌ Path "\${jsonPath}" not found in response. Keys:\`, Object.keys(parsedBody));
|
|
489
533
|
return;
|
|
490
534
|
}
|
|
491
535
|
|
|
@@ -494,17 +538,17 @@ export function iStoreIn(jsonPath, fileName) {
|
|
|
494
538
|
globalThis.savedTokens[fileName] = value;
|
|
495
539
|
|
|
496
540
|
// Also store alias (e.g., 'user' from 'data/user.json')
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
globalThis.savedTokens[alias] = value;
|
|
541
|
+
const alias = fileName.split(/[\\\\/]/).pop()?.replace(/\\.json$/, '') || fileName;
|
|
542
|
+
globalThis.savedTokens[alias] = value;
|
|
500
543
|
|
|
501
544
|
console.log(\`✅ Staged token for "\${fileName}": \${typeof value === 'string' ? '***' : JSON.stringify(value)}\`);
|
|
545
|
+
console.log('🔍 Parsing response type:', typeof responseData.json === 'function' ? 'k6 Response' : 'Parsed JSON');
|
|
502
546
|
}
|
|
503
547
|
/**
|
|
504
548
|
* Background: I am authenticated as a "user"
|
|
505
549
|
* Applies stored token to default headers
|
|
506
550
|
*/
|
|
507
|
-
export function iAmAuthenticatedAsA(userType) {
|
|
551
|
+
export function iAmAuthenticatedAsA(userType${anyType}) {
|
|
508
552
|
const token = globalThis.savedTokens?.[\`data/\${userType}.json\`] ||
|
|
509
553
|
globalThis.savedTokens?.[userType];
|
|
510
554
|
|
|
@@ -514,10 +558,101 @@ export function iAmAuthenticatedAsA(userType) {
|
|
|
514
558
|
} else {
|
|
515
559
|
console.warn(\`⚠️ No token found for $\{userType}\`);
|
|
516
560
|
}
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* And I set the default headers:
|
|
564
|
+
*/
|
|
565
|
+
export function iSetTheDefaultHeaders(data${anyType}) {
|
|
566
|
+
if (data && data.length > 0) {
|
|
567
|
+
// Merge new headers into existing ones
|
|
568
|
+
Object.assign(defaultHeaders, data[0]);
|
|
569
|
+
// OR if you want to fully replace, document it clearly
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* When I make a GET request to "..."
|
|
574
|
+
*/
|
|
575
|
+
// For requests WITHOUT custom headers
|
|
576
|
+
export function iMakeAGetRequestTo(endpoint${anyType}) {
|
|
577
|
+
return iMakeAGetRequestToWithHeaders(endpoint, []);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// For requests WITH headers
|
|
581
|
+
export function iMakeAGetRequestToWithHeaders(endpoint${anyType}, headersTable${anyType}) {
|
|
582
|
+
const url = \`\${baseUrl}\${endpoint}\`;
|
|
583
|
+
let requestHeaders = { ...defaultHeaders };
|
|
584
|
+
if (headersTable?.length > 0) {
|
|
585
|
+
Object.assign(requestHeaders, headersTable[0]);
|
|
586
|
+
}
|
|
587
|
+
const res = http.get(url, { headers: requestHeaders });
|
|
588
|
+
check(res, {
|
|
589
|
+
'headers sent correctly': (r) => {
|
|
590
|
+
const sent = r.request.headers;
|
|
591
|
+
return Object.keys(requestHeaders).every(k => {
|
|
592
|
+
const s = sent[k];
|
|
593
|
+
const e = requestHeaders[k];
|
|
594
|
+
return Array.isArray(s) ? s.includes(e) : s === e;
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
globalThis.lastResponse = res;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Then the response status should be ...
|
|
603
|
+
*/
|
|
604
|
+
export function theResponseStatusShouldBe(expectedStatus${anyType}) {
|
|
605
|
+
const res = globalThis.lastResponse;
|
|
606
|
+
if (!res) {
|
|
607
|
+
console.error('❌ No lastResponse found!');
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
const status = parseInt(expectedStatus, 10);
|
|
611
|
+
check(res, {
|
|
612
|
+
[\`status is \${expectedStatus}\`]: (r) => r.status === status
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
export function theResponseShouldContain(field${stringType}) {
|
|
617
|
+
const res = globalThis.lastResponse;
|
|
618
|
+
if (!res) {
|
|
619
|
+
console.error('❌ No lastResponse found!');
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
try {
|
|
623
|
+
const body = res.json();
|
|
624
|
+
check(res, {
|
|
625
|
+
[\`response contains "\${field}"\`]: () => body[field] !== undefined
|
|
626
|
+
});
|
|
627
|
+
} catch (e${anyType}) {
|
|
628
|
+
check(res, {
|
|
629
|
+
[\`response contains "\${field}"\`]: () => false
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Given I have the following post data:
|
|
636
|
+
*/
|
|
637
|
+
export function iHaveTheFollowingPostData(data${stringType}) {
|
|
638
|
+
globalThis.lastPostData = data;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* When I make a POST request to "..."
|
|
643
|
+
*/
|
|
644
|
+
export function iMakeAPostRequestTo(endpoint${stringType}) {
|
|
645
|
+
const url = \`\${baseUrl}\${endpoint}\`;
|
|
646
|
+
const payload = globalThis.lastPostData || '{}'; // ← Use stored data
|
|
647
|
+
const response = http.post(url, payload, { headers: defaultHeaders });
|
|
648
|
+
globalThis.lastResponse = response;
|
|
649
|
+
check(response, {
|
|
650
|
+
'POST status is 201': (r) => r.status === 201
|
|
651
|
+
});
|
|
517
652
|
}
|
|
518
653
|
/* ===== BROWSER STEPS ===== */
|
|
519
654
|
|
|
520
|
-
export async function iNavigateToThePage(page, url${stringType}) {
|
|
655
|
+
export async function iNavigateToThePage(page${anyType}, url${stringType}) {
|
|
521
656
|
let effectiveBase = baseUrl;
|
|
522
657
|
if (typeof effectiveBase !== 'string' || effectiveBase.trim() === '') {
|
|
523
658
|
console.warn('Invalid baseUrl detected:', baseUrl, '— using fallback');
|
|
@@ -528,7 +663,7 @@ export async function iNavigateToThePage(page, url${stringType}) {
|
|
|
528
663
|
await page.goto(fullUrl, { waitUntil: 'networkidle', timeout: 60000 });
|
|
529
664
|
}
|
|
530
665
|
|
|
531
|
-
export async function iClickTheButton(page, selector${stringType}) {
|
|
666
|
+
export async function iClickTheButton(page${anyType}, selector${stringType}) {
|
|
532
667
|
await page.locator(selector).click();
|
|
533
668
|
}
|
|
534
669
|
|
|
@@ -543,32 +678,54 @@ export async function iShouldSeeTheText(page${anyType}, expectedText${stringType
|
|
|
543
678
|
await locator.waitFor({ state: 'visible', timeout: 30000 });
|
|
544
679
|
const count = await locator.count();
|
|
545
680
|
console.log(\`Found \${count} visible elements matching "\${expectedText}"\`);
|
|
546
|
-
check(
|
|
547
|
-
[\`Text/heading containing "\${expectedText}" is visible\`]:
|
|
681
|
+
check(locator, {
|
|
682
|
+
[\`Text/heading containing "\${expectedText}" is visible\`]: () => count >= 1
|
|
548
683
|
});
|
|
549
|
-
} catch (e) {
|
|
684
|
+
} catch (e${anyType}) {
|
|
550
685
|
console.error(\`Text wait failed for "\${expectedText}":\`, e.message || e);
|
|
551
|
-
|
|
552
|
-
|
|
686
|
+
check(locator, {
|
|
687
|
+
[\`Text/heading containing "\${expectedText}" is visible\`]: () => false
|
|
688
|
+
}); }
|
|
553
689
|
}
|
|
554
690
|
|
|
555
691
|
/* === Additional Browser Steps === */
|
|
556
692
|
|
|
557
|
-
export async function iClickOnTheElement(page, selector) {
|
|
558
|
-
const locator =
|
|
559
|
-
|
|
693
|
+
export async function iClickOnTheElement(page${anyType}, selector${stringType}, nth${isTS ? '?: any' : ''}) {
|
|
694
|
+
const locator = nth
|
|
695
|
+
? page.locator(selector).nth(parseInt(nth) - 1)
|
|
696
|
+
: page.locator(selector);
|
|
697
|
+
await locator.waitFor({ state: 'visible', timeout: 15000 });
|
|
560
698
|
await locator.click();
|
|
561
|
-
|
|
699
|
+
console.log(\`✅ Clicked element: \${selector}\`);
|
|
700
|
+
|
|
562
701
|
}
|
|
563
702
|
|
|
564
|
-
export async function iFillTheFieldWith(
|
|
565
|
-
|
|
703
|
+
export async function iFillTheFieldWith(
|
|
704
|
+
page${anyType},
|
|
705
|
+
selector${stringType},
|
|
706
|
+
value${stringType},
|
|
707
|
+
nth${isTS ? '?: any' : ''}
|
|
708
|
+
) {
|
|
709
|
+
const locator = nth
|
|
710
|
+
? page.locator(selector).nth(parseInt(nth) - 1)
|
|
711
|
+
: page.locator(selector);
|
|
566
712
|
await locator.waitFor({ state: 'visible', timeout: 15000 });
|
|
567
713
|
await locator.fill(value);
|
|
568
714
|
console.log(\`✅ Filled field \${selector} with "\${value}"\`);
|
|
569
715
|
}
|
|
570
|
-
|
|
571
|
-
|
|
716
|
+
/**
|
|
717
|
+
* And I fill "..."
|
|
718
|
+
* (short version – assumes last focused element or needs locator before)
|
|
719
|
+
* Better to use the full version with selector
|
|
720
|
+
*/
|
|
721
|
+
export async function iFill(
|
|
722
|
+
page${anyType},
|
|
723
|
+
value${stringType}
|
|
724
|
+
) {
|
|
725
|
+
await page.keyboard.type(value);
|
|
726
|
+
console.log(\`✅ Filled value: "\${value}" (using keyboard)\`);
|
|
727
|
+
}
|
|
728
|
+
export async function thePageTitleShouldBe(page${anyType}, expectedTitle${stringType}) {
|
|
572
729
|
await page.waitForLoadState('networkidle');
|
|
573
730
|
const title = await page.title();
|
|
574
731
|
check(title, {
|
|
@@ -576,14 +733,14 @@ export async function thePageTitleShouldBe(page, expectedTitle${stringType}) {
|
|
|
576
733
|
});
|
|
577
734
|
}
|
|
578
735
|
|
|
579
|
-
export async function theCurrentUrlShouldContain(page, expectedFragment${stringType}) {
|
|
736
|
+
export async function theCurrentUrlShouldContain(page${anyType}, expectedFragment${stringType}) {
|
|
580
737
|
const url = page.url();
|
|
581
738
|
check(url, {
|
|
582
739
|
[\`URL contains "\${expectedFragment}"\`]: (u) => u.includes(expectedFragment)
|
|
583
740
|
});
|
|
584
741
|
}
|
|
585
742
|
|
|
586
|
-
export async function iShouldSeeTheElement(page, selector) {
|
|
743
|
+
export async function iShouldSeeTheElement(page${anyType}, selector${stringType}) {
|
|
587
744
|
const locator = page.locator(selector);
|
|
588
745
|
await locator.waitFor({ state: 'visible', timeout: 20000 });
|
|
589
746
|
const isVisible = await locator.isVisible();
|
|
@@ -591,7 +748,7 @@ export async function iShouldSeeTheElement(page, selector) {
|
|
|
591
748
|
console.log(\`✅ Verified visibility of element: \${selector}\`);
|
|
592
749
|
}
|
|
593
750
|
|
|
594
|
-
export async function iShouldNotSeeTheText(page, text${stringType}) {
|
|
751
|
+
export async function iShouldNotSeeTheText(page${anyType}, text${stringType}) {
|
|
595
752
|
const locator = page.getByText(text, { exact: false });
|
|
596
753
|
const isHidden = (await locator.count()) === 0 || !(await locator.isVisible());
|
|
597
754
|
check(isHidden, {
|
|
@@ -599,74 +756,13 @@ export async function iShouldNotSeeTheText(page, text${stringType}) {
|
|
|
599
756
|
});
|
|
600
757
|
}
|
|
601
758
|
|
|
602
|
-
export async function iSelectFromTheDropdown(page, option${stringType}, selector${stringType}) {
|
|
759
|
+
export async function iSelectFromTheDropdown(page${anyType}, option${stringType}, selector${stringType}) {
|
|
603
760
|
const locator = page.locator(selector);
|
|
604
761
|
await locator.selectOption(option);
|
|
605
762
|
console.log(\`Selected "\${option}" from dropdown "\${selector}"\`);
|
|
606
763
|
}
|
|
607
|
-
/**
|
|
608
|
-
* When I drag the element "..." to "..."
|
|
609
|
-
* Example: I drag the element "#draggable" to "#droppable"
|
|
610
|
-
*/
|
|
611
|
-
export async function iDragTheElementTo(page${anyType}, sourceselector${stringType}, targetselector${stringType}) {
|
|
612
|
-
try {
|
|
613
|
-
const source = page.locator(sourceSelector);
|
|
614
|
-
const target = page.locator(targetSelector);
|
|
615
|
-
|
|
616
|
-
// Wait for both elements to be visible and stable
|
|
617
|
-
await source.waitFor({ state: 'visible', timeout: 15000 });
|
|
618
|
-
await target.waitFor({ state: 'visible', timeout: 15000 });
|
|
619
|
-
|
|
620
|
-
// Perform the drag-and-drop
|
|
621
|
-
await source.dragTo(target, {
|
|
622
|
-
// Optional: force the action if element is covered / small
|
|
623
|
-
force: true,
|
|
624
|
-
// Optional: timeout for the whole operation
|
|
625
|
-
timeout: 30000,
|
|
626
|
-
});
|
|
627
|
-
|
|
628
|
-
console.log(\`Successfully dragged "\${sourceSelector}" to "\${targetSelector}"\`);
|
|
629
|
-
|
|
630
|
-
// Optional: small assertion that drop area changed (if it has visual feedback)
|
|
631
|
-
// await expect(target).toHaveText('Dropped!'); // if you have expect imported
|
|
632
|
-
} catch (error) {
|
|
633
|
-
console.error(\`Drag failed from "\${sourceSelector}" to "\${targetSelector}":\`, error.message || error);
|
|
634
|
-
throw error; // re-throw to fail the iteration
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
/**
|
|
638
|
-
* And I drag to "#droppable"
|
|
639
|
-
* This step assumes the previous step returned a source locator
|
|
640
|
-
* Example usage:
|
|
641
|
-
* When I get element by selector "#draggable"
|
|
642
|
-
* And I drag to "#droppable"
|
|
643
|
-
*/
|
|
644
|
-
${isTS
|
|
645
|
-
? `export async function iDragTo(page${anyType}, targetselector${stringType}, sourceLocator?${anyType}) {`
|
|
646
|
-
: `export async function iDragTo(page, targetSelector, sourceLocator) {`}
|
|
647
764
|
|
|
648
|
-
|
|
649
|
-
// If no sourceLocator was passed (previous step didn't return it), fail early
|
|
650
|
-
if (!sourceLocator) {
|
|
651
|
-
throw new Error("No source locator provided. Did you use 'I get element by selector' before this step?");
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
const target = page.locator(targetSelector);
|
|
655
|
-
await target.waitFor({ state: 'visible', timeout: 15000 });
|
|
656
|
-
|
|
657
|
-
// Perform drag-and-drop from source → target
|
|
658
|
-
await sourceLocator.dragTo(target, {
|
|
659
|
-
force: true, // helpful if element is partially covered
|
|
660
|
-
timeout: 30000,
|
|
661
|
-
});
|
|
662
|
-
|
|
663
|
-
console.log(\`Dragged source element to target: \${targetSelector}\`);
|
|
664
|
-
} catch (error) {
|
|
665
|
-
console.error(\`Drag to "\${targetSelector}" failed:\`, error.message || error);
|
|
666
|
-
throw error; // fail the iteration
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
export async function iWaitForTheElementToBeVisible(page, selector${stringType}) {
|
|
765
|
+
export async function iWaitForTheElementToBeVisible(page${anyType}, selector${stringType}) {
|
|
670
766
|
const locator = page.locator(selector);
|
|
671
767
|
await locator.waitFor({ state: 'visible', timeout: 30000 });
|
|
672
768
|
}
|
|
@@ -682,7 +778,7 @@ export async function iGetElementBySelector(page${anyType}, selector${stringType
|
|
|
682
778
|
}
|
|
683
779
|
|
|
684
780
|
/**
|
|
685
|
-
* And I find element by
|
|
781
|
+
* And I find element by label "Male"
|
|
686
782
|
* (uses getByLabel – good for form labels)
|
|
687
783
|
*/
|
|
688
784
|
export async function iFindElementByLabel(page${anyType}, labelText${stringType}) {
|
|
@@ -721,30 +817,33 @@ export async function iFindElementByTextarea(page${anyType}, placeholderOrLabel$
|
|
|
721
817
|
*/
|
|
722
818
|
export async function iClick(page${anyType}) {
|
|
723
819
|
try {
|
|
724
|
-
// Get
|
|
725
|
-
const
|
|
820
|
+
// Get bounding box of the focused element
|
|
821
|
+
const bbox = await page.evaluate(() => {
|
|
822
|
+
const el = document.activeElement;
|
|
823
|
+
if (!el || !el.getBoundingClientRect) return null;
|
|
824
|
+
const rect = el.getBoundingClientRect();
|
|
825
|
+
return {
|
|
826
|
+
x: rect.left + window.scrollX,
|
|
827
|
+
y: rect.top + window.scrollY,
|
|
828
|
+
width: rect.width,
|
|
829
|
+
height: rect.height
|
|
830
|
+
};
|
|
831
|
+
});
|
|
726
832
|
|
|
727
|
-
if (!
|
|
728
|
-
console.warn('No focused element found
|
|
833
|
+
if (!bbox || bbox.width === 0 || bbox.height === 0) {
|
|
834
|
+
console.warn('No clickable focused element found');
|
|
729
835
|
return;
|
|
730
836
|
}
|
|
731
837
|
|
|
732
|
-
//
|
|
733
|
-
const
|
|
734
|
-
|
|
735
|
-
// Ensure it's visible and clickable
|
|
736
|
-
await locator.waitFor({ state: 'visible', timeout: 10000 });
|
|
737
|
-
|
|
738
|
-
// Perform real mouse click
|
|
739
|
-
await locator.click({
|
|
740
|
-
force: true, // click even if slightly covered
|
|
741
|
-
timeout: 10000,
|
|
742
|
-
});
|
|
838
|
+
// Click at the center of the element
|
|
839
|
+
const x = bbox.x + bbox.width / 2;
|
|
840
|
+
const y = bbox.y + bbox.height / 2;
|
|
743
841
|
|
|
744
|
-
|
|
745
|
-
|
|
842
|
+
await page.mouse.click(x, y);
|
|
843
|
+
console.log('✅ Performed mouse click on focused element');
|
|
844
|
+
} catch (error${anyType}) {
|
|
746
845
|
console.error('Click failed on focused element:', error.message || error);
|
|
747
|
-
throw error;
|
|
846
|
+
throw error;
|
|
748
847
|
}
|
|
749
848
|
}
|
|
750
849
|
function getNth(locator${anyType}, nthStr${stringType}) {
|
|
@@ -820,10 +919,10 @@ export async function iFindElementsByText(page${anyType}, text${stringType}) {
|
|
|
820
919
|
* Uses getByRole('button')
|
|
821
920
|
*/
|
|
822
921
|
export async function iFindButtonByText(page${anyType}, buttonText${stringType}) {
|
|
823
|
-
const locator = page.
|
|
922
|
+
const locator = page.getByText(buttonText, { exact: false });
|
|
824
923
|
await locator.waitFor({ state: 'visible', timeout: 20000 });
|
|
825
924
|
const count = await locator.count();
|
|
826
|
-
console.log(\`Found \${count}
|
|
925
|
+
console.log(\`Found \${count} element(s) containing text "\${buttonText}"\`);
|
|
827
926
|
return locator;
|
|
828
927
|
}
|
|
829
928
|
/**
|
|
@@ -848,15 +947,18 @@ const locator = page.locator(\`input[value="\${valueText}"], textarea[value="\${
|
|
|
848
947
|
|
|
849
948
|
// Optional: log tag names of matches
|
|
850
949
|
if (count > 0) {
|
|
851
|
-
const tags = await locator.evaluateAll(els =>
|
|
950
|
+
const tags = await locator.evaluateAll((els${domElementArrayType}) =>
|
|
951
|
+
els.map((el${domElementType}) => el.tagName.toLowerCase())
|
|
952
|
+
);
|
|
852
953
|
console.log(\`Matching elements are: \${ tags.join(', ') } \`);
|
|
853
954
|
}
|
|
854
955
|
|
|
855
956
|
return locator;
|
|
856
|
-
} catch (error) {
|
|
957
|
+
} catch (error${anyType}) {
|
|
857
958
|
console.error(\`Could not find element by value "\${valueText}": \`, error.message || error);
|
|
858
|
-
|
|
859
|
-
|
|
959
|
+
check(locator, {
|
|
960
|
+
[\`Element with value "\${valueText}" is found\`]: () => false
|
|
961
|
+
}); throw error;
|
|
860
962
|
}
|
|
861
963
|
}
|
|
862
964
|
|
|
@@ -894,10 +996,11 @@ ${isTS
|
|
|
894
996
|
}
|
|
895
997
|
|
|
896
998
|
return locator;
|
|
897
|
-
} catch (error) {
|
|
999
|
+
} catch (error${anyType}) {
|
|
898
1000
|
console.error(\`Could not find element by role "\${roleName}": \`, error.message || error);
|
|
899
|
-
|
|
900
|
-
|
|
1001
|
+
check(locator, {
|
|
1002
|
+
[\`Element by role "\${roleName}" is found\`]: () => false
|
|
1003
|
+
}); throw error;
|
|
901
1004
|
}
|
|
902
1005
|
}
|
|
903
1006
|
/**
|
|
@@ -934,19 +1037,7 @@ export async function iFindElementById(page${anyType}, id${stringType}) {
|
|
|
934
1037
|
console.log(\`Found element by ID: \${selector}\`);
|
|
935
1038
|
return locator;
|
|
936
1039
|
}
|
|
937
|
-
/**
|
|
938
|
-
* And I click "N"st element by selector "..."
|
|
939
|
-
* Example: And I click "1"st element by selector ".btn"
|
|
940
|
-
*/
|
|
941
|
-
export async function iClickNthElementBySelector(page${anyType}, n${stringType}, selector${stringType}) {
|
|
942
|
-
const index = parseInt(n.replace(/\D/g, '')) - 1; // "1"st → 0, "2"nd → 1, etc.
|
|
943
|
-
if (isNaN(index)) throw new Error(\`Invalid nth value: \${ n } \`);
|
|
944
1040
|
|
|
945
|
-
const locator = page.locator(selector).nth(index);
|
|
946
|
-
await locator.waitFor({ state: 'visible', timeout: 15000 });
|
|
947
|
-
await locator.click();
|
|
948
|
-
console.log(\`Clicked the \${ n } element matching selector: \${ selector } \`);
|
|
949
|
-
}
|
|
950
1041
|
/**
|
|
951
1042
|
* And I click on exact text "..."
|
|
952
1043
|
* Clicks the first element that **exactly matches** the given visible text.
|
|
@@ -968,7 +1059,7 @@ export async function iClickOnExactText(page${anyType}, text${stringType}) {
|
|
|
968
1059
|
|
|
969
1060
|
await locator.first().click(); // safest: click first even if multiple
|
|
970
1061
|
console.log(\`✅ Clicked element with exact text: "\${text}"\`);
|
|
971
|
-
} catch (error) {
|
|
1062
|
+
} catch (error${anyType}) {
|
|
972
1063
|
console.error(\`Failed to click exact text "\${text}":\`, error.message || error);
|
|
973
1064
|
throw error;
|
|
974
1065
|
}
|
|
@@ -1013,8 +1104,10 @@ coverage/
|
|
|
1013
1104
|
skipLibCheck: true,
|
|
1014
1105
|
forceConsistentCasingInFileNames: true,
|
|
1015
1106
|
types: ["k6"],
|
|
1107
|
+
allowImportingTsExtensions: true,
|
|
1108
|
+
noEmit: true
|
|
1016
1109
|
},
|
|
1017
|
-
include: ["src/**/*", "steps/**/*", "**/*.ts"],
|
|
1110
|
+
include: ["src/**/*", "steps/**/*", "**/*.ts", "**/*.d.ts"],
|
|
1018
1111
|
exclude: ["node_modules"],
|
|
1019
1112
|
};
|
|
1020
1113
|
fs_1.default.writeFileSync(path_1.default.join(outputPath, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.generator.js","sourceRoot":"","sources":["../../src/generators/project.generator.ts"],"names":[],"mappings":";;;;;;AAAA,sCAAsC;AACtC,4CAAoB;AACpB,gDAAwB;AAGxB,MAAa,gBAAgB;IAC3B,wBAAwB,CAAC,MAAqB,EAAE,UAAkB;QAChE,0BAA0B;QAC1B,IAAI,CAAC,eAAe,CAAC,cAAI,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC;QACxD,IAAI,CAAC,eAAe,CAAC,cAAI,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;QACrD,IAAI,CAAC,eAAe,CAAC,cAAI,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC;QACzD,IAAI,CAAC,eAAe,CAAC,cAAI,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;QACpD,IAAI,CAAC,eAAe,CAAC,cAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC;QACvD,YAAE,CAAC,SAAS,CAAC,cAAI,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACjE,wBAAwB;QACxB,IAAI,CAAC,mBAAmB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAE7C,kBAAkB;QAClB,IAAI,CAAC,cAAc,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAExC,+BAA+B;QAC/B,IAAI,CAAC,qBAAqB,CAAC,UAAU,CAAC,CAAC;QACvC,IAAI,CAAC,4BAA4B,CAAC,UAAU,CAAC,CAAC;QAC9C,IAAI,CAAC,yBAAyB,CAAC,UAAU,CAAC,CAAC;QAC3C,oDAAoD;QACpD,IAAI,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC;QACrC,mCAAmC;QACnC,IAAI,CAAC,mBAAmB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAC7C,qBAAqB;QACrB,IAAI,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"project.generator.js","sourceRoot":"","sources":["../../src/generators/project.generator.ts"],"names":[],"mappings":"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"}
|