@sun-asterisk/sungen 3.1.2 → 3.2.0-beta.142
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 +4 -428
- package/dist/capabilities/builtins.d.ts +31 -0
- package/dist/capabilities/builtins.d.ts.map +1 -0
- package/dist/capabilities/builtins.js +84 -0
- package/dist/capabilities/builtins.js.map +1 -0
- package/dist/capabilities/context-router.d.ts +34 -0
- package/dist/capabilities/context-router.d.ts.map +1 -0
- package/dist/capabilities/context-router.js +49 -0
- package/dist/capabilities/context-router.js.map +1 -0
- package/dist/capabilities/context.d.ts +68 -0
- package/dist/capabilities/context.d.ts.map +1 -0
- package/dist/capabilities/context.js +17 -0
- package/dist/capabilities/context.js.map +1 -0
- package/dist/capabilities/discover.d.ts +2 -0
- package/dist/capabilities/discover.d.ts.map +1 -0
- package/dist/capabilities/discover.js +109 -0
- package/dist/capabilities/discover.js.map +1 -0
- package/dist/capabilities/registry.d.ts +92 -0
- package/dist/capabilities/registry.d.ts.map +1 -0
- package/dist/capabilities/registry.js +43 -0
- package/dist/capabilities/registry.js.map +1 -0
- package/dist/capabilities/sensor.d.ts +52 -0
- package/dist/capabilities/sensor.d.ts.map +1 -0
- package/dist/capabilities/sensor.js +3 -0
- package/dist/capabilities/sensor.js.map +1 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +17 -11
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/capability.d.ts.map +1 -1
- package/dist/cli/commands/capability.js +57 -5
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/context.d.ts +9 -0
- package/dist/cli/commands/context.d.ts.map +1 -0
- package/dist/cli/commands/context.js +91 -0
- package/dist/cli/commands/context.js.map +1 -0
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +42 -30
- 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 +35 -8
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/ledger.d.ts.map +1 -1
- package/dist/cli/commands/ledger.js +15 -5
- package/dist/cli/commands/ledger.js.map +1 -1
- package/dist/cli/commands/manifest.d.ts.map +1 -1
- package/dist/cli/commands/manifest.js +10 -9
- package/dist/cli/commands/manifest.js.map +1 -1
- package/dist/cli/commands/repair.d.ts +8 -0
- package/dist/cli/commands/repair.d.ts.map +1 -0
- package/dist/cli/commands/repair.js +97 -0
- package/dist/cli/commands/repair.js.map +1 -0
- package/dist/cli/commands/script-check.d.ts.map +1 -1
- package/dist/cli/commands/script-check.js +13 -9
- package/dist/cli/commands/script-check.js.map +1 -1
- package/dist/cli/commands/trace.d.ts.map +1 -1
- package/dist/cli/commands/trace.js +7 -4
- package/dist/cli/commands/trace.js.map +1 -1
- package/dist/cli/index.js +14 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts +1 -0
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +1 -0
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
- package/dist/generators/test-generator/code-generator.d.ts +18 -9
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +162 -115
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/patterns/index.d.ts +0 -10
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +10 -47
- package/dist/generators/test-generator/patterns/index.js.map +1 -1
- package/dist/generators/test-generator/template-engine.d.ts +1 -0
- package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
- package/dist/generators/test-generator/template-engine.js +1 -1
- package/dist/generators/test-generator/template-engine.js.map +1 -1
- package/dist/harness/annotation-overrides.d.ts +11 -0
- package/dist/harness/annotation-overrides.d.ts.map +1 -0
- package/dist/harness/annotation-overrides.js +38 -0
- package/dist/harness/annotation-overrides.js.map +1 -0
- package/dist/harness/audit.d.ts +9 -1
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +140 -10
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/capability-plan.d.ts +14 -0
- package/dist/harness/capability-plan.d.ts.map +1 -1
- package/dist/harness/capability-plan.js +63 -1
- package/dist/harness/capability-plan.js.map +1 -1
- package/dist/harness/catalog/drivers.yaml +35 -12
- package/dist/harness/data-driven-lint.d.ts.map +1 -1
- package/dist/harness/data-driven-lint.js +23 -0
- package/dist/harness/data-driven-lint.js.map +1 -1
- package/dist/harness/flow-check.d.ts +9 -0
- package/dist/harness/flow-check.d.ts.map +1 -1
- package/dist/harness/flow-check.js +13 -6
- package/dist/harness/flow-check.js.map +1 -1
- package/dist/harness/intent.d.ts +6 -0
- package/dist/harness/intent.d.ts.map +1 -1
- package/dist/harness/intent.js +20 -4
- package/dist/harness/intent.js.map +1 -1
- package/dist/harness/ledger.d.ts.map +1 -1
- package/dist/harness/ledger.js +3 -2
- package/dist/harness/ledger.js.map +1 -1
- package/dist/harness/manifest.d.ts.map +1 -1
- package/dist/harness/manifest.js +3 -2
- package/dist/harness/manifest.js.map +1 -1
- package/dist/harness/parse.d.ts +2 -0
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +16 -4
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/quality-gates.js +1 -1
- package/dist/harness/quality-gates.js.map +1 -1
- 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/harness/repair.d.ts +20 -0
- package/dist/harness/repair.d.ts.map +1 -0
- package/dist/harness/repair.js +111 -0
- package/dist/harness/repair.js.map +1 -0
- package/dist/harness/script-check.d.ts +3 -1
- package/dist/harness/script-check.d.ts.map +1 -1
- package/dist/harness/script-check.js +22 -8
- package/dist/harness/script-check.js.map +1 -1
- package/dist/harness/sensors.d.ts +40 -0
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +54 -2
- package/dist/harness/sensors.js.map +1 -1
- package/dist/harness/trace.d.ts.map +1 -1
- package/dist/harness/trace.js +4 -3
- package/dist/harness/trace.js.map +1 -1
- package/dist/harness/unit-paths.d.ts +3 -0
- package/dist/harness/unit-paths.d.ts.map +1 -0
- package/dist/harness/unit-paths.js +52 -0
- package/dist/harness/unit-paths.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
- package/dist/orchestrator/ai-rules-updater.js +2 -0
- package/dist/orchestrator/ai-rules-updater.js.map +1 -1
- package/dist/orchestrator/context-discovery.d.ts +12 -0
- package/dist/orchestrator/context-discovery.d.ts.map +1 -0
- package/dist/orchestrator/context-discovery.js +46 -0
- package/dist/orchestrator/context-discovery.js.map +1 -0
- package/dist/orchestrator/templates/ai-instructions/claude-agent-reviewer.md +7 -1
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +10 -5
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +18 -1
- package/dist/orchestrator/templates/ai-instructions/claude-skill-api-design.md +62 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +1 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-harness-audit.md +2 -1
- package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +19 -2
- package/dist/orchestrator/templates/ai-instructions/claude-skill-viewpoint.md +14 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +10 -5
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +11 -1
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-api-design.md +62 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +1 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-harness-audit.md +2 -1
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +19 -2
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint.md +14 -0
- package/dist/orchestrator/templates/specs-api.d.ts +55 -0
- package/dist/orchestrator/templates/specs-api.d.ts.map +1 -0
- package/dist/orchestrator/templates/specs-api.js +171 -0
- package/dist/orchestrator/templates/specs-api.js.map +1 -0
- package/dist/orchestrator/templates/specs-api.ts +154 -0
- package/dist/orchestrator/templates/specs-db.d.ts +3 -0
- package/dist/orchestrator/templates/specs-db.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-db.js +78 -1
- package/dist/orchestrator/templates/specs-db.js.map +1 -1
- package/dist/orchestrator/templates/specs-db.ts +78 -1
- package/dist/orchestrator/templates/specs-test-data.ts +2 -1
- package/package.json +7 -30
- package/src/capabilities/builtins.ts +85 -0
- package/src/capabilities/context-router.ts +66 -0
- package/src/capabilities/context.ts +65 -0
- package/src/capabilities/discover.ts +62 -0
- package/src/capabilities/registry.ts +113 -0
- package/src/capabilities/sensor.ts +47 -0
- package/src/cli/commands/audit.ts +15 -9
- package/src/cli/commands/capability.ts +53 -5
- package/src/cli/commands/context.ts +52 -0
- package/src/cli/commands/delivery.ts +40 -31
- package/src/cli/commands/generate.ts +37 -8
- package/src/cli/commands/ledger.ts +13 -5
- package/src/cli/commands/manifest.ts +9 -7
- package/src/cli/commands/repair.ts +57 -0
- package/src/cli/commands/script-check.ts +12 -8
- package/src/cli/commands/trace.ts +7 -4
- package/src/cli/index.ts +14 -1
- package/src/generators/test-generator/adapters/adapter-interface.ts +1 -1
- package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
- package/src/generators/test-generator/code-generator.ts +163 -111
- package/src/generators/test-generator/patterns/index.ts +9 -35
- package/src/generators/test-generator/template-engine.ts +2 -2
- package/src/harness/annotation-overrides.ts +27 -0
- package/src/harness/audit.ts +141 -12
- package/src/harness/capability-plan.ts +51 -1
- package/src/harness/catalog/drivers.yaml +35 -12
- package/src/harness/data-driven-lint.ts +20 -0
- package/src/harness/flow-check.ts +15 -6
- package/src/harness/intent.ts +25 -4
- package/src/harness/ledger.ts +3 -2
- package/src/harness/manifest.ts +3 -2
- package/src/harness/parse.ts +11 -2
- package/src/harness/quality-gates.ts +1 -1
- package/src/harness/query-catalog.ts +0 -0
- package/src/harness/repair.ts +75 -0
- package/src/harness/script-check.ts +25 -8
- package/src/harness/sensors.ts +71 -2
- package/src/harness/trace.ts +4 -3
- package/src/harness/unit-paths.ts +14 -0
- package/src/index.ts +32 -0
- package/src/orchestrator/ai-rules-updater.ts +2 -0
- package/src/orchestrator/context-discovery.ts +50 -0
- package/src/orchestrator/templates/ai-instructions/claude-agent-reviewer.md +7 -1
- package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +10 -5
- package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +18 -1
- package/src/orchestrator/templates/ai-instructions/claude-skill-api-design.md +62 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +1 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-harness-audit.md +2 -1
- package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +19 -2
- package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint.md +14 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +10 -5
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +11 -1
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-api-design.md +62 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +1 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-harness-audit.md +2 -1
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +19 -2
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint.md +14 -0
- package/src/orchestrator/templates/specs-api.ts +154 -0
- package/src/orchestrator/templates/specs-db.ts +78 -1
- package/src/orchestrator/templates/specs-test-data.ts +2 -1
- package/dist/generators/test-generator/patterns/assertion-patterns.d.ts +0 -7
- package/dist/generators/test-generator/patterns/assertion-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/assertion-patterns.js +0 -626
- package/dist/generators/test-generator/patterns/assertion-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/capture-patterns.d.ts +0 -21
- package/dist/generators/test-generator/patterns/capture-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/capture-patterns.js +0 -87
- package/dist/generators/test-generator/patterns/capture-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/database-patterns.d.ts +0 -6
- package/dist/generators/test-generator/patterns/database-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/database-patterns.js +0 -95
- package/dist/generators/test-generator/patterns/database-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/form-patterns.d.ts +0 -6
- package/dist/generators/test-generator/patterns/form-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/form-patterns.js +0 -160
- package/dist/generators/test-generator/patterns/form-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/interaction-patterns.d.ts +0 -6
- package/dist/generators/test-generator/patterns/interaction-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/interaction-patterns.js +0 -433
- package/dist/generators/test-generator/patterns/interaction-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/keyboard-patterns.d.ts +0 -7
- package/dist/generators/test-generator/patterns/keyboard-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/keyboard-patterns.js +0 -47
- package/dist/generators/test-generator/patterns/keyboard-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/navigation-patterns.d.ts +0 -6
- package/dist/generators/test-generator/patterns/navigation-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/navigation-patterns.js +0 -125
- package/dist/generators/test-generator/patterns/navigation-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/scope-patterns.d.ts +0 -7
- package/dist/generators/test-generator/patterns/scope-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/scope-patterns.js +0 -36
- package/dist/generators/test-generator/patterns/scope-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/scroll-patterns.d.ts +0 -7
- package/dist/generators/test-generator/patterns/scroll-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/scroll-patterns.js +0 -25
- package/dist/generators/test-generator/patterns/scroll-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/setup-patterns.d.ts +0 -6
- package/dist/generators/test-generator/patterns/setup-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/setup-patterns.js +0 -72
- package/dist/generators/test-generator/patterns/setup-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/table-patterns.d.ts +0 -19
- package/dist/generators/test-generator/patterns/table-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/table-patterns.js +0 -239
- package/dist/generators/test-generator/patterns/table-patterns.js.map +0 -1
- package/docs/orchestration-spec.md +0 -267
- package/src/generators/test-generator/patterns/assertion-patterns.ts +0 -691
- package/src/generators/test-generator/patterns/capture-patterns.ts +0 -97
- package/src/generators/test-generator/patterns/database-patterns.ts +0 -96
- package/src/generators/test-generator/patterns/form-patterns.ts +0 -167
- package/src/generators/test-generator/patterns/interaction-patterns.ts +0 -465
- package/src/generators/test-generator/patterns/keyboard-patterns.ts +0 -51
- package/src/generators/test-generator/patterns/navigation-patterns.ts +0 -140
- package/src/generators/test-generator/patterns/scope-patterns.ts +0 -40
- package/src/generators/test-generator/patterns/scroll-patterns.ts +0 -27
- package/src/generators/test-generator/patterns/setup-patterns.ts +0 -76
- package/src/generators/test-generator/patterns/table-patterns.ts +0 -279
|
@@ -4,8 +4,9 @@ import { ParsedFeature, ParsedScenario, ParsedStep } from '../gherkin-parser';
|
|
|
4
4
|
import { StepMapper } from './step-mapper';
|
|
5
5
|
import { TestGeneratorAdapter, adapterRegistry } from './adapters';
|
|
6
6
|
import { transformToRuntimeData } from './utils/runtime-data-transformer';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { capabilityRegistry } from '../../capabilities/registry';
|
|
8
|
+
import { discoverAndRegisterCapabilities } from '../../capabilities/discover';
|
|
9
|
+
import { readCapabilities } from '../../harness/capability';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Filter base scenario steps for @extend: only keep Given→When steps.
|
|
@@ -88,6 +89,40 @@ function extractPassThroughTags(scenarioTags: string[], featureTags: string[]):
|
|
|
88
89
|
return unique.map(t => `'${t}'`).join(', ');
|
|
89
90
|
}
|
|
90
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Derive a feature's unit from its source path: the catalog-resolution id (relative to qa/), the
|
|
94
|
+
* output subdir, and whether it is a UI flow. Recognizes the api-first project model:
|
|
95
|
+
* qa/screens/<screen>/features/… → id `<screen>` (subdir `<screen>`)
|
|
96
|
+
* qa/flows/<flow>/features/… → id `flows/<flow>` (subdir `flows/<flow>`, isFlow)
|
|
97
|
+
* qa/api/<area>/features/… → id `api/<area>` (subdir `api/<area>`)
|
|
98
|
+
* qa/api/flows/<flow>/features/… → id `api/flows/<flow>` (subdir `api/flows/<flow>`)
|
|
99
|
+
* api-first units are NOT UI flows (no cross-screen namespacing) — they resolve their own api/db catalogs.
|
|
100
|
+
*/
|
|
101
|
+
function deriveUnitFromFeaturePath(sourceFile: string): { unitId: string; outputSubdir: string; isFlow: boolean } {
|
|
102
|
+
const parts = path.dirname(sourceFile).split(path.sep);
|
|
103
|
+
const qa = parts.lastIndexOf('qa');
|
|
104
|
+
if (qa >= 0 && parts[qa + 1] === 'api') {
|
|
105
|
+
if (parts[qa + 2] === 'flows' && parts[qa + 3]) {
|
|
106
|
+
const id = `api/flows/${parts[qa + 3]}`;
|
|
107
|
+
return { unitId: id, outputSubdir: id, isFlow: false };
|
|
108
|
+
}
|
|
109
|
+
if (parts[qa + 2]) {
|
|
110
|
+
const id = `api/${parts[qa + 2]}`;
|
|
111
|
+
return { unitId: id, outputSubdir: id, isFlow: false };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const flowsIndex = parts.indexOf('flows');
|
|
115
|
+
const screensIndex = parts.indexOf('screens');
|
|
116
|
+
if (flowsIndex >= 0 && flowsIndex < parts.length - 1) {
|
|
117
|
+
const f = parts[flowsIndex + 1];
|
|
118
|
+
return { unitId: `flows/${f}`, outputSubdir: `flows/${f}`, isFlow: true };
|
|
119
|
+
}
|
|
120
|
+
if (screensIndex >= 0 && screensIndex < parts.length - 1) {
|
|
121
|
+
return { unitId: parts[screensIndex + 1], outputSubdir: parts[screensIndex + 1], isFlow: false };
|
|
122
|
+
}
|
|
123
|
+
return { unitId: '', outputSubdir: '', isFlow: false };
|
|
124
|
+
}
|
|
125
|
+
|
|
91
126
|
/**
|
|
92
127
|
* Check for @screenshot:on-failure tag
|
|
93
128
|
*/
|
|
@@ -141,6 +176,19 @@ function isManual(tags: string[]): boolean {
|
|
|
141
176
|
return tags.some(tag => tag === '@manual');
|
|
142
177
|
}
|
|
143
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Capabilities a scenario declares via `@requires:<cap>` (TQ-11) — automation-ready work that
|
|
181
|
+
* needs an opt-in driver (e.g. `@requires:db`, `@requires:api`). When the cap is enabled the
|
|
182
|
+
* scenario compiles to a real test; when absent it compiles to a clean `test.skip` stub (no
|
|
183
|
+
* driver imports) so the run never breaks and the case is visible as "pending capability".
|
|
184
|
+
*/
|
|
185
|
+
function requiresCaps(tags: string[]): string[] {
|
|
186
|
+
return tags
|
|
187
|
+
.filter(t => /^@requires:/i.test(t))
|
|
188
|
+
.map(t => t.slice('@requires:'.length).trim().toLowerCase())
|
|
189
|
+
.filter(Boolean);
|
|
190
|
+
}
|
|
191
|
+
|
|
144
192
|
/**
|
|
145
193
|
* Check for multiple auth tags and log warning
|
|
146
194
|
*/
|
|
@@ -210,29 +258,16 @@ export class CodeGenerator {
|
|
|
210
258
|
fileName = this.featureNameToFileName(feature.name);
|
|
211
259
|
}
|
|
212
260
|
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
// qa/flows/{flowName}/features/{featureName}.feature -> flows/flowName
|
|
216
|
-
let outputSubdir = '';
|
|
217
|
-
if (feature.sourceFile) {
|
|
218
|
-
const sourceDir = path.dirname(feature.sourceFile);
|
|
219
|
-
const parts = sourceDir.split(path.sep);
|
|
220
|
-
const flowsIndex = parts.indexOf('flows');
|
|
221
|
-
const screensIndex = parts.indexOf('screens');
|
|
222
|
-
if (flowsIndex >= 0 && flowsIndex < parts.length - 2) {
|
|
223
|
-
outputSubdir = path.join('flows', parts[flowsIndex + 1]);
|
|
224
|
-
} else if (screensIndex >= 0 && screensIndex < parts.length - 2) {
|
|
225
|
-
outputSubdir = parts[screensIndex + 1];
|
|
226
|
-
}
|
|
227
|
-
}
|
|
261
|
+
// Output subdirectory from the source path (screens / flows / api-first areas+flows).
|
|
262
|
+
const outputSubdir = feature.sourceFile ? deriveUnitFromFeaturePath(feature.sourceFile).outputSubdir : '';
|
|
228
263
|
|
|
229
264
|
// Build output path with subdirectory
|
|
230
265
|
const filePath = outputSubdir
|
|
231
266
|
? path.join(outputDir, outputSubdir, fileName)
|
|
232
267
|
: path.join(outputDir, fileName);
|
|
233
268
|
|
|
234
|
-
// Compute relative path from output file back to specs/generated/
|
|
235
|
-
const depth = outputSubdir ? outputSubdir.split(
|
|
269
|
+
// Compute relative path from output file back to specs/generated/ (subdir uses '/').
|
|
270
|
+
const depth = outputSubdir ? outputSubdir.split('/').length : 0;
|
|
236
271
|
const basePath = depth > 0 ? Array(depth).fill('..').join('/') : '..';
|
|
237
272
|
|
|
238
273
|
// Serial + @cleanup tags → need cleanupPage import from base
|
|
@@ -240,11 +275,17 @@ export class CodeGenerator {
|
|
|
240
275
|
const hasCleanupTags = (feature.tags || []).some(t => t.startsWith('@cleanup:'));
|
|
241
276
|
const needsCleanupImport = !isParallelFeature && hasCleanupTags;
|
|
242
277
|
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
278
|
+
// Active capabilities for this feature (registry-driven): the default UI + any whose annotation
|
|
279
|
+
// tags appear (@query) or whose detectsStep matches (declarative DB steps). Each active
|
|
280
|
+
// capability emits its declared runtime helpers (db → specs/db.ts).
|
|
281
|
+
const activeCapabilityIds = this.activeCapabilityIds(feature);
|
|
282
|
+
const needsDb = activeCapabilityIds.includes('db');
|
|
283
|
+
const needsApi = activeCapabilityIds.includes('api');
|
|
284
|
+
for (const id of activeCapabilityIds) {
|
|
285
|
+
for (const h of capabilityRegistry.get(id)?.runtimeHelpers ?? []) this.syncGeneratedHelper(outputDir, h.file, h.template);
|
|
286
|
+
}
|
|
246
287
|
|
|
247
|
-
const imports = this.adapter.renderImports({ runtimeData: this.options.runtimeData, basePath, needsCleanupImport, needsDb });
|
|
288
|
+
const imports = this.adapter.renderImports({ runtimeData: this.options.runtimeData, basePath, needsCleanupImport, needsDb, needsApi });
|
|
248
289
|
|
|
249
290
|
// Generate test code (async now to support AI mapping)
|
|
250
291
|
const testCode = await this.generateTestCode(feature);
|
|
@@ -300,64 +341,52 @@ export class CodeGenerator {
|
|
|
300
341
|
/**
|
|
301
342
|
* Ensure specs/base.ts exists in the output directory
|
|
302
343
|
*/
|
|
303
|
-
/**
|
|
304
|
-
|
|
344
|
+
/**
|
|
345
|
+
* Capabilities active for a feature (registry-driven): the default (UI) capability, plus any
|
|
346
|
+
* whose annotation tags appear on a scenario (e.g. `@query` → db) or whose `detectsStep`
|
|
347
|
+
* matches a step (db's declarative `User see [table] row where …`). Drives runtime-helper
|
|
348
|
+
* emission + the `db` import — replaces the hardcoded `featureUsesDb` check (R4).
|
|
349
|
+
*/
|
|
350
|
+
private activeCapabilityIds(feature: ParsedFeature): string[] {
|
|
351
|
+
discoverAndRegisterCapabilities();
|
|
305
352
|
const steps: ParsedStep[] = [];
|
|
306
353
|
if (feature.background?.steps) steps.push(...feature.background.steps);
|
|
307
354
|
for (const sc of feature.scenarios || []) if (sc.steps) steps.push(...sc.steps);
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
355
|
+
const scenarioTags = (feature.scenarios || []).flatMap((sc) => sc.tags || []);
|
|
356
|
+
const ids = new Set<string>();
|
|
357
|
+
const def = capabilityRegistry.defaultCapabilityId();
|
|
358
|
+
if (def) ids.add(def);
|
|
359
|
+
for (const cap of capabilityRegistry.all()) {
|
|
360
|
+
const annoMatch = (cap.annotations ?? []).some((a) => scenarioTags.some((t) => t === a || t.startsWith(a + ':')));
|
|
361
|
+
const stepMatch = cap.detectsStep ? steps.some((s) => s && typeof s.text === 'string' && cap.detectsStep!(s.text)) : false;
|
|
362
|
+
if (annoMatch || stepMatch) ids.add(cap.id);
|
|
363
|
+
}
|
|
364
|
+
return [...ids];
|
|
311
365
|
}
|
|
312
366
|
|
|
313
367
|
/**
|
|
314
|
-
*
|
|
315
|
-
* Each
|
|
368
|
+
* Precondition steps a scenario's capabilities inject (e.g. db `@query:<name>` → bind {{name}}).
|
|
369
|
+
* Each capability owns its annotation codegen via the SPI (`preconditionCodegen`); the compiler
|
|
370
|
+
* just composes + indents the returned statements (R4).
|
|
316
371
|
*/
|
|
317
|
-
private
|
|
318
|
-
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
if (!
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
`testData.bind(${JSON.stringify(name)}, await db.fetchQuery(${label}, ${JSON.stringify(sql)}, [${paramExprs.join(', ')}], ${ds}));`,
|
|
336
|
-
4,
|
|
337
|
-
),
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
return out;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/** Parse `@query:name(a={{x}},b="lit",c=3)` overrides → { a: "testData.get('x')", … } JS exprs. */
|
|
344
|
-
private parseQueryOverrides(raw?: string): Record<string, string> {
|
|
345
|
-
const out: Record<string, string> = {};
|
|
346
|
-
if (!raw) return out;
|
|
347
|
-
for (const part of raw.split(',')) {
|
|
348
|
-
const eq = part.indexOf('=');
|
|
349
|
-
if (eq < 0) continue;
|
|
350
|
-
const key = part.slice(0, eq).trim();
|
|
351
|
-
const val = part.slice(eq + 1).trim();
|
|
352
|
-
if (!key) continue;
|
|
353
|
-
const v = val.match(/^\{\{\s*([^}]+?)\s*\}\}$/);
|
|
354
|
-
const q = val.match(/^["'](.*)["']$/);
|
|
355
|
-
if (v) out[key] = `testData.get(${JSON.stringify(v[1])})`;
|
|
356
|
-
else if (q) out[key] = JSON.stringify(q[1]);
|
|
357
|
-
else if (/^-?\d+(?:\.\d+)?$/.test(val)) out[key] = val;
|
|
358
|
-
else out[key] = JSON.stringify(val);
|
|
359
|
-
}
|
|
360
|
-
return out;
|
|
372
|
+
private capabilityPreconditions(scenario: ParsedScenario): Array<{ comment?: string; code: string; boundVars?: string[] }> {
|
|
373
|
+
discoverAndRegisterCapabilities();
|
|
374
|
+
const tags = scenario.tags || [];
|
|
375
|
+
const out: Array<{ comment?: string; code: string; boundVars?: string[] }> = [];
|
|
376
|
+
for (const cap of capabilityRegistry.all()) {
|
|
377
|
+
if (!cap.preconditionCodegen) continue;
|
|
378
|
+
out.push(...cap.preconditionCodegen({ tags, screenName: this.queryScreenName, cwd: process.cwd() }));
|
|
379
|
+
}
|
|
380
|
+
// Order by each precondition's annotation position on the scenario (not capability-registry order),
|
|
381
|
+
// so a cross-capability sequence runs as authored — e.g. `@api:pay @concurrent:2 @query:charge_count`
|
|
382
|
+
// mutates THEN reads the DB (the idempotency cross-check). Stable: ties keep their original order.
|
|
383
|
+
const pos = (p: { boundVars?: string[] }): number => {
|
|
384
|
+
const name = p.boundVars?.[0];
|
|
385
|
+
if (!name) return tags.length;
|
|
386
|
+
const i = tags.findIndex((t) => new RegExp(`^@\\w+:${name}\\b`).test(t));
|
|
387
|
+
return i < 0 ? tags.length : i;
|
|
388
|
+
};
|
|
389
|
+
return out.map((p, i) => ({ p, i })).sort((a, b) => pos(a.p) - pos(b.p) || a.i - b.i).map((x) => x.p);
|
|
361
390
|
}
|
|
362
391
|
|
|
363
392
|
/**
|
|
@@ -378,11 +407,6 @@ export class CodeGenerator {
|
|
|
378
407
|
console.log(`✓ ${exists ? 'Updated' : 'Created'}: specs/${fileName}`);
|
|
379
408
|
}
|
|
380
409
|
|
|
381
|
-
/** Ensure specs/db.ts is present and current (Data Driver runtime helper). */
|
|
382
|
-
ensureDbFile(outputDir: string): void {
|
|
383
|
-
this.syncGeneratedHelper(outputDir, 'db.ts', 'specs-db.ts');
|
|
384
|
-
}
|
|
385
|
-
|
|
386
410
|
ensureBaseFile(outputDir: string): void {
|
|
387
411
|
this.syncGeneratedHelper(outputDir, 'base.ts', 'specs-base.ts');
|
|
388
412
|
// base.ts depends on locale-fixture.ts — keep them paired.
|
|
@@ -403,27 +427,23 @@ export class CodeGenerator {
|
|
|
403
427
|
featureName = this.featureNameToFileName(feature.name).replace('.spec.ts', '');
|
|
404
428
|
}
|
|
405
429
|
|
|
406
|
-
// Derive
|
|
407
|
-
// qa/
|
|
408
|
-
//
|
|
430
|
+
// Derive the unit from the source path when not explicitly set. `unitId` is the catalog/test-data
|
|
431
|
+
// resolution id relative to qa/ (`<screen>` · `flows/<flow>` · `api/<area>` · `api/flows/<flow>`);
|
|
432
|
+
// `effectiveScreenName` is the bare name for UI screen-context (selector/data namespacing).
|
|
409
433
|
let effectiveScreenName = this.screenName;
|
|
410
434
|
let isFlowFeature = !!this.options.flowMode;
|
|
435
|
+
let unitId = isFlowFeature ? `flows/${effectiveScreenName || ''}` : (effectiveScreenName || '');
|
|
411
436
|
if (!this.screenName && feature.sourceFile) {
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
effectiveScreenName
|
|
418
|
-
isFlowFeature = true;
|
|
419
|
-
} else if (screensIndex >= 0 && screensIndex < parts.length - 2) {
|
|
420
|
-
effectiveScreenName = parts[screensIndex + 1];
|
|
421
|
-
isFlowFeature = false;
|
|
437
|
+
const u = deriveUnitFromFeaturePath(feature.sourceFile);
|
|
438
|
+
if (u.unitId) {
|
|
439
|
+
unitId = u.unitId;
|
|
440
|
+
isFlowFeature = u.isFlow;
|
|
441
|
+
effectiveScreenName = u.unitId.split('/').pop() || u.unitId; // bare name (no api/ or flows/ prefix)
|
|
442
|
+
this.stepMapper.setScreenContext(effectiveScreenName);
|
|
422
443
|
}
|
|
423
|
-
this.stepMapper.setScreenContext(effectiveScreenName);
|
|
424
444
|
}
|
|
425
|
-
// Catalog-resolution
|
|
426
|
-
this.queryScreenName =
|
|
445
|
+
// Catalog + test-data resolution id (flows → `flows/<flow>`, api-first → `api/<area>` / `api/flows/<flow>`).
|
|
446
|
+
this.queryScreenName = unitId;
|
|
427
447
|
|
|
428
448
|
// Reset flow mode per feature to prevent state leak in --all mode
|
|
429
449
|
this.stepMapper.setFlowMode(isFlowFeature);
|
|
@@ -487,6 +507,10 @@ export class CodeGenerator {
|
|
|
487
507
|
// Generate all scenarios with feature tags for inheritance
|
|
488
508
|
// Skip scenarios tagged with @manual
|
|
489
509
|
// Track auth role per scenario for grouping
|
|
510
|
+
// TQ-11 — enabled capabilities (qa/capabilities.yaml): a @requires:<cap> scenario whose cap is
|
|
511
|
+
// absent compiles to a skip stub instead of a real test (read once per feature).
|
|
512
|
+
const enabledCaps = new Set<string>(readCapabilities(process.cwd()).enabled.map(d => d.toLowerCase()));
|
|
513
|
+
|
|
490
514
|
const renderedScenarios: Array<{ code: string; authRole?: string }> = [];
|
|
491
515
|
for (const scenario of feature.scenarios) {
|
|
492
516
|
if (isManual(scenario.tags)) {
|
|
@@ -501,6 +525,16 @@ export class CodeGenerator {
|
|
|
501
525
|
continue;
|
|
502
526
|
}
|
|
503
527
|
|
|
528
|
+
// TQ-11 — @requires:<cap> with the cap NOT enabled → emit a clean skip stub (no driver
|
|
529
|
+
// imports, so the spec still loads), visible as skipped-with-reason. With the cap enabled it
|
|
530
|
+
// falls through and compiles as a real test.
|
|
531
|
+
const reqAbsent = requiresCaps(scenario.tags).filter(c => !enabledCaps.has(c));
|
|
532
|
+
if (reqAbsent.length) {
|
|
533
|
+
if (this.options.verbose) console.log(` ⏸ Pending capability (${reqAbsent.join(', ')}): ${scenario.name}`);
|
|
534
|
+
renderedScenarios.push({ code: this.generateRequiresSkipStub(scenario.name, reqAbsent), authRole: undefined });
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
|
|
504
538
|
// Resolve auth tags for @extend scenarios (same logic as generateScenario)
|
|
505
539
|
let authFeatureTags = feature.tags || [];
|
|
506
540
|
if (scenario.extendsName) {
|
|
@@ -574,7 +608,7 @@ export class CodeGenerator {
|
|
|
574
608
|
cleanupConfig,
|
|
575
609
|
screenshotOnFailure,
|
|
576
610
|
runtimeData: this.options.runtimeData,
|
|
577
|
-
screenName:
|
|
611
|
+
screenName: this.queryScreenName, // catalog/test-data id (screen · flows/<flow> · api/<area>)
|
|
578
612
|
featureFileName: featureName,
|
|
579
613
|
isParallel,
|
|
580
614
|
flowMode: isFlowFeature,
|
|
@@ -636,6 +670,21 @@ export class CodeGenerator {
|
|
|
636
670
|
return renderMap[hookType]();
|
|
637
671
|
}
|
|
638
672
|
|
|
673
|
+
/**
|
|
674
|
+
* TQ-11 — a `@requires:<cap>` scenario whose capability is NOT enabled. Emits a real `test()`
|
|
675
|
+
* that skips itself with an actionable reason, and references NO driver runtime (so the spec
|
|
676
|
+
* loads even though the driver isn't installed). Once the cap is added + regenerated, the
|
|
677
|
+
* scenario compiles to its full automated body instead.
|
|
678
|
+
*/
|
|
679
|
+
private generateRequiresSkipStub(name: string, caps: string[]): string {
|
|
680
|
+
const reason = `requires ${caps.join(' + ')} — run \`sungen capability add ${caps.join(' ')}\` to automate this`;
|
|
681
|
+
return [
|
|
682
|
+
` test(${JSON.stringify(name)}, async () => {`,
|
|
683
|
+
` test.skip(true, ${JSON.stringify(reason)});`,
|
|
684
|
+
` });`,
|
|
685
|
+
].join('\n');
|
|
686
|
+
}
|
|
687
|
+
|
|
639
688
|
private async generateScenario(
|
|
640
689
|
scenario: ParsedScenario,
|
|
641
690
|
hasBackground: boolean,
|
|
@@ -683,18 +732,16 @@ export class CodeGenerator {
|
|
|
683
732
|
for (const r of refs) this.stepMapper.registerCaptured(r);
|
|
684
733
|
}
|
|
685
734
|
|
|
686
|
-
//
|
|
687
|
-
//
|
|
688
|
-
// instead of a compile-time YAML lookup that would fail.
|
|
689
|
-
const
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
.map((m) => m[1]);
|
|
693
|
-
if (queryNames.length) {
|
|
735
|
+
// Capability preconditions (db `@query:<name>` → bind {{name}}) are owned by the capability via
|
|
736
|
+
// the SPI. Their bound `{{name.*}}` vars exist only at runtime → register them as captured so
|
|
737
|
+
// they resolve to a runtime get() instead of a compile-time YAML lookup that would fail.
|
|
738
|
+
const preconditions = this.capabilityPreconditions(scenario);
|
|
739
|
+
const boundVars = preconditions.flatMap((p) => p.boundVars || []);
|
|
740
|
+
if (boundVars.length) {
|
|
694
741
|
for (const st of stepsToMap) {
|
|
695
742
|
for (const mt of (st.text || '').matchAll(/\{\{\s*([^}]+?)\s*\}\}/g)) {
|
|
696
743
|
const head = mt[1].split(/[.[]/)[0];
|
|
697
|
-
if (
|
|
744
|
+
if (boundVars.includes(head)) this.stepMapper.registerCaptured(mt[1]);
|
|
698
745
|
}
|
|
699
746
|
}
|
|
700
747
|
}
|
|
@@ -728,11 +775,11 @@ export class CodeGenerator {
|
|
|
728
775
|
}
|
|
729
776
|
}
|
|
730
777
|
|
|
731
|
-
//
|
|
732
|
-
//
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
778
|
+
// Capability preconditions (db `@query:<name>` / api `@api:<name>` → bind {{name}}; computed
|
|
779
|
+
// above) run BEFORE the scenario's own steps — prepend them, indenting the capability statements.
|
|
780
|
+
if (preconditions.length) {
|
|
781
|
+
steps.unshift(...preconditions.map((p) => ({ comment: p.comment, code: this.indentCode(p.code, 4) })));
|
|
782
|
+
}
|
|
736
783
|
|
|
737
784
|
// Extract pass-through tags (feature + scenario, excluding functional tags)
|
|
738
785
|
const tags = extractPassThroughTags(scenario.tags, featureTags);
|
|
@@ -752,7 +799,12 @@ export class CodeGenerator {
|
|
|
752
799
|
// global `testData` transform that runs next on the rest of the file leaves it alone.
|
|
753
800
|
// The loop header's `testData.cases()/withRow()` are literal code (no markers) → untouched.
|
|
754
801
|
if (casesDataset && this.options.runtimeData) {
|
|
755
|
-
|
|
802
|
+
// AP-3: capability preconditions (`@api`/`@query`) and value assertions (`expect {{x}} is {{y}}`)
|
|
803
|
+
// emit LITERAL `testData.get/bind/set(…)` (not markers), so the marker transform above misses
|
|
804
|
+
// them. Rewrite those to the per-row `rowData` view — each row then fires its own `@api` call
|
|
805
|
+
// with that row's input and asserts that row's expected status/body (the success/failure matrix).
|
|
806
|
+
// `testData.cases()/withRow()` (the global loader, no `.get/.bind/.set`) is intentionally left alone.
|
|
807
|
+
return transformToRuntimeData(rendered, 'rowData').replace(/\btestData\.(get|bind|set)\(/g, 'rowData.$1(');
|
|
756
808
|
}
|
|
757
809
|
return rendered;
|
|
758
810
|
}
|
|
@@ -1,18 +1,8 @@
|
|
|
1
1
|
import { ParsedStep } from '../../gherkin-parser';
|
|
2
2
|
import { MappedStep } from '../step-mapper';
|
|
3
3
|
import { StepPattern, PatternContext } from './types';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { interactionPatterns } from './interaction-patterns';
|
|
7
|
-
import { assertionPatterns } from './assertion-patterns';
|
|
8
|
-
import { setupPatterns } from './setup-patterns';
|
|
9
|
-
import { keyboardPatterns } from './keyboard-patterns';
|
|
10
|
-
import { scrollPatterns } from './scroll-patterns';
|
|
11
|
-
import { scopePatterns } from './scope-patterns';
|
|
12
|
-
import { tablePatterns } from './table-patterns';
|
|
13
|
-
import { capturePatterns } from './capture-patterns';
|
|
14
|
-
import { databasePatterns } from './database-patterns';
|
|
15
|
-
import { expectPatterns } from './expect-patterns';
|
|
4
|
+
import { capabilityRegistry } from '../../../capabilities/registry';
|
|
5
|
+
import { discoverAndRegisterCapabilities } from '../../../capabilities/discover';
|
|
16
6
|
|
|
17
7
|
/**
|
|
18
8
|
* Pattern Registry - manages all step patterns
|
|
@@ -28,18 +18,11 @@ export class PatternRegistry {
|
|
|
28
18
|
* Register default patterns from all pattern modules
|
|
29
19
|
*/
|
|
30
20
|
private registerDefaultPatterns(): void {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
this.patterns.push(...
|
|
36
|
-
this.patterns.push(...keyboardPatterns);
|
|
37
|
-
this.patterns.push(...scrollPatterns);
|
|
38
|
-
this.patterns.push(...scopePatterns);
|
|
39
|
-
this.patterns.push(...tablePatterns);
|
|
40
|
-
this.patterns.push(...capturePatterns);
|
|
41
|
-
this.patterns.push(...databasePatterns);
|
|
42
|
-
this.patterns.push(...expectPatterns);
|
|
21
|
+
// Patterns are composed from the capability registry (Capability SPI, R1) instead of a
|
|
22
|
+
// hardcoded push list. Built-ins (ui · db · core) register the same set as before, so the
|
|
23
|
+
// composed list + priority sort is behaviour-identical (golden snapshots are the contract).
|
|
24
|
+
discoverAndRegisterCapabilities();
|
|
25
|
+
this.patterns.push(...capabilityRegistry.patterns());
|
|
43
26
|
|
|
44
27
|
// Sort by priority (higher first)
|
|
45
28
|
this.patterns.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
|
@@ -159,15 +142,6 @@ export class PatternRegistry {
|
|
|
159
142
|
}
|
|
160
143
|
}
|
|
161
144
|
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
export { navigationPatterns } from './navigation-patterns';
|
|
165
|
-
export { formPatterns } from './form-patterns';
|
|
166
|
-
export { interactionPatterns } from './interaction-patterns';
|
|
167
|
-
export { assertionPatterns } from './assertion-patterns';
|
|
168
|
-
export { keyboardPatterns } from './keyboard-patterns';
|
|
169
|
-
export { scrollPatterns } from './scroll-patterns';
|
|
170
|
-
export { scopePatterns } from './scope-patterns';
|
|
171
|
-
export { tablePatterns } from './table-patterns';
|
|
172
|
-
export { databasePatterns, isDbStep } from './database-patterns';
|
|
145
|
+
// The UI step patterns now live in @sungen/driver-ui (R5.4) and the DB patterns in @sungen/driver-db
|
|
146
|
+
// (R5.5); both are contributed via the capability registry, not re-exported here.
|
|
173
147
|
export * from './types';
|
|
@@ -229,8 +229,8 @@ export class TemplateEngine {
|
|
|
229
229
|
this.baseContext = {};
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
renderImports(options?: { runtimeData?: boolean; basePath?: string; isParallel?: boolean; needsCleanupImport?: boolean ; needsDb?: boolean }): string {
|
|
233
|
-
return this.render('imports', { runtimeData: options?.runtimeData, basePath: options?.basePath || '..', isParallel: options?.isParallel, needsCleanupImport: options?.needsCleanupImport, needsDb: options?.needsDb });
|
|
232
|
+
renderImports(options?: { runtimeData?: boolean; basePath?: string; isParallel?: boolean; needsCleanupImport?: boolean ; needsDb?: boolean; needsApi?: boolean }): string {
|
|
233
|
+
return this.render('imports', { runtimeData: options?.runtimeData, basePath: options?.basePath || '..', isParallel: options?.isParallel, needsCleanupImport: options?.needsCleanupImport, needsDb: options?.needsDb, needsApi: options?.needsApi });
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
renderTestFile(data: {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared annotation-override grammar for precondition annotations (`@query`/`@api`).
|
|
3
|
+
*
|
|
4
|
+
* Parses `name(a={{x}},b="lit",c=3)` overrides into a map of JS expressions, e.g.
|
|
5
|
+
* `{ a: "testData.get('x')", b: "\"lit\"", c: "3" }`. Used by the DB and API capability drivers'
|
|
6
|
+
* precondition codegen; lives in core so both drivers can share it. Gherkin tags carry no whitespace,
|
|
7
|
+
* so values are single tokens — flows thread a prior response via a whole-value ref, e.g.
|
|
8
|
+
* `@api:get_profile(token={{login.body.token}})`, with the auth scheme declared in the catalog header.
|
|
9
|
+
*/
|
|
10
|
+
export function parseQueryOverrides(raw?: string): Record<string, string> {
|
|
11
|
+
const out: Record<string, string> = {};
|
|
12
|
+
if (!raw) return out;
|
|
13
|
+
for (const part of raw.split(',')) {
|
|
14
|
+
const eq = part.indexOf('=');
|
|
15
|
+
if (eq < 0) continue;
|
|
16
|
+
const key = part.slice(0, eq).trim();
|
|
17
|
+
const val = part.slice(eq + 1).trim();
|
|
18
|
+
if (!key) continue;
|
|
19
|
+
const v = val.match(/^\{\{\s*([^}]+?)\s*\}\}$/);
|
|
20
|
+
const q = val.match(/^["'](.*)["']$/);
|
|
21
|
+
if (v) out[key] = `testData.get(${JSON.stringify(v[1])})`;
|
|
22
|
+
else if (q) out[key] = JSON.stringify(q[1]);
|
|
23
|
+
else if (/^-?\d+(?:\.\d+)?$/.test(val)) out[key] = val;
|
|
24
|
+
else out[key] = JSON.stringify(val);
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|