@sun-asterisk/sungen 1.0.17 → 1.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -601
- package/dist/cli/commands/live-scan-command.d.ts +9 -0
- package/dist/cli/commands/live-scan-command.d.ts.map +1 -0
- package/dist/cli/commands/live-scan-command.js +72 -0
- package/dist/cli/commands/live-scan-command.js.map +1 -0
- package/dist/cli/index.js +34 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/core/live-scanner/config-reader.d.ts +10 -0
- package/dist/core/live-scanner/config-reader.d.ts.map +1 -0
- package/dist/core/live-scanner/config-reader.js +87 -0
- package/dist/core/live-scanner/config-reader.js.map +1 -0
- package/dist/core/live-scanner/element-finder.d.ts +20 -0
- package/dist/core/live-scanner/element-finder.d.ts.map +1 -0
- package/dist/core/live-scanner/element-finder.js +289 -0
- package/dist/core/live-scanner/element-finder.js.map +1 -0
- package/dist/core/live-scanner/index.d.ts +8 -0
- package/dist/core/live-scanner/index.d.ts.map +1 -0
- package/dist/core/live-scanner/index.js +33 -0
- package/dist/core/live-scanner/index.js.map +1 -0
- package/dist/core/live-scanner/matrix-reader.d.ts +17 -0
- package/dist/core/live-scanner/matrix-reader.d.ts.map +1 -0
- package/dist/core/live-scanner/matrix-reader.js +97 -0
- package/dist/core/live-scanner/matrix-reader.js.map +1 -0
- package/dist/core/live-scanner/matrix-writer.d.ts +7 -0
- package/dist/core/live-scanner/matrix-writer.d.ts.map +1 -0
- package/dist/core/live-scanner/matrix-writer.js +92 -0
- package/dist/core/live-scanner/matrix-writer.js.map +1 -0
- package/dist/core/live-scanner/role-fallback.d.ts +15 -0
- package/dist/core/live-scanner/role-fallback.d.ts.map +1 -0
- package/dist/core/live-scanner/role-fallback.js +45 -0
- package/dist/core/live-scanner/role-fallback.js.map +1 -0
- package/dist/core/live-scanner/scanner.d.ts +13 -0
- package/dist/core/live-scanner/scanner.d.ts.map +1 -0
- package/dist/core/live-scanner/scanner.js +225 -0
- package/dist/core/live-scanner/scanner.js.map +1 -0
- package/dist/core/live-scanner/step-replayer.d.ts +26 -0
- package/dist/core/live-scanner/step-replayer.d.ts.map +1 -0
- package/dist/core/live-scanner/step-replayer.js +184 -0
- package/dist/core/live-scanner/step-replayer.js.map +1 -0
- package/dist/core/live-scanner/types.d.ts +50 -0
- package/dist/core/live-scanner/types.d.ts.map +1 -0
- package/dist/core/live-scanner/types.js +14 -0
- package/dist/core/live-scanner/types.js.map +1 -0
- package/dist/generators/cli.js +1 -1
- package/dist/generators/scaffold-generator/index.d.ts +20 -2
- package/dist/generators/scaffold-generator/index.d.ts.map +1 -1
- package/dist/generators/scaffold-generator/index.js +170 -10
- package/dist/generators/scaffold-generator/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/radio-select-action.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +2 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +2 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +6 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/label.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/placeholder.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/role.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/text.hbs +1 -1
- package/dist/generators/test-generator/patterns/assertion-patterns.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/assertion-patterns.js +6 -3
- package/dist/generators/test-generator/patterns/assertion-patterns.js.map +1 -1
- package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
- package/dist/generators/test-generator/template-engine.js +3 -5
- package/dist/generators/test-generator/template-engine.js.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.js +18 -7
- package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts +1 -0
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +6 -0
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/input/cli-adapter.d.ts +4 -0
- package/dist/input/cli-adapter.d.ts.map +1 -1
- package/dist/input/cli-adapter.js +18 -3
- package/dist/input/cli-adapter.js.map +1 -1
- package/dist/orchestrator/project-initializer.d.ts +6 -1
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +34 -8
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/live-scan-command.ts +79 -0
- package/src/cli/index.ts +40 -7
- package/src/config/default.config.yaml +6 -0
- package/src/core/live-scanner/config-reader.ts +57 -0
- package/src/core/live-scanner/element-finder.ts +333 -0
- package/src/core/live-scanner/index.ts +7 -0
- package/src/core/live-scanner/matrix-reader.ts +70 -0
- package/src/core/live-scanner/matrix-writer.ts +66 -0
- package/src/core/live-scanner/role-fallback.ts +43 -0
- package/src/core/live-scanner/scanner.ts +231 -0
- package/src/core/live-scanner/step-replayer.ts +219 -0
- package/src/core/live-scanner/types.ts +56 -0
- package/src/generators/cli.ts +1 -1
- package/src/generators/scaffold-generator/index.ts +181 -14
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/radio-select-action.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +2 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +2 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +6 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/label.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/placeholder.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/role.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/text.hbs +1 -1
- package/src/generators/test-generator/patterns/assertion-patterns.ts +6 -3
- package/src/generators/test-generator/template-engine.ts +3 -4
- package/src/generators/test-generator/utils/data-resolver.ts +20 -9
- package/src/generators/test-generator/utils/selector-resolver.ts +8 -0
- package/src/input/cli-adapter.ts +19 -3
- package/src/orchestrator/project-initializer.ts +37 -8
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Step Replayer
|
|
3
|
+
* Replays Gherkin steps on a live Playwright page to reveal dynamic elements.
|
|
4
|
+
* Takes a snapshot and finds elements after each action.
|
|
5
|
+
*/
|
|
6
|
+
import type { Page } from 'playwright';
|
|
7
|
+
import { LiveElement } from './types';
|
|
8
|
+
interface ParsedStepInfo {
|
|
9
|
+
keyword: string;
|
|
10
|
+
text: string;
|
|
11
|
+
selectorRef?: string;
|
|
12
|
+
dataRef?: string;
|
|
13
|
+
elementType?: string;
|
|
14
|
+
nth?: number;
|
|
15
|
+
}
|
|
16
|
+
interface ReplayResult {
|
|
17
|
+
elements: Record<string, LiveElement>;
|
|
18
|
+
errors: string[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Replay a sequence of Gherkin steps on a Playwright page,
|
|
22
|
+
* finding and interacting with elements along the way.
|
|
23
|
+
*/
|
|
24
|
+
export declare function replaySteps(page: Page, steps: ParsedStepInfo[], featurePath: string, baseUrl: string): Promise<ReplayResult>;
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=step-replayer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-replayer.d.ts","sourceRoot":"","sources":["../../../src/core/live-scanner/step-replayer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,WAAW,EAAkB,MAAM,SAAS,CAAC;AAGtD,UAAU,cAAc;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,UAAU,YAAY;IACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACtC,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,cAAc,EAAE,EACvB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC,CA6EvB"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Step Replayer
|
|
4
|
+
* Replays Gherkin steps on a live Playwright page to reveal dynamic elements.
|
|
5
|
+
* Takes a snapshot and finds elements after each action.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.replaySteps = replaySteps;
|
|
9
|
+
const element_finder_1 = require("./element-finder");
|
|
10
|
+
const selector_resolver_1 = require("../../generators/test-generator/utils/selector-resolver");
|
|
11
|
+
/**
|
|
12
|
+
* Replay a sequence of Gherkin steps on a Playwright page,
|
|
13
|
+
* finding and interacting with elements along the way.
|
|
14
|
+
*/
|
|
15
|
+
async function replaySteps(page, steps, featurePath, baseUrl) {
|
|
16
|
+
const elements = {};
|
|
17
|
+
const errors = [];
|
|
18
|
+
let currentContext = 'page';
|
|
19
|
+
for (const step of steps) {
|
|
20
|
+
try {
|
|
21
|
+
const action = detectAction(step.text);
|
|
22
|
+
// Handle navigation (Given User is on [x] page)
|
|
23
|
+
if (action === 'navigate') {
|
|
24
|
+
await page.goto(baseUrl + featurePath, { waitUntil: 'networkidle', timeout: 15000 });
|
|
25
|
+
await settle(page);
|
|
26
|
+
if (step.selectorRef) {
|
|
27
|
+
const key = generateElementKey(step.selectorRef, step.elementType);
|
|
28
|
+
// Record the page element but don't search for it on the DOM
|
|
29
|
+
elements[key] = {
|
|
30
|
+
gherkinRef: step.selectorRef,
|
|
31
|
+
gherkinType: step.elementType || 'page',
|
|
32
|
+
selectorType: '',
|
|
33
|
+
selectorValue: featurePath,
|
|
34
|
+
role: '',
|
|
35
|
+
name: step.selectorRef,
|
|
36
|
+
testid: null,
|
|
37
|
+
tag: '',
|
|
38
|
+
placeholder: null,
|
|
39
|
+
label: null,
|
|
40
|
+
context: 'page',
|
|
41
|
+
matchMethod: 'exact_role',
|
|
42
|
+
exact: false,
|
|
43
|
+
warning: null,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
// Skip steps without element references
|
|
49
|
+
if (!step.selectorRef)
|
|
50
|
+
continue;
|
|
51
|
+
const key = generateElementKey(step.selectorRef, step.elementType);
|
|
52
|
+
const nth = step.nth || 0;
|
|
53
|
+
const elementType = step.elementType || 'button';
|
|
54
|
+
// Find the element on the page
|
|
55
|
+
const element = await (0, element_finder_1.findElement)(page, step.selectorRef, elementType, nth);
|
|
56
|
+
element.context = currentContext;
|
|
57
|
+
// Record in matrix (first match wins for duplicate keys)
|
|
58
|
+
if (!elements[key]) {
|
|
59
|
+
elements[key] = element;
|
|
60
|
+
}
|
|
61
|
+
// Execute the action if element was found
|
|
62
|
+
if (element.matchMethod !== 'unresolved') {
|
|
63
|
+
if (action === 'click') {
|
|
64
|
+
await executeClick(page, step.selectorRef, elementType, nth);
|
|
65
|
+
await settle(page);
|
|
66
|
+
currentContext = await detectCurrentContext(page, currentContext, step.text);
|
|
67
|
+
}
|
|
68
|
+
else if (action === 'fill') {
|
|
69
|
+
// For fill actions during scanning, use a placeholder value
|
|
70
|
+
// We just need to verify the element exists
|
|
71
|
+
}
|
|
72
|
+
// 'see' and 'wait' actions: just record, don't interact
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
errors.push(`Step "${step.text}": element [${step.selectorRef}] not found`);
|
|
76
|
+
// Continue scanning remaining steps — record what we can even if some elements are missing
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
errors.push(`Step "${step.text}": ${error.message}`);
|
|
81
|
+
// On navigation or critical errors, stop replaying this scenario
|
|
82
|
+
if (error.message.includes('Navigation') || error.message.includes('net::')) {
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { elements, errors };
|
|
88
|
+
}
|
|
89
|
+
async function executeClick(page, ref, elementType, nth) {
|
|
90
|
+
const namePattern = new RegExp(escapeRegex(ref), 'i');
|
|
91
|
+
// Try role-based click first
|
|
92
|
+
const { getRoleFallbacks } = require('./role-fallback');
|
|
93
|
+
const roles = getRoleFallbacks(elementType);
|
|
94
|
+
for (const role of roles) {
|
|
95
|
+
try {
|
|
96
|
+
const locator = page.getByRole(role, { name: namePattern });
|
|
97
|
+
const target = nth > 0 ? locator.nth(nth) : locator.first();
|
|
98
|
+
if (await target.isVisible({ timeout: 3000 })) {
|
|
99
|
+
await target.click({ timeout: 5000 });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Fallback to text click
|
|
108
|
+
try {
|
|
109
|
+
const locator = page.getByText(namePattern);
|
|
110
|
+
const target = nth > 0 ? locator.nth(nth) : locator.first();
|
|
111
|
+
await target.click({ timeout: 5000 });
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Try testid as last resort
|
|
115
|
+
const normalized = ref.toLowerCase().replace(/[\s_-]+/g, '-');
|
|
116
|
+
await page.locator(`[data-testid*="${normalized}" i]`).first().click({ timeout: 5000 });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function detectAction(text) {
|
|
120
|
+
const lower = text.toLowerCase();
|
|
121
|
+
if (/\b(is on|navigate|go to|visit|open)\b/.test(lower) && /\bpage\b/.test(lower))
|
|
122
|
+
return 'navigate';
|
|
123
|
+
if (/\b(click|press|tap|submit)\b/.test(lower))
|
|
124
|
+
return 'click';
|
|
125
|
+
if (/\b(fill|enter|type|input)\b/.test(lower))
|
|
126
|
+
return 'fill';
|
|
127
|
+
if (/\b(see|verify|check|expect|visible|displayed)\b/.test(lower))
|
|
128
|
+
return 'see';
|
|
129
|
+
if (/\b(select|choose|pick)\b/.test(lower))
|
|
130
|
+
return 'select';
|
|
131
|
+
if (/\b(wait)\b/.test(lower))
|
|
132
|
+
return 'wait';
|
|
133
|
+
return 'see';
|
|
134
|
+
}
|
|
135
|
+
async function detectCurrentContext(page, previous, stepText) {
|
|
136
|
+
const lower = stepText.toLowerCase();
|
|
137
|
+
// Check if we opened a dialog/modal
|
|
138
|
+
if (/\b(dialog|modal|panel)\b/.test(lower)) {
|
|
139
|
+
return 'dialog';
|
|
140
|
+
}
|
|
141
|
+
// Check if a dialog is now visible on the page
|
|
142
|
+
try {
|
|
143
|
+
const dialogLocator = page.getByRole('dialog');
|
|
144
|
+
if (await dialogLocator.count() > 0) {
|
|
145
|
+
return 'dialog';
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// ignore
|
|
150
|
+
}
|
|
151
|
+
// Check for menu
|
|
152
|
+
if (/\b(menu|dropdown)\b/.test(lower)) {
|
|
153
|
+
return 'menu';
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
const menuLocator = page.getByRole('menu');
|
|
157
|
+
if (await menuLocator.count() > 0) {
|
|
158
|
+
return 'menu';
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// ignore
|
|
163
|
+
}
|
|
164
|
+
return previous;
|
|
165
|
+
}
|
|
166
|
+
async function settle(page) {
|
|
167
|
+
try {
|
|
168
|
+
await page.waitForLoadState('networkidle', { timeout: 5000 });
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// Timeout is acceptable — page may have long-polling connections
|
|
172
|
+
}
|
|
173
|
+
// Small extra wait for animations/transitions
|
|
174
|
+
await page.waitForTimeout(300);
|
|
175
|
+
}
|
|
176
|
+
function generateElementKey(ref, elementType) {
|
|
177
|
+
// Use the same key generation as the scaffold generator
|
|
178
|
+
// so live-scan keys match scaffold keys in the map command.
|
|
179
|
+
return selector_resolver_1.SelectorResolver.generateKey(ref);
|
|
180
|
+
}
|
|
181
|
+
function escapeRegex(str) {
|
|
182
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=step-replayer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-replayer.js","sourceRoot":"","sources":["../../../src/core/live-scanner/step-replayer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live Scanner Types
|
|
3
|
+
* Defines the data structures for live DOM scanning and matrix file output.
|
|
4
|
+
*
|
|
5
|
+
* Selector types map directly to Playwright locator strategies:
|
|
6
|
+
* testid → page.getByTestId(value)
|
|
7
|
+
* role → page.getByRole(value, { name })
|
|
8
|
+
* label → page.getByLabel(value)
|
|
9
|
+
* placeholder → page.getByPlaceholder(value)
|
|
10
|
+
* text → page.getByText(value)
|
|
11
|
+
*/
|
|
12
|
+
export type MatchMethod = 'testid' | 'exact_role' | 'role_fallback' | 'label' | 'placeholder' | 'text_only' | 'unresolved';
|
|
13
|
+
export type SelectorType = 'testid' | 'role' | 'label' | 'placeholder' | 'text';
|
|
14
|
+
export type ElementContext = 'page' | 'dialog' | 'menu' | 'modal' | 'popup';
|
|
15
|
+
export interface LiveElement {
|
|
16
|
+
gherkinRef: string;
|
|
17
|
+
gherkinType: string;
|
|
18
|
+
selectorType: SelectorType | '';
|
|
19
|
+
selectorValue: string;
|
|
20
|
+
role: string;
|
|
21
|
+
name: string;
|
|
22
|
+
testid: string | null;
|
|
23
|
+
tag: string;
|
|
24
|
+
placeholder: string | null;
|
|
25
|
+
label: string | null;
|
|
26
|
+
context: ElementContext;
|
|
27
|
+
matchMethod: MatchMethod;
|
|
28
|
+
exact: boolean;
|
|
29
|
+
warning: string | null;
|
|
30
|
+
}
|
|
31
|
+
export interface LiveScanScenario {
|
|
32
|
+
auth: string | null;
|
|
33
|
+
extends: string | null;
|
|
34
|
+
path: string;
|
|
35
|
+
elements: Record<string, LiveElement>;
|
|
36
|
+
}
|
|
37
|
+
export interface LiveScanResult {
|
|
38
|
+
screen: string;
|
|
39
|
+
baseUrl: string;
|
|
40
|
+
scannedAt: string;
|
|
41
|
+
scenarios: Record<string, LiveScanScenario>;
|
|
42
|
+
}
|
|
43
|
+
export interface LiveScanOptions {
|
|
44
|
+
baseUrl?: string;
|
|
45
|
+
screenName: string;
|
|
46
|
+
screensDir: string;
|
|
47
|
+
headed?: boolean;
|
|
48
|
+
authDir?: string;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/live-scanner/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,YAAY,GAAG,eAAe,GAAG,OAAO,GAAG,aAAa,GAAG,WAAW,GAAG,YAAY,CAAC;AAE3H,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,aAAa,GAAG,MAAM,CAAC;AAEhF,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;AAE5E,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,YAAY,GAAG,EAAE,CAAC;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,cAAc,CAAC;IACxB,WAAW,EAAE,WAAW,CAAC;IACzB,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;CAC7C;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Live Scanner Types
|
|
4
|
+
* Defines the data structures for live DOM scanning and matrix file output.
|
|
5
|
+
*
|
|
6
|
+
* Selector types map directly to Playwright locator strategies:
|
|
7
|
+
* testid → page.getByTestId(value)
|
|
8
|
+
* role → page.getByRole(value, { name })
|
|
9
|
+
* label → page.getByLabel(value)
|
|
10
|
+
* placeholder → page.getByPlaceholder(value)
|
|
11
|
+
* text → page.getByText(value)
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/live-scanner/types.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG"}
|
package/dist/generators/cli.js
CHANGED
|
@@ -80,7 +80,7 @@ const program = new commander_1.Command();
|
|
|
80
80
|
program
|
|
81
81
|
.name('qa-generator')
|
|
82
82
|
.description('AI-Native QA Selector DSL Generator')
|
|
83
|
-
.version('1.0.
|
|
83
|
+
.version('1.0.19');
|
|
84
84
|
// ============================================================================
|
|
85
85
|
// Command: discover
|
|
86
86
|
// ============================================================================
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export interface ScaffoldElement {
|
|
6
6
|
locator: string;
|
|
7
|
-
type: 'placeholder' | 'role' | 'text' | 'label' | 'page';
|
|
7
|
+
type: 'placeholder' | 'role' | 'text' | 'label' | 'page' | 'testid';
|
|
8
8
|
value: string;
|
|
9
9
|
name?: string;
|
|
10
10
|
nth: number;
|
|
11
|
+
exact?: boolean;
|
|
11
12
|
}
|
|
12
13
|
export interface ScaffoldResult {
|
|
13
14
|
[key: string]: ScaffoldElement;
|
|
@@ -22,7 +23,7 @@ export interface MapResult {
|
|
|
22
23
|
elementCount: number;
|
|
23
24
|
dataRefCount: number;
|
|
24
25
|
selectorsSkipped?: boolean;
|
|
25
|
-
|
|
26
|
+
testDataMerged?: boolean;
|
|
26
27
|
}
|
|
27
28
|
export declare class ScaffoldGenerator {
|
|
28
29
|
/**
|
|
@@ -112,6 +113,16 @@ export declare class ScaffoldGenerator {
|
|
|
112
113
|
outputPath: string;
|
|
113
114
|
elementCount: number;
|
|
114
115
|
}>;
|
|
116
|
+
/**
|
|
117
|
+
* Enrich scaffold with live-scan data if a .live-scan.yaml file exists.
|
|
118
|
+
* Uses the Playwright locator strategy detected by live-scan:
|
|
119
|
+
* testid → type: testid, value: <testid>
|
|
120
|
+
* role → type: role, value: <aria-role>, name: <accessible-name>
|
|
121
|
+
* label → type: label, value: <label-text>
|
|
122
|
+
* placeholder → type: placeholder, value: <placeholder-text>
|
|
123
|
+
* text → type: text, value: <visible-text>
|
|
124
|
+
*/
|
|
125
|
+
private enrichWithLiveScan;
|
|
115
126
|
/**
|
|
116
127
|
* Helper to capitalize first letter
|
|
117
128
|
*/
|
|
@@ -134,6 +145,13 @@ export declare class ScaffoldGenerator {
|
|
|
134
145
|
* Write selectors YAML file
|
|
135
146
|
*/
|
|
136
147
|
private writeSelectorsYaml;
|
|
148
|
+
/**
|
|
149
|
+
* Smart-merge test-data objects:
|
|
150
|
+
* - Keys present in both: keep the existing value (user may have filled it in)
|
|
151
|
+
* - Keys only in fresh (new refs in features): add with empty value
|
|
152
|
+
* - Keys only in existing (removed from features): drop them
|
|
153
|
+
*/
|
|
154
|
+
private mergeTestData;
|
|
137
155
|
/**
|
|
138
156
|
* Write test-data YAML file
|
|
139
157
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/generators/scaffold-generator/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/generators/scaffold-generator/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IACpE,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAAC;CAChC;AAED,MAAM,WAAW,cAAc;IAC7B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,cAAc,CAAC;CACxC;AAED,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAiBD,qBAAa,iBAAiB;IAC5B;;OAEG;IACH,gBAAgB,CAAC,eAAe,EAAE,MAAM,GAAG,cAAc;IAKzD;;OAEG;IACH,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc;IAQpD;;OAEG;IACH,OAAO,CAAC,eAAe;IAqDvB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAyEzB;;;OAGG;IACH;;OAEG;IACH,OAAO,CAAC,YAAY;IA4BpB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAgB1B;;;;OAIG;IACH;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAYzB;;OAEG;IACH,OAAO,CAAC,aAAa;IA2CrB;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAqH7B;;OAEG;IACH,OAAO,CAAC,YAAY;IAyBpB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAIzB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAgB1B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAUxB;;OAEG;IACH,mBAAmB,CAAC,QAAQ,EAAE,cAAc,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM;IAyB1E;;OAEG;IACH,aAAa,CACX,QAAQ,EAAE,cAAc,EACxB,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,MAAM,GAClB,IAAI;IAUP;;OAEG;IACH,kBAAkB,CAChB,eAAe,EAAE,MAAM,EACvB,SAAS,EAAE,MAAM,EACjB,cAAc,GAAE,OAAe,GAC9B;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE;IAsB/C;;OAEG;IACH,kBAAkB,CAChB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAChB,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IAmBpE;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IA6G1B;;OAEG;IACH,OAAO,CAAC,eAAe;IAIvB;;;OAGG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,GAAE,MAAqB,EAAE,cAAc,GAAE,OAAe,GAAG,SAAS,EAAE;IAoFlH;;OAEG;IACH,OAAO,CAAC,eAAe;IAoCvB;;;OAGG;IACH,OAAO,CAAC,aAAa;IA0BrB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAwB1B;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IAwBrB;;OAEG;IACH,OAAO,CAAC,iBAAiB;CAqB1B"}
|
|
@@ -125,6 +125,9 @@ class ScaffoldGenerator {
|
|
|
125
125
|
if (/^(logo|image|img|icon)\b/.test(lowerText) || /^\d*\s*(logo|image|img|icon)\b/.test(lowerText)) {
|
|
126
126
|
return 'img';
|
|
127
127
|
}
|
|
128
|
+
if (/^(dialog|modal)\b/.test(lowerText) || /^\d*\s*(dialog|modal)\b/.test(lowerText)) {
|
|
129
|
+
return 'dialog';
|
|
130
|
+
}
|
|
128
131
|
if (/^(heading|header)\b/.test(lowerText) || /^\d*\s*(heading|header)\b/.test(lowerText)) {
|
|
129
132
|
return 'heading';
|
|
130
133
|
}
|
|
@@ -317,7 +320,7 @@ class ScaffoldGenerator {
|
|
|
317
320
|
case 'see':
|
|
318
321
|
// For assertions - respect targetType for role-based elements
|
|
319
322
|
if (targetType === 'img' || targetType === 'button' || targetType === 'link' ||
|
|
320
|
-
targetType === 'checkbox' || targetType === 'radio' || targetType === 'heading') {
|
|
323
|
+
targetType === 'checkbox' || targetType === 'radio' || targetType === 'heading' || targetType === 'dialog') {
|
|
321
324
|
const seeRoleValue = this.getRoleValue(targetType);
|
|
322
325
|
return {
|
|
323
326
|
locator: '',
|
|
@@ -383,6 +386,8 @@ class ScaffoldGenerator {
|
|
|
383
386
|
return 'img';
|
|
384
387
|
case 'heading':
|
|
385
388
|
return 'heading';
|
|
389
|
+
case 'dialog':
|
|
390
|
+
return 'dialog';
|
|
386
391
|
default:
|
|
387
392
|
return 'button'; // Default to button
|
|
388
393
|
}
|
|
@@ -495,6 +500,119 @@ class ScaffoldGenerator {
|
|
|
495
500
|
}
|
|
496
501
|
return results;
|
|
497
502
|
}
|
|
503
|
+
/**
|
|
504
|
+
* Enrich scaffold with live-scan data if a .live-scan.yaml file exists.
|
|
505
|
+
* Uses the Playwright locator strategy detected by live-scan:
|
|
506
|
+
* testid → type: testid, value: <testid>
|
|
507
|
+
* role → type: role, value: <aria-role>, name: <accessible-name>
|
|
508
|
+
* label → type: label, value: <label-text>
|
|
509
|
+
* placeholder → type: placeholder, value: <placeholder-text>
|
|
510
|
+
* text → type: text, value: <visible-text>
|
|
511
|
+
*/
|
|
512
|
+
enrichWithLiveScan(scaffold, liveScanPath) {
|
|
513
|
+
if (!fs_1.default.existsSync(liveScanPath)) {
|
|
514
|
+
return scaffold;
|
|
515
|
+
}
|
|
516
|
+
let matrixData;
|
|
517
|
+
try {
|
|
518
|
+
const { readMatrix, mergeMatrixElements } = require('../../core/live-scanner/matrix-reader');
|
|
519
|
+
matrixData = readMatrix(liveScanPath);
|
|
520
|
+
if (!matrixData)
|
|
521
|
+
return scaffold;
|
|
522
|
+
const elements = mergeMatrixElements(matrixData);
|
|
523
|
+
if (!elements || Object.keys(elements).length === 0)
|
|
524
|
+
return scaffold;
|
|
525
|
+
console.log(` 🔗 Using live-scan data from ${path_1.default.basename(liveScanPath)}`);
|
|
526
|
+
let enrichedCount = 0;
|
|
527
|
+
let warningCount = 0;
|
|
528
|
+
const enriched = { ...scaffold };
|
|
529
|
+
for (const [key, scaffoldElement] of Object.entries(enriched)) {
|
|
530
|
+
// Try exact key first, then base key without --N suffix or --type suffix
|
|
531
|
+
let liveElement = elements[key];
|
|
532
|
+
if (!liveElement && key.includes('--')) {
|
|
533
|
+
const baseKey = key.replace(/--.*$/, '');
|
|
534
|
+
liveElement = elements[baseKey];
|
|
535
|
+
}
|
|
536
|
+
if (!liveElement) {
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
// If live-scan couldn't find the element, clear the identifying field so user must fill manually
|
|
540
|
+
if (liveElement.matchMethod === 'unresolved') {
|
|
541
|
+
if (scaffoldElement.name !== undefined) {
|
|
542
|
+
enriched[key] = { ...scaffoldElement, name: '' };
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
enriched[key] = { ...scaffoldElement, value: '' };
|
|
546
|
+
}
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
// Use the Playwright locator strategy that live-scan detected
|
|
550
|
+
const selectorType = liveElement.selectorType;
|
|
551
|
+
// Only set exact when true (omit from YAML when false for cleaner output)
|
|
552
|
+
const exactField = liveElement.exact ? { exact: true } : {};
|
|
553
|
+
if (selectorType === 'testid' && liveElement.testid) {
|
|
554
|
+
// page.getByTestId('value')
|
|
555
|
+
enriched[key] = {
|
|
556
|
+
...scaffoldElement,
|
|
557
|
+
type: 'testid',
|
|
558
|
+
value: liveElement.testid,
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
else if (selectorType === 'role' && liveElement.role) {
|
|
562
|
+
// page.getByRole('button', { name: 'Submit' })
|
|
563
|
+
enriched[key] = {
|
|
564
|
+
...scaffoldElement,
|
|
565
|
+
type: 'role',
|
|
566
|
+
value: liveElement.role,
|
|
567
|
+
name: liveElement.name || liveElement.gherkinRef,
|
|
568
|
+
...exactField,
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
else if (selectorType === 'label' && liveElement.label) {
|
|
572
|
+
// page.getByLabel('Username')
|
|
573
|
+
enriched[key] = {
|
|
574
|
+
...scaffoldElement,
|
|
575
|
+
type: 'label',
|
|
576
|
+
value: liveElement.label,
|
|
577
|
+
...exactField,
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
else if (selectorType === 'placeholder' && liveElement.placeholder) {
|
|
581
|
+
// page.getByPlaceholder('Enter email')
|
|
582
|
+
enriched[key] = {
|
|
583
|
+
...scaffoldElement,
|
|
584
|
+
type: 'placeholder',
|
|
585
|
+
value: liveElement.placeholder,
|
|
586
|
+
...exactField,
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
else if (selectorType === 'text') {
|
|
590
|
+
// page.getByText('Welcome')
|
|
591
|
+
enriched[key] = {
|
|
592
|
+
...scaffoldElement,
|
|
593
|
+
type: 'text',
|
|
594
|
+
value: liveElement.name || liveElement.gherkinRef,
|
|
595
|
+
...exactField,
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
else {
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
enrichedCount++;
|
|
602
|
+
if (liveElement.warning) {
|
|
603
|
+
warningCount++;
|
|
604
|
+
console.log(` ⚠️ ${key}: ${liveElement.warning}`);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
const textOnlyCount = Object.keys(enriched).length - enrichedCount;
|
|
608
|
+
console.log(` 📊 Live-scan: ${enrichedCount} enriched, ${textOnlyCount} text-inferred${warningCount > 0 ? `, ${warningCount} warnings` : ''}`);
|
|
609
|
+
return enriched;
|
|
610
|
+
}
|
|
611
|
+
catch (error) {
|
|
612
|
+
console.warn(` ⚠️ Failed to read live-scan data: ${error.message}`);
|
|
613
|
+
return scaffold;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
498
616
|
/**
|
|
499
617
|
* Helper to capitalize first letter
|
|
500
618
|
*/
|
|
@@ -534,6 +652,9 @@ class ScaffoldGenerator {
|
|
|
534
652
|
// Build scaffolds from this file
|
|
535
653
|
const selectorScaffold = this.buildScaffold(elements);
|
|
536
654
|
const testDataScaffold = this.buildTestData(dataRefs);
|
|
655
|
+
// Enrich scaffold with live-scan data if available
|
|
656
|
+
const liveScanPath = path_1.default.join(selectorsDir, `${filename}.live-scan.yaml`);
|
|
657
|
+
const liveScanEnriched = this.enrichWithLiveScan(selectorScaffold, liveScanPath);
|
|
537
658
|
// Check if files exist
|
|
538
659
|
const selectorsPath = path_1.default.join(selectorsDir, `${filename}.yaml`);
|
|
539
660
|
const selectorsOverridePath = path_1.default.join(selectorsDir, `${filename}.override.yaml`);
|
|
@@ -543,11 +664,19 @@ class ScaffoldGenerator {
|
|
|
543
664
|
const testDataExists = fs_1.default.existsSync(testDataPath);
|
|
544
665
|
// Write selectors YAML (BASE ONLY - never touch override files)
|
|
545
666
|
if (!selectorsExists || forceOverwrite) {
|
|
546
|
-
this.writeSelectorsYaml(
|
|
667
|
+
this.writeSelectorsYaml(liveScanEnriched, selectorsPath, filename);
|
|
547
668
|
}
|
|
548
669
|
// Override file is NEVER touched by map command
|
|
549
|
-
// Write test-data YAML
|
|
550
|
-
|
|
670
|
+
// Write test-data YAML — always smart-merge regardless of --force:
|
|
671
|
+
// • existing keys keep their current value
|
|
672
|
+
// • new keys from features are added (empty string)
|
|
673
|
+
// • keys no longer referenced in features are removed
|
|
674
|
+
if (testDataExists) {
|
|
675
|
+
const existingRaw = yaml_1.default.parse(fs_1.default.readFileSync(testDataPath, 'utf-8')) || {};
|
|
676
|
+
const merged = this.mergeTestData(existingRaw, testDataScaffold);
|
|
677
|
+
this.writeTestDataYaml(merged, testDataPath, filename);
|
|
678
|
+
}
|
|
679
|
+
else {
|
|
551
680
|
this.writeTestDataYaml(testDataScaffold, testDataPath, filename);
|
|
552
681
|
}
|
|
553
682
|
// Override file is NEVER touched by map command
|
|
@@ -555,10 +684,10 @@ class ScaffoldGenerator {
|
|
|
555
684
|
featureFile: filename,
|
|
556
685
|
selectorsPath,
|
|
557
686
|
testDataPath,
|
|
558
|
-
elementCount: Object.keys(
|
|
687
|
+
elementCount: Object.keys(liveScanEnriched).length,
|
|
559
688
|
dataRefCount: dataRefs.length,
|
|
560
689
|
selectorsSkipped: selectorsExists && !forceOverwrite,
|
|
561
|
-
|
|
690
|
+
testDataMerged: testDataExists,
|
|
562
691
|
});
|
|
563
692
|
}
|
|
564
693
|
return results;
|
|
@@ -644,14 +773,45 @@ class ScaffoldGenerator {
|
|
|
644
773
|
});
|
|
645
774
|
fs_1.default.writeFileSync(outputPath, header + yamlContent, 'utf-8');
|
|
646
775
|
}
|
|
776
|
+
/**
|
|
777
|
+
* Smart-merge test-data objects:
|
|
778
|
+
* - Keys present in both: keep the existing value (user may have filled it in)
|
|
779
|
+
* - Keys only in fresh (new refs in features): add with empty value
|
|
780
|
+
* - Keys only in existing (removed from features): drop them
|
|
781
|
+
*/
|
|
782
|
+
mergeTestData(existing, fresh) {
|
|
783
|
+
const merged = {};
|
|
784
|
+
for (const key of Object.keys(fresh)) {
|
|
785
|
+
if (key in existing) {
|
|
786
|
+
const existingVal = existing[key];
|
|
787
|
+
const freshVal = fresh[key];
|
|
788
|
+
if (typeof freshVal === 'object' && typeof existingVal === 'object') {
|
|
789
|
+
// Both are nested — recurse
|
|
790
|
+
merged[key] = this.mergeTestData(existingVal, freshVal);
|
|
791
|
+
}
|
|
792
|
+
else {
|
|
793
|
+
// Keep the existing (user-supplied) value
|
|
794
|
+
merged[key] = existingVal;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
// New key — add with the scaffold default (empty string or nested object)
|
|
799
|
+
merged[key] = fresh[key];
|
|
800
|
+
}
|
|
801
|
+
// Keys present in existing but absent from fresh are intentionally dropped (no longer referenced)
|
|
802
|
+
}
|
|
803
|
+
return merged;
|
|
804
|
+
}
|
|
647
805
|
/**
|
|
648
806
|
* Write test-data YAML file
|
|
649
807
|
*/
|
|
650
808
|
writeTestDataYaml(testData, outputPath, screenName) {
|
|
651
|
-
const header = `# ${this.capitalizeFirst(screenName)} Screen Test Data
|
|
652
|
-
#
|
|
653
|
-
#
|
|
654
|
-
#
|
|
809
|
+
const header = `# ${this.capitalizeFirst(screenName)} Screen Test Data
|
|
810
|
+
# Managed by: sungen map (smart-merge)
|
|
811
|
+
# • New {{variable}} refs are added automatically (empty value)
|
|
812
|
+
# • Removed refs are dropped on the next sungen map run
|
|
813
|
+
# • Your existing values are never overwritten
|
|
814
|
+
# To fix a value permanently, copy the key to ${screenName}.override.yaml
|
|
655
815
|
#
|
|
656
816
|
# Reference in features using {{variable}} syntax
|
|
657
817
|
|