@sun-asterisk/sungen 3.2.9 → 3.2.10-beta.11
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/capabilities/discover.d.ts.map +1 -1
- package/dist/capabilities/discover.js +5 -2
- package/dist/capabilities/discover.js.map +1 -1
- package/dist/capabilities/registry.d.ts +9 -0
- package/dist/capabilities/registry.d.ts.map +1 -1
- package/dist/capabilities/registry.js +4 -0
- package/dist/capabilities/registry.js.map +1 -1
- package/dist/cli/commands/capability.d.ts.map +1 -1
- package/dist/cli/commands/capability.js +44 -2
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +15 -5
- 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 +16 -0
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/exporters/spec-parser.d.ts.map +1 -1
- package/dist/exporters/spec-parser.js +16 -5
- package/dist/exporters/spec-parser.js.map +1 -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/actions/capture-row-column.hbs +2 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/click-element-with-text.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/click-with-alert-action.hbs +2 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/recipe-sequence.hbs +3 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row-nth.hbs +2 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/route-assertion.hbs +3 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-for-element.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-table-refresh.hbs +3 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/has-text-value.hbs +1 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-blur.hbs +1 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-click-text.hbs +3 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-click.hbs +1 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-expect-text.hbs +1 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-expect-value.hbs +1 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-for-each.hbs +5 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-hover.hbs +1 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-press.hbs +1 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-scroll-into-view.hbs +1 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-type.hbs +1 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-wait-for.hbs +1 -0
- package/dist/generators/test-generator/app-profile.d.ts +89 -0
- package/dist/generators/test-generator/app-profile.d.ts.map +1 -0
- package/dist/generators/test-generator/app-profile.js +157 -0
- package/dist/generators/test-generator/app-profile.js.map +1 -0
- package/dist/generators/test-generator/code-generator.d.ts +4 -0
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +7 -0
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/diagnostics.d.ts +36 -0
- package/dist/generators/test-generator/diagnostics.d.ts.map +1 -0
- package/dist/generators/test-generator/diagnostics.js +44 -0
- package/dist/generators/test-generator/diagnostics.js.map +1 -0
- package/dist/generators/test-generator/patterns/types.d.ts +12 -0
- package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
- package/dist/generators/test-generator/recipe-expander.d.ts +32 -0
- package/dist/generators/test-generator/recipe-expander.d.ts.map +1 -0
- package/dist/generators/test-generator/recipe-expander.js +118 -0
- package/dist/generators/test-generator/recipe-expander.js.map +1 -0
- package/dist/generators/test-generator/step-mapper.d.ts +17 -0
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +70 -0
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/template-engine.d.ts +5 -1
- package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
- package/dist/generators/test-generator/template-engine.js +27 -3
- package/dist/generators/test-generator/template-engine.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts +10 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +32 -0
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/harness/capability.d.ts +1 -0
- package/dist/harness/capability.d.ts.map +1 -1
- package/dist/harness/capability.js.map +1 -1
- package/dist/harness/catalog/drivers.yaml +2 -1
- package/dist/harness/query-catalog.d.ts +32 -2
- package/dist/harness/query-catalog.d.ts.map +1 -1
- package/dist/harness/query-catalog.js +0 -0
- package/dist/harness/query-catalog.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/create-data-test.md +99 -0
- package/dist/orchestrator/templates/ai-src/commands/create-test.md +3 -0
- package/dist/orchestrator/templates/ai-src/commands/run-test.md +1 -0
- package/dist/orchestrator/templates/ai-src/config/claude.md +3 -1
- package/dist/orchestrator/templates/ai-src/config/copilot.md +3 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-data-factory/SKILL.md +116 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +12 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +43 -8
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +22 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +14 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +1 -0
- package/dist/orchestrator/templates/specs-db.d.ts +201 -11
- package/dist/orchestrator/templates/specs-db.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-db.js +440 -91
- package/dist/orchestrator/templates/specs-db.js.map +1 -1
- package/dist/orchestrator/templates/specs-db.ts +463 -79
- package/dist/orchestrator/templates/specs-test-data.ts +39 -3
- package/package.json +8 -4
- package/src/capabilities/discover.ts +5 -2
- package/src/capabilities/registry.ts +13 -0
- package/src/cli/commands/capability.ts +40 -2
- package/src/cli/commands/delivery.ts +13 -5
- package/src/cli/commands/generate.ts +15 -0
- package/src/exporters/spec-parser.ts +17 -5
- 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/actions/capture-row-column.hbs +2 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/click-element-with-text.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/click-with-alert-action.hbs +2 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/recipe-sequence.hbs +3 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row-nth.hbs +2 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/route-assertion.hbs +3 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-for-element.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-table-refresh.hbs +3 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/has-text-value.hbs +1 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-blur.hbs +1 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-click-text.hbs +3 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-click.hbs +1 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-expect-text.hbs +1 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-expect-value.hbs +1 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-for-each.hbs +5 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-hover.hbs +1 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-press.hbs +1 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-scroll-into-view.hbs +1 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-type.hbs +1 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-wait-for.hbs +1 -0
- package/src/generators/test-generator/app-profile.ts +185 -0
- package/src/generators/test-generator/code-generator.ts +9 -0
- package/src/generators/test-generator/diagnostics.ts +51 -0
- package/src/generators/test-generator/patterns/types.ts +12 -0
- package/src/generators/test-generator/recipe-expander.ts +129 -0
- package/src/generators/test-generator/step-mapper.ts +73 -0
- package/src/generators/test-generator/template-engine.ts +29 -3
- package/src/generators/test-generator/utils/selector-resolver.ts +49 -2
- package/src/harness/capability.ts +1 -0
- package/src/harness/catalog/drivers.yaml +2 -1
- package/src/harness/query-catalog.ts +0 -0
- package/src/index.ts +5 -2
- package/src/orchestrator/templates/ai-src/commands/create-data-test.md +99 -0
- package/src/orchestrator/templates/ai-src/commands/create-test.md +3 -0
- package/src/orchestrator/templates/ai-src/commands/run-test.md +1 -0
- package/src/orchestrator/templates/ai-src/config/claude.md +3 -1
- package/src/orchestrator/templates/ai-src/config/copilot.md +3 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-data-factory/SKILL.md +116 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +12 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +43 -8
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +22 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +14 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +1 -0
- package/src/orchestrator/templates/specs-db.ts +463 -79
- package/src/orchestrator/templates/specs-test-data.ts +39 -3
|
@@ -5,6 +5,8 @@ import { readYaml, readYamlIfExists } from '../../../utils/yaml-io';
|
|
|
5
5
|
|
|
6
6
|
import { SelectorType } from '../../../utils/selector-types';
|
|
7
7
|
import { DataResolver } from './data-resolver';
|
|
8
|
+
import { DiagnosticsCollector } from '../diagnostics';
|
|
9
|
+
import { DEFAULT_CONTENT_NAMED_ROLES } from '../app-profile';
|
|
8
10
|
|
|
9
11
|
// Structured selector format v2
|
|
10
12
|
interface SelectorEntry {
|
|
@@ -27,7 +29,9 @@ interface SelectorEntry {
|
|
|
27
29
|
ios?: SelectorEntry;
|
|
28
30
|
|
|
29
31
|
// === v2: Complex Elements ===
|
|
30
|
-
|
|
32
|
+
// Dropdown/upload subtype: 'native' | 'custom' | 'dragdrop', or a widget-recipe/variant
|
|
33
|
+
// name ('radix-select', 'office-datepicker', …) — wins over qa/app.yaml widgets.<kind>.
|
|
34
|
+
variant?: string;
|
|
31
35
|
trigger?: string; // Visible button that triggers hidden file input
|
|
32
36
|
frame?: string; // iframe selector for cross-frame elements
|
|
33
37
|
contenteditable?: boolean; // Rich text editor (contenteditable div)
|
|
@@ -96,7 +100,7 @@ export interface ResolvedSelector {
|
|
|
96
100
|
// === v2 fields ===
|
|
97
101
|
scope?: string; // Parent landmark to scope within
|
|
98
102
|
match?: 'exact' | 'partial'; // For getByText exact matching
|
|
99
|
-
variant?:
|
|
103
|
+
variant?: string; // Dropdown/upload subtype or widget-recipe name
|
|
100
104
|
trigger?: string; // Button that triggers hidden file input
|
|
101
105
|
frame?: string; // iframe selector
|
|
102
106
|
contenteditable?: boolean; // Rich text editor
|
|
@@ -117,12 +121,26 @@ export class SelectorResolver {
|
|
|
117
121
|
private featureName?: string;
|
|
118
122
|
private screenName?: string;
|
|
119
123
|
private flowMode: boolean = false;
|
|
124
|
+
/** SG-W003 channel + config: roles whose accessible name derives from content. */
|
|
125
|
+
private diagnostics?: DiagnosticsCollector;
|
|
126
|
+
private contentNamedRoles: string[] = DEFAULT_CONTENT_NAMED_ROLES;
|
|
127
|
+
private warnedContentNamed = new Set<string>();
|
|
120
128
|
|
|
121
129
|
constructor(selectorsDir?: string, screenName?: string) {
|
|
122
130
|
this.selectorsDir = selectorsDir || path.join(process.cwd(), 'qa', 'selectors', 'screens');
|
|
123
131
|
this.screenName = screenName;
|
|
124
132
|
}
|
|
125
133
|
|
|
134
|
+
/** Attach the compile-diagnostics collector (SG-W003) */
|
|
135
|
+
setDiagnostics(collector: DiagnosticsCollector): void {
|
|
136
|
+
this.diagnostics = collector;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Override the name-from-content role list from the app profile (qa/app.yaml feedback.toast) */
|
|
140
|
+
setContentNamedRoles(roles: string[]): void {
|
|
141
|
+
this.contentNamedRoles = roles;
|
|
142
|
+
}
|
|
143
|
+
|
|
126
144
|
setFlowMode(enabled: boolean): void {
|
|
127
145
|
this.flowMode = enabled;
|
|
128
146
|
}
|
|
@@ -390,6 +408,20 @@ export class SelectorResolver {
|
|
|
390
408
|
if (elementType) {
|
|
391
409
|
const inferred = SelectorResolver.inferFromElementType(elementType, displayLabel);
|
|
392
410
|
if (inferred) {
|
|
411
|
+
// Same SG-W003 class as the explicit-entry path: an auto-inferred name on a
|
|
412
|
+
// name-from-content role ([Loading] spinner → getByRole('status', { name: 'Loading' }))
|
|
413
|
+
// can never match. Add a YAML entry with name: null to opt out.
|
|
414
|
+
if (inferred.strategy === 'role' && inferred.name && inferred.role && this.contentNamedRoles.includes(inferred.role)) {
|
|
415
|
+
const warnKey = `${inferred.role}:${displayLabel}`;
|
|
416
|
+
if (!this.warnedContentNamed.has(warnKey)) {
|
|
417
|
+
this.warnedContentNamed.add(warnKey);
|
|
418
|
+
this.diagnostics?.add(
|
|
419
|
+
'SG-W003',
|
|
420
|
+
`role "${inferred.role}" derives its accessible name from content, but "[${displayLabel}]" auto-inferred { name: '${inferred.name}' } which will likely never match. Add a selectors.yaml entry with an explicit name: or name: null`,
|
|
421
|
+
{ feature: this.featureName }
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
393
425
|
return inferred;
|
|
394
426
|
}
|
|
395
427
|
}
|
|
@@ -566,6 +598,21 @@ export class SelectorResolver {
|
|
|
566
598
|
});
|
|
567
599
|
|
|
568
600
|
case 'role':
|
|
601
|
+
// SG-W003 — the entry has no `name:` so the compiler defaulted it to the Gherkin
|
|
602
|
+
// reference text. For name-from-content roles (status/alert/…) that filter can
|
|
603
|
+
// never match the live element (its accessible name is computed from its content,
|
|
604
|
+
// e.g. a toast's message text) — issue #467. Opt out with `name: null`.
|
|
605
|
+
if (entry.name === undefined && name && this.contentNamedRoles.includes(value)) {
|
|
606
|
+
const warnKey = `${value}:${originalLabel}`;
|
|
607
|
+
if (!this.warnedContentNamed.has(warnKey)) {
|
|
608
|
+
this.warnedContentNamed.add(warnKey);
|
|
609
|
+
this.diagnostics?.add(
|
|
610
|
+
'SG-W003',
|
|
611
|
+
`role "${value}" derives its accessible name from content, but the entry for "${originalLabel}" has no name: — the defaulted { name: '${name}' } will likely never match. Set an explicit name: or opt out with name: null`,
|
|
612
|
+
{ feature: this.featureName }
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
569
616
|
return withExtras({
|
|
570
617
|
strategy: 'role',
|
|
571
618
|
role: value,
|
|
@@ -24,6 +24,7 @@ export interface DriverMeta {
|
|
|
24
24
|
package: string;
|
|
25
25
|
runtime?: string;
|
|
26
26
|
adapter?: string; // registry adapter name (defaults to id)
|
|
27
|
+
bundled?: boolean; // shipped as a core dependency — present without `capability add`
|
|
27
28
|
capabilities: string[];
|
|
28
29
|
unblocks?: string[];
|
|
29
30
|
}
|
|
@@ -51,7 +51,8 @@ drivers:
|
|
|
51
51
|
data-factory:
|
|
52
52
|
kind: capability
|
|
53
53
|
package: "@sungen/driver-data-factory"
|
|
54
|
-
status:
|
|
54
|
+
status: shipped
|
|
55
|
+
bundled: true # runtime-free knowledge+codegen — shipped by default (no `capability add` needed)
|
|
55
56
|
capabilities: ["@dataFactory"]
|
|
56
57
|
unblocks: [M1]
|
|
57
58
|
mock:
|
|
Binary file
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,9 @@ export type { DiscoveredContext } from './orchestrator/context-discovery';
|
|
|
14
14
|
|
|
15
15
|
// --- Step-pattern authoring (a driver contributes step patterns via its descriptor) ---
|
|
16
16
|
export type { PatternContext, StepPattern, StepTemplateData } from './generators/test-generator/patterns/types';
|
|
17
|
+
export { expandRecipe } from './generators/test-generator/recipe-expander';
|
|
18
|
+
export type { RecipeExpandContext, RecipeNode } from './generators/test-generator/recipe-expander';
|
|
19
|
+
export type { ResolvedAppProfile } from './generators/test-generator/app-profile';
|
|
17
20
|
export type { MappedStep } from './generators/test-generator/step-mapper';
|
|
18
21
|
export type { ParsedStep } from './generators/gherkin-parser';
|
|
19
22
|
export { getPathCode, inferPath, resolvePathVariables } from './generators/test-generator/utils/path-inference';
|
|
@@ -22,8 +25,8 @@ export { getPathCode, inferPath, resolvePathVariables } from './generators/test-
|
|
|
22
25
|
export { parseQueryOverrides } from './harness/annotation-overrides';
|
|
23
26
|
|
|
24
27
|
// --- Named-query catalog (shared: the DB driver's codegen + core's data-driven advisory lint) ---
|
|
25
|
-
export { resolveQuery, compileQuery, lintCatalog } from './harness/query-catalog';
|
|
26
|
-
export type { QueryEntry } from './harness/query-catalog';
|
|
28
|
+
export { resolveQuery, compileQuery, compileShape, isShapeEntry, lintCatalog } from './harness/query-catalog';
|
|
29
|
+
export type { QueryEntry, MongoFind, MongoPipeline } from './harness/query-catalog';
|
|
27
30
|
|
|
28
31
|
// --- Shared harness: viewpoint catalog + coverage gate / assertion depth ---
|
|
29
32
|
// (the UI capability's gateProvider composes these; they also back core's ingest + audit fallback)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-data-test
|
|
3
|
+
description: "Generate standardized test-data for a screen/flow from the Data Factory catalog — valid/boundary/invalid values with CHK-* traceability (no free-form guessing)"
|
|
4
|
+
argument-hint: [screen-or-flow-name]
|
|
5
|
+
order: 25
|
|
6
|
+
claude-tools: "Read, Grep, Bash, Glob, Write, Edit, AskUserQuestion, Skill"
|
|
7
|
+
copilot-tools: "[vscode, execute, read, agent, edit, search, todo]"
|
|
8
|
+
codex-trigger: "Run when the user asks to CREATE, generate, or standardize TEST DATA / test values for a screen or flow (valid/boundary/invalid, boundary values, negative data). Uses the Data Factory driver. Not for generating the .feature scenarios themselves (that is create-test)."
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
**Input**: A screen or flow name (e.g., `/sungen:create-data-test login`), OR **no name** to do every unit (with a confirmation — see ALL mode). Load the `sungen-data-factory` skill for the full method.
|
|
12
|
+
|
|
13
|
+
> **When to use.** `/sungen:create-test` already standardizes field test-data via Data Factory **inline** (its step 5.3), so you normally do **not** need this command as a follow-up. Use it **on demand** to *re-standardize*: after you edit a field-map or the `.overwrite`, for a unit authored before Data Factory existed, to refresh error-code mappings, or to (re)generate across many units at once (ALL mode). It MERGES into existing test-data (never clobbers).
|
|
14
|
+
|
|
15
|
+
## Role
|
|
16
|
+
|
|
17
|
+
You are a **Senior QA Engineer** producing **test data to a standard**, not by guessing. You do NOT invent values field-by-field. You follow the 4-source method (spec rules → field-type catalog → design technique → DB mapping), driven by the **Data Factory** capability driver. Values come from the catalog (`common/` + the project's `.overwrite`), each tagged with a `CHK-*` id that traces back to the spec. Where the catalog cannot cover a project-specific field, you **ask the QA** — you never fabricate domain data.
|
|
18
|
+
|
|
19
|
+
## Preconditions
|
|
20
|
+
|
|
21
|
+
1. **Availability** — Data Factory is **bundled with core** (no `capability add` needed); its CLI (`sungen data …`) is available out of the box. Just continue.
|
|
22
|
+
2. Determine mode from `$ARGUMENTS`:
|
|
23
|
+
- A **name** given → single-unit mode (that screen/flow only).
|
|
24
|
+
- **No name** → **ALL mode** (see below — always confirm first).
|
|
25
|
+
|
|
26
|
+
## ALL mode (no name) — confirm before doing everything
|
|
27
|
+
|
|
28
|
+
If no name was given, the user wants **all** screens/flows — but this is wide, and a missing name is often a typo (they meant `create-data-test home`). So:
|
|
29
|
+
|
|
30
|
+
1. List every unit under `qa/screens/*` and `qa/flows/*`, and classify each as **new** (no `test-data/<name>.yaml` yet) vs **would-overwrite**.
|
|
31
|
+
2. Show the list and use `AskUserQuestion` to confirm: *"Generate test-data for ALL N units? (X new · Y would overwrite)"* — default to **No**.
|
|
32
|
+
3. Only proceed when the user confirms. Then run the per-unit flow below for each (the CLI `sungen data gen --all` also enforces this confirm; `--yes` after the user agrees).
|
|
33
|
+
|
|
34
|
+
## Per-unit flow (single unit, or each unit in ALL mode)
|
|
35
|
+
|
|
36
|
+
Auto-detect: `qa/flows/<name>/` → flow; else `qa/screens/<name>/` → screen. (API units carry their contract in `apis.yaml` — data there is `@cases` in create-test, not this command.)
|
|
37
|
+
|
|
38
|
+
### 1. Read the sources (Nguồn 1 + 4)
|
|
39
|
+
|
|
40
|
+
Read `qa/<screens|flows>/<name>/requirements/spec.md` and `test-viewpoint.md`. For **each input field / parameter**, extract:
|
|
41
|
+
- data **type** (map to a catalog field-type: email · password · phone · string · number-integer · number-float · currency · postcode · katakana …),
|
|
42
|
+
- `required`, format, **min/max** length or value,
|
|
43
|
+
- the real **error/message code** the spec shows when a rule is violated,
|
|
44
|
+
- any **DB mapping** (unique, cross-entity dependency) noted in the spec.
|
|
45
|
+
|
|
46
|
+
Viewpoint tells you which fields are high-priority → give them the fullest data.
|
|
47
|
+
|
|
48
|
+
**No input fields → nothing to generate.** If the unit is a pure navigation / listing / capture-compare flow (e.g. a home-shopping browse flow) with **no user-input fields or parameters**, Data Factory has nothing to standardize. Report *"`<name>`: no input fields → nothing to generate"* and stop for this unit — do **not** write an empty `qa/data-factory/<name>.fields.yaml`. (Its scenarios are covered by create-test's data, not by field-level test data.)
|
|
49
|
+
|
|
50
|
+
### 2. Write the field-map — `qa/data-factory/<name>.fields.yaml`
|
|
51
|
+
|
|
52
|
+
This is the input the deterministic generator expands. One entry per field:
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
screen: <name> # or: flow: <name>
|
|
56
|
+
fields:
|
|
57
|
+
- name: email
|
|
58
|
+
type: email
|
|
59
|
+
required: true
|
|
60
|
+
unique: true # fresh-per-run (register email won't collide)
|
|
61
|
+
constraints: { maxLocal: 64, maxTotal: 254 } # project's real limits
|
|
62
|
+
errorMap: { M13: E1042, M02: E1001 } # catalog placeholder → project's real code
|
|
63
|
+
- name: password
|
|
64
|
+
type: password
|
|
65
|
+
constraints: { minLength: 8, maxLength: 32 }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**`unique: true`** — set it for a field that must be **different every run** (register email/username that can't clash with a record a previous run created). The valid value then gets a runtime `{{$timestamp}}` (email → `user+{{$timestamp}}@…`), so each run is fresh — no cross-run cache, no order-dependent flakiness. Boundary/invalid values stay literal (they test rules, not record creation).
|
|
69
|
+
|
|
70
|
+
Map every placeholder error code you can (`errorMap`) to the spec's real code. Leave a code unmapped only if the spec truly has none — the lint will flag it as an open point to confirm with BA/Dev.
|
|
71
|
+
|
|
72
|
+
**Complex forms — nested & dependent fields.** For a grouped/object field, give it **`fields:`** (sub-fields, expanded recursively; no `type` on the group). For a field whose rule depends on another, add **`dependsOn: { field: <other>, value: <v> }`** — it's recorded as a note so the scenario/`@cases` honor it (the generator doesn't resolve the runtime condition):
|
|
73
|
+
```yaml
|
|
74
|
+
fields:
|
|
75
|
+
- name: address # group → nested object
|
|
76
|
+
fields:
|
|
77
|
+
- { name: city, type: string, constraints: { maxLength: 50 } }
|
|
78
|
+
- { name: postcode, type: postcode, dependsOn: { field: country, value: JP } }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 3. Blind spots — ask, do not invent
|
|
82
|
+
|
|
83
|
+
If a field's type is **not** in the catalog (a business enum, a cross-entity constraint, a project data model), **stop and ask the QA** with `AskUserQuestion` for: (1) the valid set, (2) invalid / non-existent values, (3) the error code when wrong, (4) dependencies on other fields. Record the answer in `qa/data-factory/project/<entity>.yaml` for reuse. Never fabricate domain values.
|
|
84
|
+
|
|
85
|
+
### 4. Lint the coverage
|
|
86
|
+
|
|
87
|
+
Run (local-first): `[ -x ./bin/sungen.js ] && ./bin/sungen.js data lint --screen <name> || npx sungen data lint --screen <name>`. Fix what it reports: add missing boundary values, resolve `UNRESOLVED` items (supply the constraint), map `OPEN_ERRORREF` codes. The bar is the phuong-phap §6 checklist — every required field has valid + boundary + invalid, one invalid per rule.
|
|
88
|
+
|
|
89
|
+
### 5. Generate the test-data
|
|
90
|
+
|
|
91
|
+
Run (local-first): `[ -x ./bin/sungen.js ] && ./bin/sungen.js data gen --screen <name> || npx sungen data gen --screen <name>`. If `test-data/<name>.yaml` already exists it **merges** the standardized field data in — it **replaces only the field keys it owns and preserves every other key** (create-test's data, flow-namespaced keys, `@cases` datasets, overrides). It asks before merging (in ALL mode after they confirmed, add `--yes`; `--force` skips the prompt). The output is standardized `valid/boundary/invalid` values with `CHK-*` traceability.
|
|
92
|
+
|
|
93
|
+
### 6. Weave into scenarios (if create-test already ran)
|
|
94
|
+
|
|
95
|
+
If the `.feature` exists, turn the generated invalid/boundary sets into data-driven `Scenario Outline` + `Examples` (`@cases`) referencing the values — following `sungen-gherkin-syntax`. Otherwise leave the standardized `test-data/<name>.yaml` for `/sungen:run-test` to consume.
|
|
96
|
+
|
|
97
|
+
## After running
|
|
98
|
+
|
|
99
|
+
Use `AskUserQuestion` to offer the next step: review the generated data, run `/sungen:create-test <name>` (if scenarios don't exist yet), or `/sungen:run-test <name>`.
|
|
@@ -167,6 +167,8 @@ If the unit is **api-first** (`qa/api/<name>/` or `qa/api/flows/<name>/`), the d
|
|
|
167
167
|
|
|
168
168
|
**5d. Sequential fallback.** Use the single-context incremental path (Step 2: tier-by-tier `Write`/`Edit` batches) when: only **one** shard applies, **Copilot / no sub-agents**, or a constrained setup. Same output, just no speedup. **For flows**: `[Screen:Element]` namespace refs, test-data namespaced by phase, `@flow` tag.
|
|
169
169
|
|
|
170
|
+
5.3. **Standardize field test-data — Data Factory (when the unit has input fields).** Don't hand-invent per-field values — make them **standard**. For each input field, author the field-map `qa/data-factory/<name>.fields.yaml` (field → catalog `type` + the spec's real `constraints` + `errorMap` placeholder→real code), then run (Bash, local-first) `[ -x ./bin/sungen.js ] && ./bin/sungen.js data lint --screen <name> || npx sungen data lint --screen <name>` and `… data gen --screen <name>`, per the **`sungen-data-factory`** skill. `data gen` **MERGES** into `test-data.yaml` (replaces only its field keys, preserves your scenario/namespaced data), so it is safe to run here. Weave the standardized **boundary/invalid** sets into `@cases` (`Scenario Outline` + `Examples`) with `CHK-*` trace; keep the map's `errorMap`/`constraints` aligned to the spec. **This is the same standard as `/sungen:create-data-test`, applied inline — so you do NOT run that command separately afterwards.** A unit with **no input fields** (navigation / list / capture-compare) → **skip this step** (nothing to standardize).
|
|
171
|
+
|
|
170
172
|
5.4. **Depth self-check (deterministic — run BEFORE the audit).** Run `sungen depth-lint --screen <name>` (Bash). It reuses the audit's businessDepth classifier and splits every shallow business-critical scenario into two actionable buckets — act on them now so the audit/repair loop doesn't burn rounds on depth:
|
|
171
173
|
- **DEEPEN IN PLACE** — add a real value assertion to each (`User see all [X] contain {{v}}`, `User remember [X] as {{v}}` + `… with {{v}}`). The printed `template` is a **hint** keyed off the theme — apply judgment to the scenario's actual claim; do NOT paste a value assertion that doesn't fit (e.g. a carousel-visibility scenario should assert the product SET, not a price). If a flagged scenario is genuinely visibility/behavior (not data-correctness), that's an over-count — leave it and note it, never fake an assertion.
|
|
172
174
|
- **CROSS-SCREEN** — route to a flow (`/sungen:add-flow`) or tag `@manual:Mx` + reason; do NOT fake an on-screen data assertion. This removes it from the screen's depth denominator honestly.
|
|
@@ -194,6 +196,7 @@ If the unit is **api-first** (`qa/api/<name>/` or `qa/api/flows/<name>/`), the d
|
|
|
194
196
|
4. Follow the `sungen-tc-generation` skill for section identification, viewpoint generation, and output format. **For flows**, use the "Flow Test Generation" section in the skill. When requirements exist, use the "Requirements-Driven Generation" strategy. **For Tier 1**, apply the **Lightweight Guard** — verify required fields, validation rules, business rules, security checks, and key state transitions all have TCs after generation. **For Tier 2+**, **MUST** apply the full **Mapping Contract** — walk every `spec.md` section top-to-bottom and produce the indicated TCs per Table 1; handle `test-viewpoint.md` per Table 2. Do not silently skip sections. Present sections as a numbered list and let user pick.
|
|
195
197
|
5. Generate or update `.feature` + `test-data.yaml` following `sungen-gherkin-syntax` and `sungen-tc-generation` skills. Generate **group-by-group** (one viewpoint group at a time, tier-by-tier `Write`/`Edit` batches) to stay under the output-token cap. **For flows**: use `[Screen:Element]` namespace format, namespace test-data by phase, add `@flow` tag. **If a scenario needs `@query` DB verification**, check the datasource `engine` first — unsupported engine → follow `sungen-gherkin-syntax` § "Unsupported DB engine — fallback" instead of authoring `@query` steps.
|
|
196
198
|
> **No parallel fan-out here.** Copilot has no sub-agents, so generation is sequential (the Claude Code variant fans out one `sungen-generator` per viewpoint group and merges). Same output, no speedup.
|
|
199
|
+
5.3. **Standardize field test-data — Data Factory (when the unit has input fields).** For each input field, author the field-map `qa/data-factory/<name>.fields.yaml` (field → catalog `type` + real `constraints` + `errorMap`), then run `sungen data lint --screen ${input:name}` and `sungen data gen --screen ${input:name}` per the **`sungen-data-factory`** skill. `data gen` **MERGES** into `test-data.yaml` (replaces only its field keys, preserves the rest), so it is safe here. Weave the standardized boundary/invalid sets into `@cases` with `CHK-*` trace. **Same standard as `/sungen:create-data-test`, applied inline — do NOT run that command separately afterwards.** No input fields (navigation/list) → skip.
|
|
197
200
|
5.4. **Depth self-check (deterministic — BEFORE the audit).** Run `sungen depth-lint --screen ${input:name}`. It splits every shallow business-critical scenario into **DEEPEN IN PLACE** (add a real value assertion — the printed `template` is a theme-keyed hint, apply judgment to the actual claim; never fake one onto a visibility/behavior scenario) and **CROSS-SCREEN** (route to a flow / tag `@manual:Mx` + reason — removes it from the depth denominator honestly). Act on both, re-run until `deepen` is empty (or only honest over-counts remain), THEN gate. Lifts first-pass `businessDepth` mechanically instead of via 2–3 repair rounds.
|
|
198
201
|
5.5. **Quality gate & repair (harness — always run).** Per `sungen-harness-audit`: run `sungen audit --screen ${input:name}` (structural), THEN do an **independent semantic review inline** using the `sungen-reviewer` criteria (does each scenario's steps PROVE its title/viewpoint? observable Thens? business-critical assertion depth?). Merge both sets of issues; if gate FAILs / findings exist, repair (budget 3) and re-audit — GATE missing theme → generate it (cross-screen → **automate it in the flow** via `/sungen:add-flow`, NOT a full `@manual` screen duplicate — `sungen audit` flags an automatable `@manual` as `MANUAL-AUTOMATABLE`; reserve `@manual:Mx` for true judgment/missing-capability); DEPTH → add data assertions; BALANCE → add business-core first; TRACE → align VP ids. Never fake a pass.
|
|
199
202
|
5.6. **Record.** `sungen manifest --screen ${input:name}`. Ledger **each phase** (not just repair) — pick one `runId` at the start and pass it so `trace`/`ledger report` show THIS run, not a mix: `sungen ledger record --screen ${input:name} --run <runId> --step <discovery|viewpoint|gherkin|audit|repair:N> --ms <elapsed>`. On re-run, start with `sungen manifest --screen ${input:name} --diff` and only regenerate changed sections.
|
|
@@ -127,6 +127,7 @@ If the unit is **api-first**, skip every selector/capture phase (an API test has
|
|
|
127
127
|
## Pre-run (phased — per `sungen-selector-fix` skill)
|
|
128
128
|
|
|
129
129
|
1. Verify `<base>/<name>/` has `.feature` + `test-data.yaml`. **If the feature has `@query` steps**, check the resolved datasource `engine` in `qa/datasources.yaml` — unsupported engine → follow the `sungen-gherkin-syntax` skill § "Unsupported DB engine — fallback" before compiling (do not attempt a direct connect).
|
|
130
|
+
1.5. **Data Factory pre-check (before running)** — Data Factory is bundled with core, so whenever a field-map exists (`qa/data-factory/<name>.fields.yaml`), confirm the test-data is standardized and covers each field before you run: `[ -x ./bin/sungen.js ] && ./bin/sungen.js data lint --screen <name> || npx sungen data lint --screen <name>`. If it reports coverage gaps (missing boundary/invalid, unresolved values, unmapped error codes) or the field-map is newer than `test-data/<name>.yaml`, refresh via `/sungen:create-data-test <name>` (or `sungen data gen --screen <name>`) — then compile. Blind spots (a `type` not in the catalog) are for the QA to answer, not to invent. No field-map / driver disabled → skip this step.
|
|
130
131
|
2. **Phase 0 — Selector Pre-gen**: if `selectors.yaml` is missing/empty or doesn't cover the feature file's `[Reference]`s, apply the following decision tree before running Phase 0 from `sungen-selector-fix`:
|
|
131
132
|
|
|
132
133
|
```
|
|
@@ -18,14 +18,16 @@ You generate 3 files for sungen — a Gherkin compiler that produces Playwright
|
|
|
18
18
|
| `sungen-delivery` | Export Gherkin + Playwright results → CSV test case deliverable |
|
|
19
19
|
| `sungen-capture` | Acquire visual/design context — one skill, 4 modes: figma-mcp (Dev Mode MCP), figma-pat (URL → spec_figma.md), live (Playwright MCP scan), local (images in `requirements/ui/`) |
|
|
20
20
|
| `sungen-locale` | Bootstrap i18n — audit selectors, detect locale switch mechanism, generate test-data overlay |
|
|
21
|
+
| `sungen-data-factory` | Standardized test-data — field-type/security catalog, 4-source method, `.overwrite`, blind-spot prompts |
|
|
21
22
|
|
|
22
|
-
## Workflow (
|
|
23
|
+
## Workflow (8 AI commands)
|
|
23
24
|
|
|
24
25
|
| Command | What it does |
|
|
25
26
|
|---|---|
|
|
26
27
|
| `/sungen:add-screen <name> <path>` | Scaffold `qa/screens/<name>/` directories |
|
|
27
28
|
| `/sungen:add-flow <name> [--path <url>]` | Scaffold `qa/flows/<name>/` directories for E2E cross-screen testing |
|
|
28
29
|
| `/sungen:create-test <name>` | Generate `.feature` + `test-data.yaml` (auto-detects screen or flow) |
|
|
30
|
+
| `/sungen:create-data-test <name>` | Generate standardized test-data (valid/boundary/invalid + CHK trace) from the Data Factory catalog; no name = all units (asks to confirm) |
|
|
29
31
|
| `/sungen:review <name>` | Score syntax, coverage, viewpoint quality (auto-detects screen or flow) |
|
|
30
32
|
| `/sungen:run-test <name>` | Generate `selectors.yaml`, compile, run, auto-fix (auto-detects screen or flow) |
|
|
31
33
|
| `/sungen:delivery [name...]` | Export test cases → CSV for QA delivery (all screens if no arg) |
|
|
@@ -18,14 +18,16 @@ You generate 3 files for sungen — a Gherkin compiler that produces Playwright
|
|
|
18
18
|
| `sungen-delivery` | Export Gherkin + Playwright results → CSV test case deliverable |
|
|
19
19
|
| `sungen-capture` | Acquire visual/design context — one skill, 4 modes: figma-mcp (Dev Mode MCP), figma-pat (URL → spec_figma.md), live (Playwright MCP scan), local (images in `requirements/ui/`) |
|
|
20
20
|
| `sungen-locale` | Bootstrap i18n — audit selectors, detect locale switch mechanism, generate test-data overlay |
|
|
21
|
+
| `sungen-data-factory` | Standardized test-data — field-type/security catalog, 4-source method, `.overwrite`, blind-spot prompts |
|
|
21
22
|
|
|
22
|
-
## Workflow (
|
|
23
|
+
## Workflow (8 AI commands)
|
|
23
24
|
|
|
24
25
|
| Command | What it does |
|
|
25
26
|
|---|---|
|
|
26
27
|
| `/sungen-add-screen <name> <path>` | Scaffold `qa/screens/<name>/` directories |
|
|
27
28
|
| `/sungen-add-flow <name> [--path <url>]` | Scaffold `qa/flows/<name>/` directories for E2E cross-screen testing |
|
|
28
29
|
| `/sungen-create-test <name>` | Generate `.feature` + `test-data.yaml` (auto-detects screen or flow) |
|
|
30
|
+
| `/sungen-create-data-test <name>` | Generate standardized test-data (valid/boundary/invalid + CHK trace) from the Data Factory catalog; no name = all units (asks to confirm) |
|
|
29
31
|
| `/sungen-review <name>` | Score syntax, coverage, viewpoint quality (auto-detects screen or flow) |
|
|
30
32
|
| `/sungen-run-test <name>` | Generate `selectors.yaml`, compile, run, auto-fix (auto-detects screen or flow) |
|
|
31
33
|
| `/sungen-delivery [name...]` | Export test cases → CSV for QA delivery (all screens if no arg) |
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sungen-data-factory
|
|
3
|
+
description: 'Use when create-data-test (or create-test) needs standardized TEST DATA — valid/boundary/invalid values for input fields with CHK-* traceability. Drives the Data Factory catalog + the 4-source method. Invoke after reading spec.md, when authoring the field-map or filling test-data.'
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## What this skill does
|
|
8
|
+
|
|
9
|
+
Turn a screen/flow's fields into **standardized test data** — not guessed values. You author a
|
|
10
|
+
**field-map**; the deterministic `sungen data gen` expands it from the Data Factory **catalog**
|
|
11
|
+
(field-type + security + technique library) into valid / boundary / invalid values, each carrying a
|
|
12
|
+
`CHK-*` id that traces to the spec. Your judgment goes into *reading the spec correctly* and
|
|
13
|
+
*answering blind spots* — never into inventing values the catalog or the QA should own.
|
|
14
|
+
|
|
15
|
+
Data Factory is **bundled with core** (runtime-free) — no `capability add` needed; the `sungen data …`
|
|
16
|
+
CLI is available out of the box.
|
|
17
|
+
|
|
18
|
+
## The 4-source method (phuong-phap-tao-test-data.md)
|
|
19
|
+
|
|
20
|
+
Never invent values from memory. Cross-reference four sources — each covers the previous one's gap:
|
|
21
|
+
|
|
22
|
+
1. **Spec validate rules** — required, format, min/max, business rules, and the **error/message code**
|
|
23
|
+
per rule. Defines the legal/illegal boundary. (Says the *law*, not every way to *break* it.)
|
|
24
|
+
2. **Field-type catalog** — the accumulated "how this type breaks" knowledge (email/password/phone/
|
|
25
|
+
number/…). This is the Data Factory `common/` — it fills the "spec doesn't list every break" gap.
|
|
26
|
+
3. **Design technique** — Boundary Value Analysis / Equivalence Partitioning / Decision Table / State
|
|
27
|
+
Transition — reduces the rule + candidates to a *minimal-but-sufficient* representative set.
|
|
28
|
+
4. **DB mapping** — unique constraints, cross-entity dependency, real backend limits → precondition
|
|
29
|
+
data, not just a single input value.
|
|
30
|
+
|
|
31
|
+
Then classify every value into **Valid / Boundary / Invalid** (each invalid tagged with its error
|
|
32
|
+
code) and give it a `CHK-*` id for traceability.
|
|
33
|
+
|
|
34
|
+
## The field-map — `qa/data-factory/<name>.fields.yaml`
|
|
35
|
+
|
|
36
|
+
Your main artifact. One entry per input field; the generator expands it:
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
screen: login # or flow: <name>
|
|
40
|
+
fields:
|
|
41
|
+
- name: email
|
|
42
|
+
type: email # a catalog field-type (see list below)
|
|
43
|
+
required: true
|
|
44
|
+
constraints: { maxLocal: 64, maxTotal: 254 } # the project's REAL limits from spec
|
|
45
|
+
errorMap: { M13: E1042, M02: E1001 } # catalog placeholder code → project's real code
|
|
46
|
+
- name: password
|
|
47
|
+
type: password
|
|
48
|
+
constraints: { minLength: 8, maxLength: 32 }
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Catalog field-types** (P1): `email` · `password` · `phone` · `string` · `number-integer` ·
|
|
52
|
+
`number-float` · `currency` · `postcode` · `katakana` (JP). Security payload banks auto-append to a
|
|
53
|
+
field's invalid group via the type's `uses_security` (`sqli`, `xss`, `idor`, `broken-auth`, `upload`,
|
|
54
|
+
`input-validation`).
|
|
55
|
+
|
|
56
|
+
Rules:
|
|
57
|
+
- Pick the closest `type`; put the project's real numbers in `constraints` (they override the
|
|
58
|
+
catalog defaults).
|
|
59
|
+
- Map every `errorRef` placeholder (`M13`, `M39`, …) to the spec's real code via `errorMap`. Leave
|
|
60
|
+
one unmapped **only** if the spec has none — the lint flags it as an open point to confirm.
|
|
61
|
+
- **Complex forms:** a **group** field takes nested **`fields:`** (recursive; no `type` on the group → renders a nested object like `address.city`). A field whose rule depends on another takes **`dependsOn: { field, value }`** — recorded as a `_depends_on` note (the generator doesn't resolve the runtime condition; weave it into the scenario/`@cases`).
|
|
62
|
+
- Set **`unique: true`** on a field that must be fresh every run (register email/username, anything
|
|
63
|
+
that would clash with a record a previous run left behind). Its valid value then carries a runtime
|
|
64
|
+
`{{$timestamp}}` (email → `user+{{$timestamp}}@…`) so each run is unique — no cross-run cache
|
|
65
|
+
(which would make tests order-dependent/flaky). Prefer this over caching prior-run values. For
|
|
66
|
+
reusing a value produced earlier **within the same run** (register → then log in with it), bind it
|
|
67
|
+
via the scenario (`@dataFactory` recipe / `testData.bind`), not a cross-run cache.
|
|
68
|
+
|
|
69
|
+
## No input fields → nothing to generate
|
|
70
|
+
|
|
71
|
+
Data Factory standardizes **field-level input data**. If a unit has no user-input fields or
|
|
72
|
+
parameters — a pure navigation, listing, or capture-compare flow — there is nothing to generate.
|
|
73
|
+
Report *"no input fields → nothing to generate"* and skip it; do **not** write an empty field-map.
|
|
74
|
+
Those scenarios are covered by the data create-test already produced, not by field-level test data.
|
|
75
|
+
(This is a correct outcome, not a gap — don't force a field-map to make the unit "processed".)
|
|
76
|
+
|
|
77
|
+
## Blind spots — ask, never fabricate
|
|
78
|
+
|
|
79
|
+
If a field's type is **not** in the catalog — a business enum, a cross-entity constraint, a
|
|
80
|
+
project-specific data model — do **not** guess. Ask the QA (via `AskUserQuestion`) for: (1) the valid
|
|
81
|
+
set, (2) invalid / non-existent values, (3) the error code when wrong, (4) dependencies on other
|
|
82
|
+
fields. Record it in `qa/data-factory/project/<entity>.yaml` so it becomes reusable project catalog.
|
|
83
|
+
This is the core discipline: *common is never enough; when blind, think and ask — don't invent.*
|
|
84
|
+
|
|
85
|
+
## Customizing the catalog — `.overwrite` (survives `sungen update`)
|
|
86
|
+
|
|
87
|
+
The shipped `common/` is a floor. Project rules go in `qa/data-factory/common.overwrite.yaml`,
|
|
88
|
+
deep-merged over the common (**later-wins**, keyed by `type` + data-point `id`): same `id` replaces,
|
|
89
|
+
new `id` appends, `constraints` merge key-by-key. `sungen update` refreshes `common/` but never
|
|
90
|
+
touches your overlay. If an update makes an overlay entry stale (references a bank/constraint that no
|
|
91
|
+
longer exists), the loader **warns you to prune it** — it never edits your file. Prefer a targeted
|
|
92
|
+
`errorMap`/`constraints` in the field-map for per-screen tweaks; use `.overwrite` only for rules that
|
|
93
|
+
apply project-wide.
|
|
94
|
+
|
|
95
|
+
## Bulk-import fixtures (CSV)
|
|
96
|
+
|
|
97
|
+
For CSV/Excel import or bulk-upload testing, `sungen data fixture --screen <name> [--rows N] [--invalid]`
|
|
98
|
+
turns the field-map into a fixture FILE under `qa/fixtures/`: `<name>.csv` (N valid rows; nested groups
|
|
99
|
+
→ dotted headers; `unique` fields row-indexed) that should import cleanly, and — with `--invalid` —
|
|
100
|
+
`<name>.invalid.csv` (one seeded-bad row per field + a `_violation` column) for row-level rejection
|
|
101
|
+
tests. Reference it from an `@api` upload (`files:`) or a UI file input.
|
|
102
|
+
|
|
103
|
+
## Verify before done — the phuong-phap §6 checklist
|
|
104
|
+
|
|
105
|
+
Run `sungen data lint` and clear it: every required field has **≥1 valid**, **boundary present**
|
|
106
|
+
(exact min/max, not only over-boundary) for bounded types, **≥1 invalid per rule** (not one generic
|
|
107
|
+
"wrong" value for many rules), no `UNRESOLVED` values, error codes mapped or explicitly left open.
|
|
108
|
+
Then `sungen data gen` writes the standardized `test-data/<name>.yaml`. Do not hand-edit the
|
|
109
|
+
generated values — change the field-map or `.overwrite` and re-run (determinism: same input → same
|
|
110
|
+
data).
|
|
111
|
+
|
|
112
|
+
## Weaving into scenarios
|
|
113
|
+
|
|
114
|
+
When a `.feature` exists, express invalid/boundary sets as data-driven `Scenario Outline` + `Examples`
|
|
115
|
+
(`@cases`) referencing the values — see `sungen-gherkin-syntax`. This keeps one logic + many values
|
|
116
|
+
instead of copy-pasted scenarios, and each row carries its `CHK-*` id for traceability.
|
|
@@ -78,9 +78,20 @@ needs any of these, it is a **finding for QA** — surface it in the run summary
|
|
|
78
78
|
| strict mode violation | Multiple elements match | Add `nth: 0`, `exact: true`, or more specific `name` |
|
|
79
79
|
| Timeout / not found | Element doesn't exist or name wrong | Re-snapshot → copy exact accessible name. Check iframe/dialog scope |
|
|
80
80
|
| Element is not an input | Wrong element type targeted | Change `type` or `value` to match actual element |
|
|
81
|
-
| not a select | Custom dropdown, not native `<select>` | Set `variant:
|
|
81
|
+
| not a select | Custom dropdown, not native `<select>` | Set the widget variant: `variant: radix-select`/`antd-select` (built-in recipes), a custom recipe name from `qa/app.yaml`, or project-wide `widgets.select` in `qa/app.yaml` |
|
|
82
82
|
| Frame not found | iframe selector wrong or doesn't exist | Fix `frame` value, verify iframe in snapshot |
|
|
83
83
|
|
|
84
|
+
### Compile diagnostics (`sungen generate` output) → fix BEFORE running
|
|
85
|
+
|
|
86
|
+
| Code | Meaning | Fix |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| `SG-W001` | Step matched no pattern — compiled to a no-op `// TODO` (the test runs weaker than it reads) | Rephrase to a documented pattern (`sungen-gherkin-syntax`) or tag `@manual` with a reason |
|
|
89
|
+
| `SG-W002` | A step's `{{value}}` was parsed but never used in the generated code | The step form isn't the one you think — check the pattern's exact syntax |
|
|
90
|
+
| `SG-W003` | `{ name }` filter defaulted from the Gherkin reference on a name-from-content role (toast/alert/status) | Add explicit `name:` or opt out with `name: null` |
|
|
91
|
+
| `SG-W005` | `qa/app.yaml` unknown key / unknown preset / unparseable | Fix the key, or `extends: native\|radix\|antd` |
|
|
92
|
+
| `SG-W006` | `qa/app.<env>.yaml` found — unsupported by design | Describe the app once in `qa/app.yaml`; env differences belong in test-data overlays |
|
|
93
|
+
| `SG-W007` | Browser-alert handler step written AFTER an action — listener registers too late, the dialog auto-dismisses silently | Use the compound form `click [X] button and accept [OK] alert` (preferred), or move the alert step before the trigger |
|
|
94
|
+
|
|
84
95
|
### Assertion errors → apply the Source-of-truth gate above FIRST
|
|
85
96
|
|
|
86
97
|
> The "Fix" column below applies **only when the expected value was wrong relative to `spec.md`** (a test defect). If the app's value contradicts the spec, the row is a **candidate bug → report it, do not edit the expected to match live**. Never weaken `toHaveText`→`toContainText` just to pass.
|
|
@@ -50,16 +50,41 @@ User expand | collapse [T] row
|
|
|
50
50
|
### Alert / Keyboard / Wait / Scroll
|
|
51
51
|
|
|
52
52
|
```
|
|
53
|
-
User click [
|
|
53
|
+
User click [T] button and accept [OK] alert # PREFERRED (v3.3): natural order,
|
|
54
|
+
User click [T] button and dismiss [Cancel] alert # compiler registers the listener first
|
|
55
|
+
User click [OK | Cancel] alert # two-step form: must come BEFORE the trigger
|
|
54
56
|
User fill [T] alert with {{v}}
|
|
55
57
|
User see [message text] alert
|
|
56
58
|
User press Escape key | Enter on [T] field
|
|
57
|
-
User wait for N seconds | [T]
|
|
59
|
+
User wait for N seconds | [T] page
|
|
60
|
+
User wait for [T] TYPE is visible | hidden | enabled | disabled # ANY reference (v3.3)
|
|
61
|
+
User wait for [T] TYPE with {{v}} # until it shows the value
|
|
62
|
+
User wait for [T] table to refresh # filter/search/pagination round-trip (v3.3)
|
|
58
63
|
User scroll to [T] section
|
|
59
64
|
User switch to [T] frame | [main] frame
|
|
60
65
|
```
|
|
61
66
|
|
|
62
|
-
>
|
|
67
|
+
> **Browser alerts (native `window.confirm/alert/prompt` only):** prefer the compound form —
|
|
68
|
+
> it reads in the user's real order and declares the trigger↔dialog causality in one step.
|
|
69
|
+
> The two-step form stays valid but the alert step must appear BEFORE the triggering action
|
|
70
|
+
> (a late `page.once('dialog')` lets the dialog auto-dismiss silently — compile warning
|
|
71
|
+
> `SG-W007`). Custom confirm MODALS (ARIA dialogs — most modern apps) are ordinary buttons:
|
|
72
|
+
> write normal click steps, no alert step at all.
|
|
73
|
+
> State-based waits (`is hidden`, `with {{v}}`, `table to refresh`) are legitimate; time-based
|
|
74
|
+
> `wait for N seconds` stays a last resort. `table to refresh` watches the app's loading
|
|
75
|
+
> indicator (`qa/app.yaml` `feedback.loading.indicator`, default `[aria-busy="true"]`).
|
|
76
|
+
|
|
77
|
+
### Positional table rows (v3.3)
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
User remember [Col] column in [T] table row {{n}} as {{var}} # read a cell by POSITION
|
|
81
|
+
User click [Act] button in [T] table row {{n}} # act on the SAME row
|
|
82
|
+
User click [Act] button in [T] table row 2 # literal row number
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Row numbers are 1-based data rows (header not counted; override via `qa/app.yaml`
|
|
86
|
+
`conventions.table`). Use positional rows when you must READ then ACT on the same row —
|
|
87
|
+
two content-filtered queries can hit different rows if the table re-renders in between.
|
|
63
88
|
|
|
64
89
|
### Assertions (8 patterns → determines Playwright assertion)
|
|
65
90
|
|
|
@@ -118,14 +143,24 @@ Scenario: ...
|
|
|
118
143
|
|
|
119
144
|
Path access on a bound result: `{{q.count}}`/`{{q.length}}`, `{{q.first.col}}`, `{{q.last.col}}`, `{{q[2].col}}`, `{{q.col}}` (= first row's col). `expect A is B` also supports `is at least` / `is at most` / `is not`. Tier-2 declarative (trivial inline, no catalog): `User see [<table>] row where [<col>] is {{v}} [has [<col2>] = "x"]`, `… no row where …`, `… count is {{n}}`. Full grammar + catalog/datasource/secret rules → **Advanced → Database** doc. Only emit DB steps when the project has a `database/` catalog / `datasources.yaml`.
|
|
120
145
|
|
|
146
|
+
### NoSQL engines (Cosmos, MongoDB, DynamoDB) — natively supported
|
|
147
|
+
|
|
148
|
+
Beyond the three SQL engines, the Data Driver has native NoSQL engines. Declarative steps (`User see [table] row where …`, `… no row …`, `… count is …`) work across ALL of them; only the raw `@query` catalog shape differs per engine:
|
|
149
|
+
|
|
150
|
+
- **`engine: cosmos`** (Azure Cosmos DB, NoSQL API) — SQL dialect. Author `sql:` catalog entries against the mandatory `c` alias (`SELECT * FROM c WHERE c.<field> = :p`); named params bind automatically. Config: `endpoint`+`key` (or a connection-string `url`), `database`, `container` (default when no `[table]`). Read-only by construction (the query API can't mutate).
|
|
151
|
+
- **`engine: mongodb`** — no SQL. Author a `find:` (`{collection, filter, projection?, sort?, limit?}`) or `pipeline:` (`{collection, stages: […]}`) catalog entry; `:param` placeholders bind at runtime. `$out`/`$merge`/`$where`/`$function`/`$accumulator`/`mapReduce` are refused. Config: a `mongodb://` `url`. Read-only = a server-side read role.
|
|
152
|
+
- **`engine: dynamodb`** — raw `@query` is PartiQL SELECT (`sql: SELECT … FROM "table" WHERE pk = :p`). Declarative steps route GetItem/Query only for partition-/sort-key filters via a `keySchema:` registry; a **non-key filter is refused** (use a raw PartiQL SELECT — never a full-table Scan). Config: `region` + `keySchema` (credentials from the AWS env chain). Read-only = IAM `PartiQLSelect`-only.
|
|
153
|
+
|
|
154
|
+
Full config + catalog examples → **Advanced → Database** doc.
|
|
155
|
+
|
|
121
156
|
### Unsupported DB engine — fallback (ask, don't improvise)
|
|
122
157
|
|
|
123
|
-
Direct SQL verification (`@query`, the Data Driver)
|
|
158
|
+
Direct SQL verification (`@query`, the Data Driver) covers these SQL engines: **`{postgres, sqlite, mysql}`** — plus the three native NoSQL engines above (Cosmos, MongoDB, DynamoDB). When a datasource's `engine` (or the DB the user describes) is **outside all of those** — e.g. Cassandra, Oracle, Neo4j, a proprietary store:
|
|
124
159
|
|
|
125
160
|
1. **Do not** attempt a direct connect. **Do not** improvise a verification method.
|
|
126
161
|
2. Present a fixed `AskUserQuestion` with exactly these 3 branches (always these, no others invented):
|
|
127
162
|
- **@api** — verify state via the project's internal API (route to the API driver).
|
|
128
|
-
- **@manual** — emit a `@manual` step + manual-check guidance (e.g.
|
|
163
|
+
- **@manual** — emit a `@manual` step + manual-check guidance (e.g. the vendor console / CLI — `cqlsh` for Cassandra, `cypher-shell` for Neo4j).
|
|
129
164
|
- **Proxy / read-replica SQL** — if the user can supply a SQL-compatible endpoint (including Azure Cosmos DB **for PostgreSQL** / Citus), treat it as `engine: postgres` (or `mysql`) and use the normal Data Driver path above.
|
|
130
165
|
3. **Recommend, don't decide.** Check the project for signals and put the best-fit branch **first**, tagged **"(Recommended)"** with a one-line reason:
|
|
131
166
|
- a project API driver / `qa/api/` catalog / `PEERCONNE_URL`-style internal API exists → **@api** first;
|
|
@@ -138,11 +173,11 @@ Direct SQL verification (`@query`, the Data Driver) only supports these engines:
|
|
|
138
173
|
Example:
|
|
139
174
|
```
|
|
140
175
|
AskUserQuestion:
|
|
141
|
-
question: "This datasource's engine (
|
|
176
|
+
question: "This datasource's engine (cassandra) isn't a supported Data Driver engine ({postgres, sqlite, mysql} + Cosmos/MongoDB/DynamoDB) — direct connect isn't supported. How should DB state be verified?"
|
|
142
177
|
options:
|
|
143
178
|
- "@api — verify via the project's internal API (Recommended: qa/api/ catalog found)"
|
|
144
|
-
- "@manual — emit a @manual step +
|
|
145
|
-
- "Proxy / read-replica SQL — supply a SQL-compatible endpoint
|
|
179
|
+
- "@manual — emit a @manual step + cqlsh check"
|
|
180
|
+
- "Proxy / read-replica SQL — supply a SQL-compatible endpoint → engine: postgres"
|
|
146
181
|
- "Other — describe your own verification approach"
|
|
147
182
|
```
|
|
148
183
|
|
|
@@ -83,6 +83,13 @@ When running Phase 0 for a **flow** (`qa/flows/<name>/`), check existing screen
|
|
|
83
83
|
- If any mismatch → write an explicit YAML entry using the real DOM name. Do not leave a mismatch to be caught at runtime.
|
|
84
84
|
- **Typical mismatch cases**: Gherkin uses English label (`[Submit]`) but app displays Vietnamese (`"Gửi"`); placeholder is descriptive (`"Nhập email của bạn"`) not a bare field name (`"Email"`); button text includes an icon glyph before/after the word.
|
|
85
85
|
7. **Substring ambiguity check**: for each `role` + `name` selector, check if any other element in the snapshot has a name that **contains** this name as a substring (e.g., `"Đăng ký"` vs `"Đăng ký bằng Google"`). If yes → add `exact: true` to prevent strict mode violation at runtime.
|
|
86
|
+
7b. **Widget & profile fingerprinting** (App Profile, v3.3): while the snapshot is open, identify the UI library and non-standard widget behavior, and record it so the compiler stops guessing:
|
|
87
|
+
- **Fingerprints** (check DOM classes/attributes): `.ant-select`/`.ant-picker` → antd · `[data-radix-*]` / shadcn portals → radix · `.MuiSelect-*`/`.MuiPickersDay` → MUI · `.select2-container` → select2 · plain `<select>` → native.
|
|
88
|
+
- **Project-wide** conclusion (the whole app uses one library) → write/update `qa/app.yaml`: `extends: radix|antd|native`. **Element-level** outlier (one widget differs from the rest) → set `variant:` on that selector entry only (e.g. `variant: native`, `variant: antd-select`).
|
|
89
|
+
- Custom widget nobody's preset covers (in-house datepicker etc.) → author a recipe in `qa/app.yaml` `recipes:` (closed primitives: click/type/press/waitFor/clickText/expectText/expectValue/forEach) and bind it per element with `variant: <recipe-name>`.
|
|
90
|
+
- Toast/alert named from content (no aria-label) → `feedback.toast: { role: status, named_by: content }` + `name: null` on the selector entries.
|
|
91
|
+
- Loading indicator for `wait for [T] table to refresh` → `feedback.loading.indicator` (skip if the app sets `aria-busy`).
|
|
92
|
+
- **Every qa/app.yaml edit MUST carry provenance** in `detection:`: who/when (`detected_by`) and the observed evidence per key (`evidence:`). A profile without evidence is a guess a human cannot review.
|
|
86
93
|
8. **Merge, don't overwrite**: preserve the page selector and any user-authored entries in `selectors.yaml`. Only add missing keys.
|
|
87
94
|
- **Type-collision check** (see `sungen-selector-keys` § Collision rule): before writing a base key, check whether it already exists with a *different* `type`. If so, give **both** entries a `--type` suffix instead of clobbering or sharing the base key — a base-key entry of the wrong type gets picked up by any step referencing that label. Watch `page` especially: a `[X] page` step reads `value` as a URL, so if `X` already holds a button (`value: 'button'`) the assertion compiles to `toHaveURL(/button/)`. Write `X--page` (URL from the live page / feature `Path:`) alongside `X--button`.
|
|
88
95
|
9. **Show summary + confirm**: list the keys that will be added, ask the user to approve, then write the file.
|
|
@@ -301,6 +308,18 @@ Only when `test-results/` screenshots are insufficient:
|
|
|
301
308
|
|
|
302
309
|
Never use `browser_evaluate` to inject or read cookies (misses httpOnly). For auth, use Phase 0.5 or `sungen makeauth`.
|
|
303
310
|
|
|
311
|
+
### Step 2b: Selector sanity probe — suspect the compiler/profile BEFORE the app
|
|
312
|
+
|
|
313
|
+
For every failing locator, while the live page is in the failing state, count matches before attributing the failure to server load, timing, or flakiness:
|
|
314
|
+
|
|
315
|
+
1. **Probe the exact generated locator** (copy it from the `.spec.ts`): count = 0 while the element is clearly on screen → the locator is wrong, not the app.
|
|
316
|
+
2. **Peel one constraint at a time and re-count**:
|
|
317
|
+
- drop the `{ name }` filter → now found? → invented/wrong accessible name → fix `name:` or set `name: null` (name-from-content roles; matches compile warning `SG-W003`)
|
|
318
|
+
- drop the dialog/parent scope → now found? → portal-rendered element → check `qa/app.yaml` `layers.portal_roles` / remove a wrong `scope: 'dialog'`
|
|
319
|
+
- drop `exact`/text filter → now found? → text mismatch (NBSP, trailing space, i18n) → fix the value or use `{{variable}}`
|
|
320
|
+
3. **Repair at the right altitude**: a per-element mistake → fix that `selectors.yaml` entry; a pattern repeated across elements (every toast, every dropdown) → fix `qa/app.yaml` once (with `detection.evidence`) so every screen inherits the fix. **Never rewrite the `.feature` to dodge a selector problem** — intent stays, identity/behavior get repaired.
|
|
321
|
+
4. Also read the generate output: `SG-W001/W002/W003` diagnostics fired at compile time usually name the broken step before you run anything.
|
|
322
|
+
|
|
304
323
|
### Step 3: Fix Broken Selectors
|
|
305
324
|
|
|
306
325
|
Selector priority (use first applicable):
|
|
@@ -367,6 +386,9 @@ Quick reference for the most frequent production failures:
|
|
|
367
386
|
| `No element found` on `[X] field` | Field has no placeholder, or placeholder ≠ X | Write explicit YAML: `type: label, value: "Actual label"` or `type: placeholder, value: "Actual placeholder"` |
|
|
368
387
|
| `No element found` on `[X] text` / `message` | Visible text differs from Gherkin label, or text is dynamic | Write explicit YAML or use `{{variable}}` for dynamic content |
|
|
369
388
|
| `strict mode violation` | Multiple elements match the same name/text | Add `exact: true` to YAML entry, or add `nth` |
|
|
389
|
+
| `strict mode violation` on `… with {{v}}` clicks (prefix-sharing options: "Văn phòng Hà Nội 1/2/…") | The `{{v}}` filter is a substring match by default | Add `exact: true` — since 3.3 it also anchors the `.filter({ hasText })` of action steps (`/^value$/`), not just role-name matching |
|
|
390
|
+
| Toast/alert/spinner assertion times out but `[role="status"]` exists on the live page (`SG-W003` at generate time) | Entry has no `name:` → compiler defaulted `{ name: '<Gherkin ref>' }`, but status/alert names derive from CONTENT and never equal the reference | Add `name: null` to the entry (match by role only). Do NOT switch to `type: locator` |
|
|
391
|
+
| Option/menu click inside a dialog times out, but the SAME locator works without dialog scope | Portal-rendered layer (Radix/MUI/antd) lives outside the dialog's DOM subtree | Since 3.3 the compiler page-roots portal roles (option/listbox/menu/menuitem/tooltip) automatically; for a listbox genuinely inside the dialog add `scope: 'dialog'` to the entry. Check `qa/app.yaml` `layers.portal_roles` (preset `native` disables the exemption) |
|
|
370
392
|
| `toBeVisible` timeout on dynamic content | Snapshot was taken while page was still loading | Wait for spinner/skeleton to clear before snapshotting; add `browser_wait_for` |
|
|
371
393
|
| All tests fail with page navigate error | Page selector URL wrong or baseURL mismatch | Re-check `playwright.config.ts` `baseURL` and page selector `value` path |
|
|
372
394
|
| Auth redirect on every test | `specs/.auth/<role>.json` missing or expired | Run Phase 0.5 to capture fresh session |
|