@sun-asterisk/sungen 2.5.2 → 2.6.1
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/commands/add-flow.d.ts +3 -0
- package/dist/cli/commands/add-flow.d.ts.map +1 -0
- package/dist/cli/commands/add-flow.js +27 -0
- package/dist/cli/commands/add-flow.js.map +1 -0
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +95 -60
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +38 -6
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/index.js +3 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts +16 -0
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +3 -0
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -2
- package/dist/generators/test-generator/adapters/playwright/templates/scenario.hbs +20 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/alert-accept-action.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/alert-dismiss-action.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/alert-fill-action.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/alert-text-assertion.hbs +2 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +1 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/navigation.hbs +2 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/route-assertion.hbs +1 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-timeout.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +84 -4
- package/dist/generators/test-generator/code-generator.d.ts +2 -0
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +105 -17
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/patterns/interaction-patterns.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/interaction-patterns.js +22 -3
- package/dist/generators/test-generator/patterns/interaction-patterns.js.map +1 -1
- package/dist/generators/test-generator/patterns/navigation-patterns.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/navigation-patterns.js +8 -3
- package/dist/generators/test-generator/patterns/navigation-patterns.js.map +1 -1
- package/dist/generators/test-generator/step-mapper.d.ts +4 -0
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +7 -0
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/template-engine.d.ts +14 -0
- package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
- package/dist/generators/test-generator/template-engine.js +1 -1
- package/dist/generators/test-generator/template-engine.js.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.d.ts +3 -20
- package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.js +23 -66
- package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts +2 -6
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +18 -80
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
- package/dist/orchestrator/ai-rules-updater.js +4 -0
- package/dist/orchestrator/ai-rules-updater.js.map +1 -1
- package/dist/orchestrator/flow-manager.d.ts +22 -0
- package/dist/orchestrator/flow-manager.d.ts.map +1 -0
- package/dist/orchestrator/flow-manager.js +251 -0
- package/dist/orchestrator/flow-manager.js.map +1 -0
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +1 -0
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/screen-manager.d.ts.map +1 -1
- package/dist/orchestrator/screen-manager.js +3 -1
- package/dist/orchestrator/screen-manager.js.map +1 -1
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-flow.md +88 -0
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +11 -8
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-review.md +8 -6
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +15 -11
- package/dist/orchestrator/templates/ai-instructions/claude-config.md +41 -10
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +12 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-delivery.md +19 -18
- package/dist/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +12 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +122 -10
- package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +31 -3
- package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +45 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +92 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +30 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +86 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +13 -10
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +16 -15
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-review.md +9 -7
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +21 -17
- package/dist/orchestrator/templates/ai-instructions/copilot-config.md +40 -9
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +12 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +19 -18
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-error-mapping.md +12 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +122 -10
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +31 -3
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +45 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +93 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +30 -0
- package/dist/orchestrator/templates/playwright.config.d.ts.map +1 -1
- package/dist/orchestrator/templates/playwright.config.js +3 -1
- package/dist/orchestrator/templates/playwright.config.js.map +1 -1
- package/dist/orchestrator/templates/playwright.config.ts +4 -1
- package/dist/orchestrator/templates/specs-base.d.ts +3 -4
- package/dist/orchestrator/templates/specs-base.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-base.js +60 -91
- package/dist/orchestrator/templates/specs-base.js.map +1 -1
- package/dist/orchestrator/templates/specs-base.ts +61 -101
- package/dist/orchestrator/templates/specs-test-data.d.ts +3 -1
- package/dist/orchestrator/templates/specs-test-data.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-test-data.js +53 -2
- package/dist/orchestrator/templates/specs-test-data.js.map +1 -1
- package/dist/orchestrator/templates/specs-test-data.ts +56 -2
- package/package.json +1 -1
- package/src/cli/commands/add-flow.ts +25 -0
- package/src/cli/commands/delivery.ts +109 -58
- package/src/cli/commands/generate.ts +43 -6
- package/src/cli/index.ts +3 -1
- package/src/generators/test-generator/adapters/adapter-interface.ts +6 -1
- package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -2
- package/src/generators/test-generator/adapters/playwright/templates/scenario.hbs +20 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/alert-accept-action.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/alert-dismiss-action.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/alert-fill-action.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/alert-text-assertion.hbs +2 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +1 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/navigation.hbs +2 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/route-assertion.hbs +1 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-timeout.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +84 -4
- package/src/generators/test-generator/code-generator.ts +119 -20
- package/src/generators/test-generator/patterns/interaction-patterns.ts +25 -3
- package/src/generators/test-generator/patterns/navigation-patterns.ts +8 -3
- package/src/generators/test-generator/step-mapper.ts +8 -0
- package/src/generators/test-generator/template-engine.ts +5 -2
- package/src/generators/test-generator/utils/data-resolver.ts +25 -77
- package/src/generators/test-generator/utils/selector-resolver.ts +23 -109
- package/src/orchestrator/ai-rules-updater.ts +5 -0
- package/src/orchestrator/flow-manager.ts +243 -0
- package/src/orchestrator/project-initializer.ts +1 -0
- package/src/orchestrator/screen-manager.ts +3 -1
- package/src/orchestrator/templates/ai-instructions/claude-cmd-add-flow.md +88 -0
- package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +11 -8
- package/src/orchestrator/templates/ai-instructions/claude-cmd-review.md +8 -6
- package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +15 -11
- package/src/orchestrator/templates/ai-instructions/claude-config.md +41 -10
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +12 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-delivery.md +19 -18
- package/src/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +12 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +122 -10
- package/src/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +31 -3
- package/src/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +45 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +92 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +30 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +86 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +13 -10
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +16 -15
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-review.md +9 -7
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +21 -17
- package/src/orchestrator/templates/ai-instructions/copilot-config.md +40 -9
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +12 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +19 -18
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-error-mapping.md +12 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +122 -10
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +31 -3
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +45 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +93 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +30 -0
- package/src/orchestrator/templates/playwright.config.ts +4 -1
- package/src/orchestrator/templates/specs-base.ts +61 -101
- package/src/orchestrator/templates/specs-test-data.ts +56 -2
- package/dist/utils/feature-finder.d.ts +0 -9
- package/dist/utils/feature-finder.d.ts.map +0 -1
- package/dist/utils/feature-finder.js +0 -67
- package/dist/utils/feature-finder.js.map +0 -1
- package/dist/utils/screen-paths.d.ts +0 -10
- package/dist/utils/screen-paths.d.ts.map +0 -1
- package/dist/utils/screen-paths.js +0 -73
- package/dist/utils/screen-paths.js.map +0 -1
- package/dist/utils/selector-loader.d.ts +0 -6
- package/dist/utils/selector-loader.d.ts.map +0 -1
- package/dist/utils/selector-loader.js +0 -20
- package/dist/utils/selector-loader.js.map +0 -1
- package/dist/utils/test-data-loader.d.ts +0 -6
- package/dist/utils/test-data-loader.d.ts.map +0 -1
- package/dist/utils/test-data-loader.js +0 -20
- package/dist/utils/test-data-loader.js.map +0 -1
- package/src/utils/feature-finder.ts +0 -33
- package/src/utils/screen-paths.ts +0 -37
- package/src/utils/selector-loader.ts +0 -23
- package/src/utils/test-data-loader.ts +0 -23
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
1
|
import * as path from 'path';
|
|
3
|
-
import yaml from 'yaml';
|
|
4
2
|
import { readYamlIfExists } from '../../../utils/yaml-io';
|
|
5
3
|
|
|
6
4
|
/**
|
|
@@ -9,17 +7,13 @@ import { readYamlIfExists } from '../../../utils/yaml-io';
|
|
|
9
7
|
* Two modes:
|
|
10
8
|
* - Compile-time (default): resolves YAML values and bakes them into generated code
|
|
11
9
|
* - Runtime (runtimeMode=true): returns markers that post-processor converts to testData.get() calls
|
|
12
|
-
*
|
|
13
|
-
* Supports override files with priority:
|
|
14
|
-
* 1. .override.yaml (highest - user customizations)
|
|
15
|
-
* 2. -override.yaml (legacy - backward compat)
|
|
16
|
-
* 3. .yaml (lowest - auto-generated base)
|
|
17
10
|
*/
|
|
18
11
|
export class DataResolver {
|
|
19
12
|
private dataCache = new Map<string, any>();
|
|
20
13
|
private testDataDir: string;
|
|
21
14
|
private screenName?: string;
|
|
22
15
|
private runtimeMode: boolean;
|
|
16
|
+
private flowMode: boolean = false;
|
|
23
17
|
|
|
24
18
|
constructor(testDataDir?: string, screenName?: string, runtimeMode: boolean = false) {
|
|
25
19
|
this.testDataDir = testDataDir || path.join(process.cwd(), 'qa', 'test-data');
|
|
@@ -27,13 +21,14 @@ export class DataResolver {
|
|
|
27
21
|
this.runtimeMode = runtimeMode;
|
|
28
22
|
}
|
|
29
23
|
|
|
30
|
-
/**
|
|
31
|
-
* Set screen context for data resolution
|
|
32
|
-
*/
|
|
33
24
|
setScreenContext(screenName: string): void {
|
|
34
25
|
this.screenName = screenName;
|
|
35
26
|
}
|
|
36
27
|
|
|
28
|
+
setFlowMode(enabled: boolean): void {
|
|
29
|
+
this.flowMode = enabled;
|
|
30
|
+
}
|
|
31
|
+
|
|
37
32
|
/**
|
|
38
33
|
* Resolve data reference to actual value
|
|
39
34
|
* @param dataRef - Format: email.valid (path.to.value without filename)
|
|
@@ -107,89 +102,42 @@ export class DataResolver {
|
|
|
107
102
|
|
|
108
103
|
/**
|
|
109
104
|
* Load data file from disk (with caching)
|
|
110
|
-
*
|
|
105
|
+
* Merges base + env-specific: {name}.yaml + {name}.{SUNGEN_ENV}.yaml
|
|
111
106
|
*/
|
|
112
107
|
private loadDataFile(fileName: string): any {
|
|
113
108
|
if (this.dataCache.has(fileName)) {
|
|
114
109
|
return this.dataCache.get(fileName)!;
|
|
115
110
|
}
|
|
116
111
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
let merged: any = {};
|
|
120
|
-
let found = false;
|
|
121
|
-
|
|
122
|
-
// Load in reverse priority order (base first, then overrides on top)
|
|
123
|
-
for (const p of [...possiblePaths].reverse()) {
|
|
124
|
-
const data = readYamlIfExists(p);
|
|
125
|
-
if (data && typeof data === 'object' && Object.keys(data).length > 0) {
|
|
126
|
-
merged = { ...merged, ...data };
|
|
127
|
-
found = true;
|
|
128
|
-
}
|
|
112
|
+
if (!this.screenName) {
|
|
113
|
+
throw new Error(`Data resolution requires screen/flow context for "${fileName}"`);
|
|
129
114
|
}
|
|
130
115
|
|
|
131
|
-
|
|
116
|
+
const subDir = this.flowMode ? 'flows' : 'screens';
|
|
117
|
+
const baseDir = path.join(process.cwd(), 'qa', subDir, this.screenName, 'test-data');
|
|
118
|
+
const basePath = path.join(baseDir, `${fileName}.yaml`);
|
|
119
|
+
const data = readYamlIfExists(basePath);
|
|
120
|
+
|
|
121
|
+
if (!data || typeof data !== 'object' || Object.keys(data).length === 0) {
|
|
132
122
|
throw new Error(
|
|
133
|
-
`Data file not found
|
|
123
|
+
`Data file not found or empty: qa/${subDir}/${this.screenName}/test-data/${fileName}.yaml`
|
|
134
124
|
);
|
|
135
125
|
}
|
|
136
126
|
|
|
137
|
-
|
|
138
|
-
return merged;
|
|
139
|
-
}
|
|
127
|
+
let merged = { ...data };
|
|
140
128
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
for (const p of possiblePaths) {
|
|
150
|
-
const data = readYamlIfExists(p);
|
|
151
|
-
if (data !== null) {
|
|
152
|
-
// Skip if file is empty or only contains null/undefined
|
|
153
|
-
if (data === undefined || (typeof data === 'object' && Object.keys(data).length === 0)) {
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Emit deprecation warning for old -override.yaml pattern
|
|
158
|
-
if (p.includes('-override.yaml')) {
|
|
159
|
-
console.warn(`⚠️ Deprecated: ${path.basename(p)}`);
|
|
160
|
-
console.warn(` Rename to: ${fileName}.override.yaml for better compatibility`);
|
|
161
|
-
}
|
|
162
|
-
return p;
|
|
129
|
+
// Merge env-specific data: {name}.{SUNGEN_ENV}.yaml
|
|
130
|
+
const env = process.env.SUNGEN_ENV;
|
|
131
|
+
if (env) {
|
|
132
|
+
const envPath = path.join(baseDir, `${fileName}.${env}.yaml`);
|
|
133
|
+
const envData = readYamlIfExists(envPath);
|
|
134
|
+
if (envData && typeof envData === 'object') {
|
|
135
|
+
merged = { ...merged, ...envData };
|
|
163
136
|
}
|
|
164
137
|
}
|
|
165
|
-
|
|
166
|
-
return null;
|
|
167
|
-
}
|
|
168
138
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
* Priority: .override.yaml > -override.yaml > base .yaml
|
|
172
|
-
*/
|
|
173
|
-
private getPossibleDataPaths(fileName: string): string[] {
|
|
174
|
-
const paths: string[] = [];
|
|
175
|
-
|
|
176
|
-
// New structure: qa/screens/{screenName}/test-data/
|
|
177
|
-
if (this.screenName) {
|
|
178
|
-
const cwd = process.cwd();
|
|
179
|
-
const basePath = path.join(cwd, 'qa', 'screens', this.screenName, 'test-data');
|
|
180
|
-
|
|
181
|
-
// Priority 1: New .override.yaml pattern (highest)
|
|
182
|
-
paths.push(path.join(basePath, `${fileName}.override.yaml`));
|
|
183
|
-
|
|
184
|
-
// Priority 2: Old -override.yaml pattern (backward compat)
|
|
185
|
-
paths.push(path.join(basePath, `${fileName}-override.yaml`));
|
|
186
|
-
|
|
187
|
-
// Priority 3: Base file (lowest)
|
|
188
|
-
paths.push(path.join(basePath, `${fileName}.yaml`));
|
|
189
|
-
paths.push(path.join(basePath, `${fileName}.yml`));
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return paths;
|
|
139
|
+
this.dataCache.set(fileName, merged);
|
|
140
|
+
return merged;
|
|
193
141
|
}
|
|
194
142
|
|
|
195
143
|
/**
|
|
@@ -82,12 +82,17 @@ export class SelectorResolver {
|
|
|
82
82
|
private selectorsDir: string;
|
|
83
83
|
private featureName?: string;
|
|
84
84
|
private screenName?: string;
|
|
85
|
+
private flowMode: boolean = false;
|
|
85
86
|
|
|
86
87
|
constructor(selectorsDir?: string, screenName?: string) {
|
|
87
88
|
this.selectorsDir = selectorsDir || path.join(process.cwd(), 'qa', 'selectors', 'screens');
|
|
88
89
|
this.screenName = screenName;
|
|
89
90
|
}
|
|
90
91
|
|
|
92
|
+
setFlowMode(enabled: boolean): void {
|
|
93
|
+
this.flowMode = enabled;
|
|
94
|
+
}
|
|
95
|
+
|
|
91
96
|
/**
|
|
92
97
|
* Set screen context for base selector file lookup
|
|
93
98
|
*/
|
|
@@ -160,9 +165,15 @@ export class SelectorResolver {
|
|
|
160
165
|
// Check if it's legacy format (contains : or has dot with screen prefix)
|
|
161
166
|
// Legacy: "login:email_field" or "login.email_field"
|
|
162
167
|
// Natural: "Email Address", "Password", "Submit Button"
|
|
168
|
+
// Flow namespaced: "Login:Submit" → key "login:submit" in flow's own YAML
|
|
163
169
|
const hasColonSeparator = selectorRef.includes(':');
|
|
164
170
|
const hasDotSeparator = selectorRef.includes('.');
|
|
165
|
-
|
|
171
|
+
|
|
172
|
+
// Flow mode: colon is a namespace separator, resolve as natural language key
|
|
173
|
+
if (hasColonSeparator && this.flowMode) {
|
|
174
|
+
return this.resolveNaturalLanguage(selectorRef, contextName!, elementType, nth);
|
|
175
|
+
}
|
|
176
|
+
|
|
166
177
|
// If it has colon, it's definitely legacy
|
|
167
178
|
if (hasColonSeparator) {
|
|
168
179
|
return this.resolveLegacyFormat(selectorRef);
|
|
@@ -601,7 +612,7 @@ export class SelectorResolver {
|
|
|
601
612
|
|
|
602
613
|
const selectors: SelectorFile = {};
|
|
603
614
|
|
|
604
|
-
// 1. Try load base file (screen-level shared selectors) - OPTIONAL
|
|
615
|
+
// 1. Try load base file (screen/flow-level shared selectors) - OPTIONAL
|
|
605
616
|
if (this.screenName) {
|
|
606
617
|
const basePath = this.findBaseSelectorPath(this.screenName);
|
|
607
618
|
if (basePath) {
|
|
@@ -610,21 +621,11 @@ export class SelectorResolver {
|
|
|
610
621
|
Object.assign(selectors, SelectorResolver.normalizeKeys(baseSelectors));
|
|
611
622
|
}
|
|
612
623
|
}
|
|
613
|
-
|
|
614
|
-
// Load base override file
|
|
615
|
-
const baseOverridePath = this.findOverrideSelectorPath(this.screenName);
|
|
616
|
-
if (baseOverridePath) {
|
|
617
|
-
const overrideSelectors = readYamlIfExists<SelectorFile>(baseOverridePath);
|
|
618
|
-
if (overrideSelectors) {
|
|
619
|
-
Object.assign(selectors, SelectorResolver.normalizeKeys(overrideSelectors));
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
624
|
}
|
|
623
625
|
|
|
624
626
|
// 2. Load feature-specific selectors - REQUIRED
|
|
625
627
|
const featurePath = this.findBaseSelectorPath(featureName);
|
|
626
628
|
if (!featurePath) {
|
|
627
|
-
// If no base file, that's OK if we have base from screen
|
|
628
629
|
if (Object.keys(selectors).length === 0) {
|
|
629
630
|
throw new Error(
|
|
630
631
|
`Selector file not found for feature "${featureName}". ` +
|
|
@@ -638,15 +639,6 @@ export class SelectorResolver {
|
|
|
638
639
|
}
|
|
639
640
|
}
|
|
640
641
|
|
|
641
|
-
// 3. Load feature override file
|
|
642
|
-
const featureOverridePath = this.findOverrideSelectorPath(featureName);
|
|
643
|
-
if (featureOverridePath) {
|
|
644
|
-
const overrideSelectors = readYamlIfExists<SelectorFile>(featureOverridePath);
|
|
645
|
-
if (overrideSelectors) {
|
|
646
|
-
Object.assign(selectors, SelectorResolver.normalizeKeys(overrideSelectors));
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
|
|
650
642
|
this.selectorCache.set(cacheKey, selectors);
|
|
651
643
|
return selectors;
|
|
652
644
|
}
|
|
@@ -657,72 +649,25 @@ export class SelectorResolver {
|
|
|
657
649
|
private findBaseSelectorPath(name: string): string | null {
|
|
658
650
|
const possiblePaths: string[] = [];
|
|
659
651
|
|
|
660
|
-
// New structure: qa/screens/{screenName}/selectors/
|
|
652
|
+
// New structure: qa/screens/{screenName}/selectors/ or qa/flows/{flowName}/selectors/
|
|
661
653
|
const qaDir = path.dirname(path.dirname(this.selectorsDir));
|
|
662
|
-
|
|
663
|
-
if (this.screenName) {
|
|
664
|
-
// Priority: Base .yaml only
|
|
665
|
-
possiblePaths.push(
|
|
666
|
-
path.join(qaDir, 'screens', this.screenName, 'selectors', `${name}.yaml`)
|
|
667
|
-
);
|
|
668
|
-
} else {
|
|
669
|
-
possiblePaths.push(
|
|
670
|
-
path.join(qaDir, 'screens', name, 'selectors', `${name}.yaml`)
|
|
671
|
-
);
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
// Old structure
|
|
675
|
-
possiblePaths.push(path.join(this.selectorsDir, `${name}.yaml`));
|
|
676
|
-
|
|
677
|
-
for (const p of possiblePaths) {
|
|
678
|
-
if (fs.existsSync(p)) {
|
|
679
|
-
return p;
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
return null;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
/**
|
|
687
|
-
* Find OVERRIDE selector file path
|
|
688
|
-
* Priority: .override.yaml > -override.yaml
|
|
689
|
-
*/
|
|
690
|
-
private findOverrideSelectorPath(name: string): string | null {
|
|
691
|
-
const possiblePaths: string[] = [];
|
|
654
|
+
const subDir = this.flowMode ? 'flows' : 'screens';
|
|
692
655
|
|
|
693
|
-
// New structure: qa/screens/{screenName}/selectors/
|
|
694
|
-
const qaDir = path.dirname(path.dirname(this.selectorsDir));
|
|
695
|
-
|
|
696
656
|
if (this.screenName) {
|
|
697
|
-
// Priority 1: New .override.yaml
|
|
698
657
|
possiblePaths.push(
|
|
699
|
-
path.join(qaDir,
|
|
700
|
-
);
|
|
701
|
-
|
|
702
|
-
// Priority 2: Old -override.yaml
|
|
703
|
-
possiblePaths.push(
|
|
704
|
-
path.join(qaDir, 'screens', this.screenName, 'selectors', `${name}-override.yaml`)
|
|
658
|
+
path.join(qaDir, subDir, this.screenName, 'selectors', `${name}.yaml`)
|
|
705
659
|
);
|
|
706
660
|
} else {
|
|
707
661
|
possiblePaths.push(
|
|
708
|
-
path.join(qaDir,
|
|
709
|
-
);
|
|
710
|
-
possiblePaths.push(
|
|
711
|
-
path.join(qaDir, 'screens', name, 'selectors', `${name}-override.yaml`)
|
|
662
|
+
path.join(qaDir, subDir, name, 'selectors', `${name}.yaml`)
|
|
712
663
|
);
|
|
713
664
|
}
|
|
714
665
|
|
|
715
666
|
// Old structure
|
|
716
|
-
possiblePaths.push(path.join(this.selectorsDir, `${name}.
|
|
717
|
-
possiblePaths.push(path.join(this.selectorsDir, `${name}-override.yaml`));
|
|
667
|
+
possiblePaths.push(path.join(this.selectorsDir, `${name}.yaml`));
|
|
718
668
|
|
|
719
669
|
for (const p of possiblePaths) {
|
|
720
670
|
if (fs.existsSync(p)) {
|
|
721
|
-
// Emit deprecation warning for old -override.yaml pattern
|
|
722
|
-
if (p.includes('-override.yaml')) {
|
|
723
|
-
console.warn(`⚠️ Deprecated: ${path.basename(p)}`);
|
|
724
|
-
console.warn(` Rename to: ${name}.override.yaml for better compatibility`);
|
|
725
|
-
}
|
|
726
671
|
return p;
|
|
727
672
|
}
|
|
728
673
|
}
|
|
@@ -732,59 +677,28 @@ export class SelectorResolver {
|
|
|
732
677
|
|
|
733
678
|
/**
|
|
734
679
|
* Find selector file path (checks both old and new structures)
|
|
735
|
-
* Priority: .override.yaml > -override.yaml > base .yaml
|
|
736
680
|
*/
|
|
737
681
|
private findSelectorPath(name: string): string | null {
|
|
738
|
-
// Build list of possible paths (priority order)
|
|
739
682
|
const possiblePaths: string[] = [];
|
|
740
683
|
|
|
741
|
-
|
|
742
|
-
const
|
|
743
|
-
|
|
684
|
+
const qaDir = path.dirname(path.dirname(this.selectorsDir));
|
|
685
|
+
const subDir = this.flowMode ? 'flows' : 'screens';
|
|
686
|
+
|
|
744
687
|
if (this.screenName) {
|
|
745
|
-
// Priority 1: New .override.yaml pattern (highest)
|
|
746
688
|
possiblePaths.push(
|
|
747
|
-
path.join(qaDir,
|
|
748
|
-
);
|
|
749
|
-
|
|
750
|
-
// Priority 2: Old -override.yaml pattern (backward compat)
|
|
751
|
-
possiblePaths.push(
|
|
752
|
-
path.join(qaDir, 'screens', this.screenName, 'selectors', `${name}-override.yaml`)
|
|
753
|
-
);
|
|
754
|
-
|
|
755
|
-
// Priority 3: Base file (lowest)
|
|
756
|
-
possiblePaths.push(
|
|
757
|
-
path.join(qaDir, 'screens', this.screenName, 'selectors', `${name}.yaml`)
|
|
689
|
+
path.join(qaDir, subDir, this.screenName, 'selectors', `${name}.yaml`)
|
|
758
690
|
);
|
|
759
691
|
} else {
|
|
760
|
-
// Fallback: assume name is both screen and feature
|
|
761
|
-
possiblePaths.push(
|
|
762
|
-
path.join(qaDir, 'screens', name, 'selectors', `${name}.override.yaml`)
|
|
763
|
-
);
|
|
764
692
|
possiblePaths.push(
|
|
765
|
-
path.join(qaDir,
|
|
766
|
-
);
|
|
767
|
-
possiblePaths.push(
|
|
768
|
-
path.join(qaDir, 'screens', name, 'selectors', `${name}.yaml`)
|
|
693
|
+
path.join(qaDir, subDir, name, 'selectors', `${name}.yaml`)
|
|
769
694
|
);
|
|
770
695
|
}
|
|
771
696
|
|
|
772
697
|
// Old structure: qa/selectors/screens/
|
|
773
|
-
// Priority 1: New .override.yaml
|
|
774
|
-
possiblePaths.push(path.join(this.selectorsDir, `${name}.override.yaml`));
|
|
775
|
-
// Priority 2: Old -override.yaml
|
|
776
|
-
possiblePaths.push(path.join(this.selectorsDir, `${name}-override.yaml`));
|
|
777
|
-
// Priority 3: Base
|
|
778
698
|
possiblePaths.push(path.join(this.selectorsDir, `${name}.yaml`));
|
|
779
699
|
|
|
780
|
-
// Find first existing file and emit deprecation warning for old pattern
|
|
781
700
|
for (const p of possiblePaths) {
|
|
782
701
|
if (fs.existsSync(p)) {
|
|
783
|
-
// Emit deprecation warning for old -override.yaml pattern
|
|
784
|
-
if (p.includes('-override.yaml')) {
|
|
785
|
-
console.warn(`⚠️ Deprecated: ${path.basename(p)}`);
|
|
786
|
-
console.warn(` Rename to: ${name}.override.yaml for better compatibility`);
|
|
787
|
-
}
|
|
788
702
|
return p;
|
|
789
703
|
}
|
|
790
704
|
}
|
|
@@ -16,6 +16,7 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
|
|
|
16
16
|
|
|
17
17
|
// Commands — Claude Code
|
|
18
18
|
['claude-cmd-add-screen.md', '.claude/commands/sungen/add-screen.md'],
|
|
19
|
+
['claude-cmd-add-flow.md', '.claude/commands/sungen/add-flow.md'],
|
|
19
20
|
['claude-cmd-create-test.md', '.claude/commands/sungen/create-test.md'],
|
|
20
21
|
['claude-cmd-run-test.md', '.claude/commands/sungen/run-test.md'],
|
|
21
22
|
['claude-cmd-review.md', '.claude/commands/sungen/review.md'],
|
|
@@ -23,6 +24,7 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
|
|
|
23
24
|
|
|
24
25
|
// Commands — GitHub Copilot
|
|
25
26
|
['copilot-cmd-add-screen.md', '.github/prompts/sungen-add-screen.prompt.md'],
|
|
27
|
+
['copilot-cmd-add-flow.md', '.github/prompts/sungen-add-flow.prompt.md'],
|
|
26
28
|
['copilot-cmd-create-test.md', '.github/prompts/sungen-create-test.prompt.md'],
|
|
27
29
|
['copilot-cmd-run-test.md', '.github/prompts/sungen-run-test.prompt.md'],
|
|
28
30
|
['copilot-cmd-review.md', '.github/prompts/sungen-review.prompt.md'],
|
|
@@ -43,6 +45,9 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
|
|
|
43
45
|
['claude-skill-capture-live.md', '.claude/skills/sungen-capture-live/SKILL.md'],
|
|
44
46
|
['claude-skill-figma-source.md', '.claude/skills/sungen-figma-source/SKILL.md'],
|
|
45
47
|
|
|
48
|
+
// Skills — Copilot (prompt-based)
|
|
49
|
+
['copilot-skill-figma-source.md', '.github/prompts/sungen-figma-source.prompt.md'],
|
|
50
|
+
|
|
46
51
|
// Skills — GitHub Copilot
|
|
47
52
|
['github-skill-sungen-gherkin-syntax.md', '.github/skills/sungen-gherkin-syntax/SKILL.md'],
|
|
48
53
|
['github-skill-sungen-selector-keys.md', '.github/skills/sungen-selector-keys/SKILL.md'],
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flow Manager
|
|
3
|
+
* Manages flow definition files in qa/flows directory
|
|
4
|
+
* Flows are independent E2E test journeys spanning multiple screens
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
|
|
10
|
+
export interface FlowOptions {
|
|
11
|
+
name: string;
|
|
12
|
+
path?: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class FlowManager {
|
|
17
|
+
private cwd: string;
|
|
18
|
+
private flowsDir: string;
|
|
19
|
+
|
|
20
|
+
constructor() {
|
|
21
|
+
this.cwd = process.cwd();
|
|
22
|
+
this.flowsDir = path.join(this.cwd, 'qa', 'flows');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async addFlow(options: FlowOptions): Promise<void> {
|
|
26
|
+
this.validateFlowName(options.name);
|
|
27
|
+
|
|
28
|
+
const flowName = this.normalizeFlowName(options.name);
|
|
29
|
+
const flowDir = path.join(this.flowsDir, flowName);
|
|
30
|
+
|
|
31
|
+
const featuresDir = path.join(flowDir, 'features');
|
|
32
|
+
const selectorsDir = path.join(flowDir, 'selectors');
|
|
33
|
+
const testDataDir = path.join(flowDir, 'test-data');
|
|
34
|
+
const requirementsDir = path.join(flowDir, 'requirements');
|
|
35
|
+
const requirementsUiDir = path.join(requirementsDir, 'ui');
|
|
36
|
+
|
|
37
|
+
const featurePath = path.join(featuresDir, `${flowName}.feature`);
|
|
38
|
+
const selectorPath = path.join(selectorsDir, `${flowName}.yaml`);
|
|
39
|
+
const testDataPath = path.join(testDataDir, `${flowName}.yaml`);
|
|
40
|
+
|
|
41
|
+
if (fs.existsSync(flowDir)) {
|
|
42
|
+
console.error(`Error: Flow "${options.name}" already exists at ${flowDir}`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log(`Creating flow: ${options.name}\n`);
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
if (!fs.existsSync(this.flowsDir)) {
|
|
50
|
+
fs.mkdirSync(this.flowsDir, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
fs.mkdirSync(featuresDir, { recursive: true });
|
|
53
|
+
fs.mkdirSync(selectorsDir, { recursive: true });
|
|
54
|
+
fs.mkdirSync(testDataDir, { recursive: true });
|
|
55
|
+
fs.mkdirSync(requirementsUiDir, { recursive: true });
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error(`Error: Failed to create directories`);
|
|
58
|
+
console.error(` ${error instanceof Error ? error.message : String(error)}`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
fs.writeFileSync(featurePath, this.generateFeatureTemplate(options, flowName), 'utf-8');
|
|
63
|
+
|
|
64
|
+
const startPath = options.path || '/login';
|
|
65
|
+
fs.writeFileSync(selectorPath, [
|
|
66
|
+
`# ${options.name} Flow Selectors`,
|
|
67
|
+
`# Namespace keys by screen to avoid duplicates: "login:submit", "awards:submit"`,
|
|
68
|
+
``,
|
|
69
|
+
`# --- Login screen ---`,
|
|
70
|
+
`login:`,
|
|
71
|
+
` type: 'page'`,
|
|
72
|
+
` value: '${startPath}'`,
|
|
73
|
+
``,
|
|
74
|
+
`# "login:email":`,
|
|
75
|
+
`# type: 'testid'`,
|
|
76
|
+
`# value: 'email-input'`,
|
|
77
|
+
``,
|
|
78
|
+
`# "login:submit":`,
|
|
79
|
+
`# type: 'role'`,
|
|
80
|
+
`# value: 'button'`,
|
|
81
|
+
`# name: 'Login'`,
|
|
82
|
+
``,
|
|
83
|
+
].join('\n'), 'utf-8');
|
|
84
|
+
|
|
85
|
+
fs.writeFileSync(testDataPath, [
|
|
86
|
+
`# ${options.name} Flow Test Data`,
|
|
87
|
+
`# Namespace by phase: login.email, submission.nominee`,
|
|
88
|
+
`# Reference in features using {{variable}} syntax`,
|
|
89
|
+
``,
|
|
90
|
+
].join('\n'), 'utf-8');
|
|
91
|
+
|
|
92
|
+
const specPath = path.join(requirementsDir, 'spec.md');
|
|
93
|
+
fs.writeFileSync(specPath, this.generateSpecTemplate(options, flowName), 'utf-8');
|
|
94
|
+
|
|
95
|
+
const viewpointPath = path.join(requirementsDir, 'test-viewpoint.md');
|
|
96
|
+
fs.writeFileSync(viewpointPath, this.generateViewpointTemplate(options), 'utf-8');
|
|
97
|
+
|
|
98
|
+
console.log(`Created files:`);
|
|
99
|
+
console.log(` ${path.relative(this.cwd, featurePath)}`);
|
|
100
|
+
console.log(` ${path.relative(this.cwd, selectorPath)}`);
|
|
101
|
+
console.log(` ${path.relative(this.cwd, testDataPath)}`);
|
|
102
|
+
console.log(` ${path.relative(this.cwd, specPath)}`);
|
|
103
|
+
console.log(` ${path.relative(this.cwd, viewpointPath)}`);
|
|
104
|
+
console.log(` ${path.relative(this.cwd, requirementsUiDir)}/`);
|
|
105
|
+
console.log('');
|
|
106
|
+
|
|
107
|
+
console.log('Next steps:');
|
|
108
|
+
console.log(` 1. Fill requirements/spec.md with flow specification (screens, transitions, business rules)`);
|
|
109
|
+
console.log(` Optionally add UI designs to requirements/ui/`);
|
|
110
|
+
console.log(` 2. Generate test cases: /sungen:create-test ${flowName}`);
|
|
111
|
+
console.log(` 3. Compile: sungen generate --flow ${flowName}`);
|
|
112
|
+
console.log(` 4. Run: npx playwright test\n`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private validateFlowName(name: string): void {
|
|
116
|
+
if (!name || name.trim().length === 0) {
|
|
117
|
+
console.error('Error: Flow name cannot be empty');
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
if (!/[a-zA-Z0-9]/.test(name)) {
|
|
121
|
+
console.error('Error: Flow name must contain at least one alphanumeric character');
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private normalizeFlowName(name: string): string {
|
|
127
|
+
return name
|
|
128
|
+
.toLowerCase()
|
|
129
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
130
|
+
.replace(/^-+|-+$/g, '')
|
|
131
|
+
.replace(/-+/g, '-');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private generateSpecTemplate(options: FlowOptions, flowName: string): string {
|
|
135
|
+
const startPath = options.path || '/login';
|
|
136
|
+
return `# ${options.name} Flow Specification
|
|
137
|
+
|
|
138
|
+
## Overview
|
|
139
|
+
- **Start URL:** ${startPath}
|
|
140
|
+
- **Auth Required:** no
|
|
141
|
+
- **Platform:** web
|
|
142
|
+
|
|
143
|
+
## Screens in Flow
|
|
144
|
+
<!-- List all screens this flow visits, in order -->
|
|
145
|
+
|
|
146
|
+
| # | Screen | URL Path | Description |
|
|
147
|
+
|---|--------|----------|-------------|
|
|
148
|
+
| 1 | [Screen Name] | ${startPath} | [What user does here] |
|
|
149
|
+
|
|
150
|
+
## Flow Steps
|
|
151
|
+
|
|
152
|
+
### Step 1: [Screen Name]
|
|
153
|
+
- **Action:** [What the user does]
|
|
154
|
+
- **Expected:** [What should happen]
|
|
155
|
+
- **Transition:** Navigates to Step 2
|
|
156
|
+
|
|
157
|
+
## Business Rules
|
|
158
|
+
<!-- Rules that affect the flow: permissions, conditions, limits -->
|
|
159
|
+
- [Rule 1]
|
|
160
|
+
|
|
161
|
+
## Notes
|
|
162
|
+
<!-- Edge cases, known issues, environment-specific behavior -->
|
|
163
|
+
- [Note 1]
|
|
164
|
+
`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private generateViewpointTemplate(options: FlowOptions): string {
|
|
168
|
+
return [
|
|
169
|
+
`# ${options.name} Flow — Test Viewpoints`,
|
|
170
|
+
'',
|
|
171
|
+
'## Edge Cases',
|
|
172
|
+
'',
|
|
173
|
+
'<!-- Sample — replace with actual edge cases for this flow:',
|
|
174
|
+
'- Network drops between screen transitions — should resume or show error?',
|
|
175
|
+
'- Session expires mid-flow — should redirect to login and preserve progress?',
|
|
176
|
+
'- Browser back button at step 3 — should return to step 2 with data intact?',
|
|
177
|
+
'- Complete flow twice rapidly — should prevent duplicate submissions',
|
|
178
|
+
'-->',
|
|
179
|
+
'',
|
|
180
|
+
'## Known Issues',
|
|
181
|
+
'',
|
|
182
|
+
'<!-- Sample — replace with actual known issues:',
|
|
183
|
+
'- [BUG-001] Form data lost if user navigates away and returns',
|
|
184
|
+
'- [BUG-002] Confirmation page shows stale data on slow connections',
|
|
185
|
+
'-->',
|
|
186
|
+
'',
|
|
187
|
+
'## Design Decisions',
|
|
188
|
+
'',
|
|
189
|
+
'<!-- Sample — replace with actual design decisions:',
|
|
190
|
+
'- Flow requires completing all steps in order (no skip)',
|
|
191
|
+
'- Draft is auto-saved at each step transition',
|
|
192
|
+
'- Cancel at any step discards the entire flow',
|
|
193
|
+
'-->',
|
|
194
|
+
'',
|
|
195
|
+
'## Cross-Screen Concerns',
|
|
196
|
+
'',
|
|
197
|
+
'<!-- Sample — replace with actual cross-screen concerns:',
|
|
198
|
+
'- Auth role must remain consistent across all screens',
|
|
199
|
+
'- Data entered in step 1 must be visible in confirmation (step 3)',
|
|
200
|
+
'- Error in step 2 should allow returning to step 1 without re-entering data',
|
|
201
|
+
'-->',
|
|
202
|
+
'',
|
|
203
|
+
'## Priority Viewpoints',
|
|
204
|
+
'',
|
|
205
|
+
'<!-- Rate importance for this flow (High / Medium / Low / Skip):',
|
|
206
|
+
'',
|
|
207
|
+
'| VP | Priority | Reason |',
|
|
208
|
+
'|---|----------|--------|',
|
|
209
|
+
'| VP-UI | Medium | Standard forms across screens |',
|
|
210
|
+
'| VP-VAL | High | Cross-screen validation rules |',
|
|
211
|
+
'| VP-LOGIC | High | Multi-step business logic, state transitions |',
|
|
212
|
+
'| VP-SEC | Medium | Auth must persist, role-based access at each screen |',
|
|
213
|
+
'-->',
|
|
214
|
+
'',
|
|
215
|
+
].join('\n');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private generateFeatureTemplate(options: FlowOptions, flowName: string): string {
|
|
219
|
+
const description = options.description || `complete the ${options.name} flow`;
|
|
220
|
+
|
|
221
|
+
return `@flow
|
|
222
|
+
Feature: ${options.name} Flow
|
|
223
|
+
|
|
224
|
+
As a user
|
|
225
|
+
I want to ${description}
|
|
226
|
+
So that I can accomplish my end-to-end goal
|
|
227
|
+
|
|
228
|
+
Background:
|
|
229
|
+
Given User is on [Login] page
|
|
230
|
+
|
|
231
|
+
@high @auth:user
|
|
232
|
+
Scenario: User login successfully
|
|
233
|
+
When User fill [Login:Email] field with {{login.email}}
|
|
234
|
+
And User fill [Login:Password] field with {{login.password}}
|
|
235
|
+
And User click [Login:Submit] button
|
|
236
|
+
Then User see [Dashboard] page
|
|
237
|
+
|
|
238
|
+
Scenario: Sample next step for ${options.name}
|
|
239
|
+
When User click [Dashboard:Element] button
|
|
240
|
+
Then User see "expected result" text
|
|
241
|
+
`;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -358,9 +358,11 @@ export class ScreenManager {
|
|
|
358
358
|
So that I can accomplish my tasks
|
|
359
359
|
Path: ${featurePath}
|
|
360
360
|
|
|
361
|
+
Background:
|
|
362
|
+
Given User is on [${screenName}] page
|
|
363
|
+
|
|
361
364
|
@high
|
|
362
365
|
Scenario: Sample scenario for ${options.name}
|
|
363
|
-
Given User is on [${screenName}] page
|
|
364
366
|
When User click [element] button
|
|
365
367
|
Then User see [result] text with {{success}}
|
|
366
368
|
`;
|