@sun-asterisk/sungen 3.2.24 → 3.2.26
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/context-router.d.ts.map +1 -1
- package/dist/capabilities/context-router.js +15 -7
- package/dist/capabilities/context-router.js.map +1 -1
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +23 -4
- 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 +6 -48
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/changed.d.ts +14 -0
- package/dist/cli/commands/changed.d.ts.map +1 -0
- package/dist/cli/commands/changed.js +128 -0
- package/dist/cli/commands/changed.js.map +1 -0
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +25 -1
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/commands/generate.js +1 -1
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/results-merge.d.ts +19 -0
- package/dist/cli/commands/results-merge.d.ts.map +1 -0
- package/dist/cli/commands/results-merge.js +191 -0
- package/dist/cli/commands/results-merge.js.map +1 -0
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +100 -12
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/index.js +4 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/exporters/api-catalog-loader.d.ts.map +1 -1
- package/dist/exporters/api-catalog-loader.js +10 -3
- package/dist/exporters/api-catalog-loader.js.map +1 -1
- package/dist/exporters/api-testcase-formatter.d.ts +9 -0
- package/dist/exporters/api-testcase-formatter.d.ts.map +1 -1
- package/dist/exporters/api-testcase-formatter.js +55 -3
- package/dist/exporters/api-testcase-formatter.js.map +1 -1
- package/dist/exporters/csv-exporter.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.js +10 -1
- package/dist/exporters/csv-exporter.js.map +1 -1
- package/dist/exporters/json-exporter.d.ts.map +1 -1
- package/dist/exporters/json-exporter.js +6 -1
- package/dist/exporters/json-exporter.js.map +1 -1
- package/dist/exporters/matrix/build.d.ts.map +1 -1
- package/dist/exporters/matrix/build.js +14 -1
- package/dist/exporters/matrix/build.js.map +1 -1
- package/dist/exporters/matrix/export.js +1 -1
- package/dist/exporters/matrix/export.js.map +1 -1
- package/dist/exporters/matrix/map-loader.d.ts.map +1 -1
- package/dist/exporters/matrix/map-loader.js +5 -0
- package/dist/exporters/matrix/map-loader.js.map +1 -1
- package/dist/exporters/matrix/render-csv.d.ts.map +1 -1
- package/dist/exporters/matrix/render-csv.js +5 -1
- package/dist/exporters/matrix/render-csv.js.map +1 -1
- package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
- package/dist/exporters/matrix/render-xlsx.js +4 -1
- package/dist/exporters/matrix/render-xlsx.js.map +1 -1
- package/dist/exporters/matrix/types.d.ts +18 -0
- package/dist/exporters/matrix/types.d.ts.map +1 -1
- package/dist/exporters/matrix/types.js.map +1 -1
- package/dist/exporters/playwright-report-parser.d.ts +17 -2
- package/dist/exporters/playwright-report-parser.d.ts.map +1 -1
- package/dist/exporters/playwright-report-parser.js +68 -8
- package/dist/exporters/playwright-report-parser.js.map +1 -1
- package/dist/exporters/results-merge.d.ts +46 -0
- package/dist/exporters/results-merge.d.ts.map +1 -0
- package/dist/exporters/results-merge.js +148 -0
- package/dist/exporters/results-merge.js.map +1 -0
- package/dist/exporters/selector-key-resolver.d.ts +4 -0
- package/dist/exporters/selector-key-resolver.d.ts.map +1 -1
- package/dist/exporters/selector-key-resolver.js +101 -1
- package/dist/exporters/selector-key-resolver.js.map +1 -1
- package/dist/exporters/types.d.ts +8 -1
- package/dist/exporters/types.d.ts.map +1 -1
- package/dist/exporters/xlsx-report-builder.d.ts.map +1 -1
- package/dist/exporters/xlsx-report-builder.js +5 -4
- package/dist/exporters/xlsx-report-builder.js.map +1 -1
- package/dist/generators/gherkin-parser/index.d.ts +2 -0
- package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
- package/dist/generators/gherkin-parser/index.js +52 -1
- package/dist/generators/gherkin-parser/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts +20 -10
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts +2 -11
- package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/appium/appium-adapter.js.map +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/imports.hbs +3 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/date-picker-unsupported.hbs +4 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/lifecycle-missing-app-id.hbs +5 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/matches-format-assertion.hbs +15 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +5 -2
- package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +185 -26
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +2 -11
- 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/adapters/playwright/templates/steps/actions/select-action.hbs +17 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/attribute-assertion.hbs +6 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/have-value-assertion.hbs +18 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-filter-assertion.hbs +5 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/matches-format-assertion.hbs +5 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +4 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-for-element-with-text.hbs +4 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-expect-text.hbs +1 -1
- package/dist/generators/test-generator/code-generator.d.ts +6 -0
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +80 -7
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/diagnostics.d.ts +60 -6
- package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
- package/dist/generators/test-generator/diagnostics.js +59 -5
- package/dist/generators/test-generator/diagnostics.js.map +1 -1
- package/dist/generators/test-generator/patterns/expect-patterns.d.ts +7 -0
- package/dist/generators/test-generator/patterns/expect-patterns.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/expect-patterns.js +36 -2
- package/dist/generators/test-generator/patterns/expect-patterns.js.map +1 -1
- package/dist/generators/test-generator/patterns/index.d.ts +19 -12
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +58 -38
- package/dist/generators/test-generator/patterns/index.js.map +1 -1
- package/dist/generators/test-generator/patterns/types.d.ts +23 -0
- package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/unsupported-step-code.d.ts +18 -0
- package/dist/generators/test-generator/patterns/unsupported-step-code.d.ts.map +1 -0
- package/dist/generators/test-generator/patterns/unsupported-step-code.js +23 -0
- package/dist/generators/test-generator/patterns/unsupported-step-code.js.map +1 -0
- package/dist/generators/test-generator/recipe-expander.js +9 -1
- package/dist/generators/test-generator/recipe-expander.js.map +1 -1
- package/dist/generators/test-generator/reset-tier.d.ts +68 -0
- package/dist/generators/test-generator/reset-tier.d.ts.map +1 -0
- package/dist/generators/test-generator/reset-tier.js +94 -0
- package/dist/generators/test-generator/reset-tier.js.map +1 -0
- package/dist/generators/test-generator/step-mapper.d.ts +39 -0
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +144 -5
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/template-engine.d.ts +2 -10
- package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
- package/dist/generators/test-generator/template-engine.js +16 -2
- package/dist/generators/test-generator/template-engine.js.map +1 -1
- package/dist/generators/test-generator/utils/format-pattern.d.ts +35 -0
- package/dist/generators/test-generator/utils/format-pattern.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/format-pattern.js +82 -0
- package/dist/generators/test-generator/utils/format-pattern.js.map +1 -0
- package/dist/generators/test-generator/utils/js-literal-scanner.d.ts +42 -0
- package/dist/generators/test-generator/utils/js-literal-scanner.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/js-literal-scanner.js +142 -0
- package/dist/generators/test-generator/utils/js-literal-scanner.js.map +1 -0
- package/dist/generators/test-generator/utils/mask-refs-for-matching.d.ts +18 -0
- package/dist/generators/test-generator/utils/mask-refs-for-matching.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/mask-refs-for-matching.js +25 -0
- package/dist/generators/test-generator/utils/mask-refs-for-matching.js.map +1 -0
- package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts +9 -1
- package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/runtime-data-transformer.js +124 -63
- package/dist/generators/test-generator/utils/runtime-data-transformer.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +20 -1
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/generators/test-generator/utils/spec-syntax-check.d.ts +14 -0
- package/dist/generators/test-generator/utils/spec-syntax-check.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/spec-syntax-check.js +26 -0
- package/dist/generators/test-generator/utils/spec-syntax-check.js.map +1 -0
- package/dist/harness/annotation-overrides.d.ts +0 -12
- package/dist/harness/annotation-overrides.d.ts.map +1 -1
- package/dist/harness/annotation-overrides.js +9 -5
- package/dist/harness/annotation-overrides.js.map +1 -1
- package/dist/harness/audit.d.ts +7 -0
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +78 -10
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/capability-plan.d.ts +2 -1
- package/dist/harness/capability-plan.d.ts.map +1 -1
- package/dist/harness/capability-plan.js +8 -3
- package/dist/harness/capability-plan.js.map +1 -1
- package/dist/harness/capability.d.ts +11 -0
- package/dist/harness/capability.d.ts.map +1 -1
- package/dist/harness/capability.js +8 -0
- package/dist/harness/capability.js.map +1 -1
- package/dist/harness/catalog/drivers.yaml +5 -0
- package/dist/harness/flow-contract.d.ts +18 -1
- package/dist/harness/flow-contract.d.ts.map +1 -1
- package/dist/harness/flow-contract.js +72 -9
- package/dist/harness/flow-contract.js.map +1 -1
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +5 -1
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/quality-gates.d.ts +12 -1
- package/dist/harness/quality-gates.d.ts.map +1 -1
- package/dist/harness/quality-gates.js +62 -7
- package/dist/harness/quality-gates.js.map +1 -1
- package/dist/harness/run-manifest.d.ts +58 -0
- package/dist/harness/run-manifest.d.ts.map +1 -0
- package/dist/harness/run-manifest.js +264 -0
- package/dist/harness/run-manifest.js.map +1 -0
- package/dist/harness/spec-branches.d.ts +88 -0
- package/dist/harness/spec-branches.d.ts.map +1 -0
- package/dist/harness/spec-branches.js +280 -0
- package/dist/harness/spec-branches.js.map +1 -0
- package/dist/harness/spec-coverage.d.ts +1 -1
- package/dist/harness/spec-coverage.js +4 -4
- package/dist/harness/spec-coverage.js.map +1 -1
- package/dist/harness/viewpoint-baseline.d.ts +9 -0
- package/dist/harness/viewpoint-baseline.d.ts.map +1 -1
- package/dist/harness/viewpoint-baseline.js +33 -3
- package/dist/harness/viewpoint-baseline.js.map +1 -1
- package/dist/harness/viewpoint-ledger.d.ts +1 -1
- package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
- package/dist/harness/viewpoint-ledger.js +88 -9
- package/dist/harness/viewpoint-ledger.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/orchestrator/lockstep-version.d.ts +22 -0
- package/dist/orchestrator/lockstep-version.d.ts.map +1 -0
- package/dist/orchestrator/lockstep-version.js +58 -0
- package/dist/orchestrator/lockstep-version.js.map +1 -0
- package/dist/orchestrator/project-deps-sync.d.ts +39 -0
- package/dist/orchestrator/project-deps-sync.d.ts.map +1 -0
- package/dist/orchestrator/project-deps-sync.js +175 -0
- package/dist/orchestrator/project-deps-sync.js.map +1 -0
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +5 -2
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/add-flow.md +16 -0
- package/dist/orchestrator/templates/ai-src/commands/create-test.md +9 -0
- package/dist/orchestrator/templates/ai-src/commands/delivery.md +9 -2
- package/dist/orchestrator/templates/ai-src/commands/run-test.md +109 -0
- package/dist/orchestrator/templates/ai-src/config/claude.md +1 -1
- package/dist/orchestrator/templates/ai-src/config/copilot.md +1 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-api-design/SKILL.md +49 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +14 -3
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +110 -6
- package/dist/orchestrator/templates/ai-src/skills/sungen-locale/SKILL.md +1 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-mobile-gestures/SKILL.md +8 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +1 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +40 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +15 -0
- package/dist/orchestrator/templates/env.appium.example +10 -0
- package/dist/orchestrator/templates/specs-api.d.ts +4 -2
- package/dist/orchestrator/templates/specs-api.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-api.js +233 -10
- package/dist/orchestrator/templates/specs-api.js.map +1 -1
- package/dist/orchestrator/templates/specs-api.ts +214 -11
- package/dist/orchestrator/templates/specs-regex-escape.d.ts +13 -0
- package/dist/orchestrator/templates/specs-regex-escape.d.ts.map +1 -0
- package/dist/orchestrator/templates/specs-regex-escape.js +19 -0
- package/dist/orchestrator/templates/specs-regex-escape.js.map +1 -0
- package/dist/orchestrator/templates/specs-regex-escape.ts +16 -0
- package/dist/orchestrator/templates/specs-test-data.ts +53 -6
- package/dist/orchestrator/templates/wdio.conf.ts +28 -9
- package/package.json +8 -6
- package/src/capabilities/context-router.ts +13 -5
- package/src/cli/commands/audit.ts +22 -3
- package/src/cli/commands/capability.ts +4 -38
- package/src/cli/commands/changed.ts +104 -0
- package/src/cli/commands/delivery.ts +27 -2
- package/src/cli/commands/generate.ts +1 -1
- package/src/cli/commands/results-merge.ts +153 -0
- package/src/cli/commands/update.ts +72 -13
- package/src/cli/index.ts +4 -0
- package/src/exporters/api-catalog-loader.ts +10 -2
- package/src/exporters/api-testcase-formatter.ts +59 -3
- package/src/exporters/csv-exporter.ts +11 -2
- package/src/exporters/json-exporter.ts +7 -2
- package/src/exporters/matrix/build.ts +13 -1
- package/src/exporters/matrix/export.ts +1 -1
- package/src/exporters/matrix/map-loader.ts +5 -0
- package/src/exporters/matrix/render-csv.ts +5 -1
- package/src/exporters/matrix/render-xlsx.ts +4 -1
- package/src/exporters/matrix/types.ts +18 -0
- package/src/exporters/playwright-report-parser.ts +68 -9
- package/src/exporters/results-merge.ts +150 -0
- package/src/exporters/selector-key-resolver.ts +81 -1
- package/src/exporters/types.ts +8 -1
- package/src/exporters/xlsx-report-builder.ts +5 -4
- package/src/generators/gherkin-parser/index.ts +55 -1
- package/src/generators/test-generator/adapters/adapter-interface.ts +23 -1
- package/src/generators/test-generator/adapters/appium/appium-adapter.ts +2 -2
- package/src/generators/test-generator/adapters/appium/templates/imports.hbs +3 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/date-picker-unsupported.hbs +4 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/lifecycle-missing-app-id.hbs +5 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/matches-format-assertion.hbs +15 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +5 -2
- package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +185 -26
- package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +2 -2
- package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs +17 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/attribute-assertion.hbs +6 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/have-value-assertion.hbs +18 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-filter-assertion.hbs +5 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/matches-format-assertion.hbs +5 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +4 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-for-element-with-text.hbs +4 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-expect-text.hbs +1 -1
- package/src/generators/test-generator/code-generator.ts +86 -7
- package/src/generators/test-generator/diagnostics.ts +60 -6
- package/src/generators/test-generator/patterns/expect-patterns.ts +37 -2
- package/src/generators/test-generator/patterns/index.ts +58 -33
- package/src/generators/test-generator/patterns/types.ts +36 -0
- package/src/generators/test-generator/patterns/unsupported-step-code.ts +19 -0
- package/src/generators/test-generator/recipe-expander.ts +9 -1
- package/src/generators/test-generator/reset-tier.ts +93 -0
- package/src/generators/test-generator/step-mapper.ts +149 -6
- package/src/generators/test-generator/template-engine.ts +18 -3
- package/src/generators/test-generator/utils/format-pattern.ts +79 -0
- package/src/generators/test-generator/utils/js-literal-scanner.ts +132 -0
- package/src/generators/test-generator/utils/mask-refs-for-matching.ts +21 -0
- package/src/generators/test-generator/utils/runtime-data-transformer.ts +126 -76
- package/src/generators/test-generator/utils/selector-resolver.ts +22 -1
- package/src/generators/test-generator/utils/spec-syntax-check.ts +22 -0
- package/src/harness/annotation-overrides.ts +10 -5
- package/src/harness/audit.ts +82 -12
- package/src/harness/capability-plan.ts +10 -5
- package/src/harness/capability.ts +16 -0
- package/src/harness/catalog/drivers.yaml +5 -0
- package/src/harness/flow-contract.ts +87 -9
- package/src/harness/parse.ts +5 -1
- package/src/harness/quality-gates.ts +64 -6
- package/src/harness/run-manifest.ts +254 -0
- package/src/harness/spec-branches.ts +346 -0
- package/src/harness/spec-coverage.ts +4 -4
- package/src/harness/viewpoint-baseline.ts +41 -6
- package/src/harness/viewpoint-ledger.ts +82 -9
- package/src/index.ts +1 -0
- package/src/orchestrator/lockstep-version.ts +46 -0
- package/src/orchestrator/project-deps-sync.ts +175 -0
- package/src/orchestrator/project-initializer.ts +5 -2
- package/src/orchestrator/templates/ai-src/commands/add-flow.md +16 -0
- package/src/orchestrator/templates/ai-src/commands/create-test.md +9 -0
- package/src/orchestrator/templates/ai-src/commands/delivery.md +9 -2
- package/src/orchestrator/templates/ai-src/commands/run-test.md +109 -0
- package/src/orchestrator/templates/ai-src/config/claude.md +1 -1
- package/src/orchestrator/templates/ai-src/config/copilot.md +1 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-api-design/SKILL.md +49 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +14 -3
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +110 -6
- package/src/orchestrator/templates/ai-src/skills/sungen-locale/SKILL.md +1 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-mobile-gestures/SKILL.md +8 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +1 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +40 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +15 -0
- package/src/orchestrator/templates/env.appium.example +10 -0
- package/src/orchestrator/templates/specs-api.ts +214 -11
- package/src/orchestrator/templates/specs-regex-escape.ts +16 -0
- package/src/orchestrator/templates/specs-test-data.ts +53 -6
- package/src/orchestrator/templates/wdio.conf.ts +28 -9
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { spawnSync } from 'child_process';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lockstep version resolution for the sungen package family.
|
|
5
|
+
*
|
|
6
|
+
* Core (`@sun-asterisk/sungen`) and every capability driver (`@sungen/driver-*`) are published
|
|
7
|
+
* together, so a project must hold ONE version line across all of them: a driver built against
|
|
8
|
+
* core 3.2.24 loading against core 3.2.20 is a silent-breakage class we refuse to ship. Both
|
|
9
|
+
* `sungen capability add` (first install) and `sungen update` (project sync) resolve the target
|
|
10
|
+
* version through here so they can never disagree.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** The running CLI's (core) version — family packages install in lockstep with it (not `@latest`). */
|
|
14
|
+
export function coreVersion(): string {
|
|
15
|
+
try { return require('../../package.json').version || 'latest'; } catch { return 'latest'; }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the version to install for a family package, given the running `core` version.
|
|
20
|
+
*
|
|
21
|
+
* Lockstep wants the EXACT `core` version. But the beta channel publishes `<core>-beta.N` before a
|
|
22
|
+
* stable `<core>` ever exists — so a released core (e.g. 3.2.10) asking for `@3.2.10` hits ETARGET
|
|
23
|
+
* out-of-the-box when npm only has `3.2.10-beta.2` (`sungen capability add` then fails for a fresh
|
|
24
|
+
* user). Fall back on the SAME version line: prefer exact `core`, else the highest `core-beta.N`,
|
|
25
|
+
* else the `beta` dist-tag as a last resort. Keeps the driver's pinned core-dep on the same line.
|
|
26
|
+
*/
|
|
27
|
+
export function resolveDriverVersion(pkg: string, core: string): string {
|
|
28
|
+
if (core === 'latest') return 'beta';
|
|
29
|
+
try {
|
|
30
|
+
const out = spawnSync('npm', ['view', pkg, 'versions', '--json'], { encoding: 'utf-8', shell: true });
|
|
31
|
+
if (out.status !== 0 || !out.stdout) return core; // can't query → let the exact install try/fail
|
|
32
|
+
let versions: string[] = [];
|
|
33
|
+
const parsed = JSON.parse(out.stdout);
|
|
34
|
+
versions = Array.isArray(parsed) ? parsed : [parsed];
|
|
35
|
+
if (versions.includes(core)) return core; // exact exists → lockstep
|
|
36
|
+
const prefix = `${core}-beta.`;
|
|
37
|
+
const betas = versions
|
|
38
|
+
.filter((v) => typeof v === 'string' && v.startsWith(prefix))
|
|
39
|
+
.map((v) => parseInt(v.slice(prefix.length), 10))
|
|
40
|
+
.filter((n) => Number.isInteger(n));
|
|
41
|
+
if (betas.length) return `${core}-beta.${Math.max(...betas)}`; // highest same-line prerelease
|
|
42
|
+
return 'beta'; // nothing on this line → newest beta dist-tag
|
|
43
|
+
} catch {
|
|
44
|
+
return core;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { spawnSync } from 'child_process';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { resolveDriverVersion } from './lockstep-version';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Project-local sync of the sungen package family after a global `sungen update`.
|
|
8
|
+
*
|
|
9
|
+
* `sungen update` used to run `npm install -g @sun-asterisk/sungen@<channel>` and stop there. That
|
|
10
|
+
* left every PROJECT-local copy behind, and a project always has them: `sungen capability add <x>`
|
|
11
|
+
* installs `@sungen/driver-<x>` as a devDependency pinned to the core version of the day, and each
|
|
12
|
+
* driver declares `@sun-asterisk/sungen` as a dependency — so a local core lands in the project's
|
|
13
|
+
* node_modules too, along with the `sungen` bin link. The lockfile then pins all of it. Result after
|
|
14
|
+
* an update: global 3.2.24, project still 3.2.20, and `npx sungen` inside the project silently
|
|
15
|
+
* running the OLD compiler — with drivers built against a core that is no longer the one loading
|
|
16
|
+
* them.
|
|
17
|
+
*
|
|
18
|
+
* So the update also reconciles the project: every family package declared (or physically present)
|
|
19
|
+
* in the project is reinstalled on the running core's version line.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** Family = core + every capability driver. These MUST share one version line in a project. */
|
|
23
|
+
const CORE_PKG = '@sun-asterisk/sungen';
|
|
24
|
+
const isFamily = (name: string): boolean => name === CORE_PKG || name.startsWith('@sungen/driver-');
|
|
25
|
+
|
|
26
|
+
/** Declared specs pointing at a checkout, not the registry — a workspace/dogfood link we must not clobber. */
|
|
27
|
+
const isLocalSpec = (spec: string): boolean =>
|
|
28
|
+
/^(file:|link:|workspace:|portal:|\.{1,2}\/|\/)/.test(spec) || spec.startsWith('git+') || spec.startsWith('github:');
|
|
29
|
+
|
|
30
|
+
export interface FamilyPkg {
|
|
31
|
+
pkg: string;
|
|
32
|
+
/** Range in package.json dependencies/devDependencies, or null when only present transitively. */
|
|
33
|
+
declared: string | null;
|
|
34
|
+
/** Version actually resolved in the project tree, or null when absent. `link` = symlinked. */
|
|
35
|
+
installed: string | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface DepSyncPlan {
|
|
39
|
+
/** `npm install -D` specs to run. Empty when nothing needs to change. */
|
|
40
|
+
specs: string[];
|
|
41
|
+
/** Family packages found in the project, for reporting. */
|
|
42
|
+
found: FamilyPkg[];
|
|
43
|
+
/** Packages deliberately left alone, with the reason. */
|
|
44
|
+
skipped: Array<{ pkg: string; why: string }>;
|
|
45
|
+
/** Set when there is no project to sync at all (no package.json / no family packages). */
|
|
46
|
+
noop?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Read a package.json, returning null when missing or malformed. */
|
|
50
|
+
function readJson(file: string): Record<string, any> | null {
|
|
51
|
+
try {
|
|
52
|
+
return JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Version of `pkg` as installed in the project tree, or null. Symlinked (workspace) → `link`. */
|
|
59
|
+
function installedVersion(cwd: string, pkg: string): string | null {
|
|
60
|
+
const dir = path.join(cwd, 'node_modules', ...pkg.split('/'));
|
|
61
|
+
try {
|
|
62
|
+
if (fs.lstatSync(dir).isSymbolicLink()) return 'link';
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return readJson(path.join(dir, 'package.json'))?.version ?? null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Decide what the project needs so its family packages sit on `core`'s version line.
|
|
71
|
+
*
|
|
72
|
+
* Pure apart from reading the project tree and (for stale packages) asking npm which versions exist
|
|
73
|
+
* on that line — no installs, so `--dry-run` and the tests can plan without side effects.
|
|
74
|
+
*/
|
|
75
|
+
export function planProjectDepSync(
|
|
76
|
+
cwd: string,
|
|
77
|
+
core: string,
|
|
78
|
+
resolve: (pkg: string, core: string) => string = resolveDriverVersion,
|
|
79
|
+
): DepSyncPlan {
|
|
80
|
+
const pkgJson = readJson(path.join(cwd, 'package.json'));
|
|
81
|
+
if (!pkgJson) {
|
|
82
|
+
return { specs: [], found: [], skipped: [], noop: 'no package.json here — nothing project-local to sync' };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const declaredAll: Record<string, string> = { ...pkgJson.dependencies, ...pkgJson.devDependencies };
|
|
86
|
+
const names = new Set(Object.keys(declaredAll).filter(isFamily));
|
|
87
|
+
|
|
88
|
+
// A transitively-installed core (pulled in by a driver) is the copy that owns the project's `sungen`
|
|
89
|
+
// bin link, so it must be reconciled even though nothing declares it.
|
|
90
|
+
if (!names.has(CORE_PKG) && installedVersion(cwd, CORE_PKG)) names.add(CORE_PKG);
|
|
91
|
+
|
|
92
|
+
if (!names.size) {
|
|
93
|
+
return {
|
|
94
|
+
specs: [],
|
|
95
|
+
found: [],
|
|
96
|
+
skipped: [],
|
|
97
|
+
noop: 'no sungen packages installed in this project — the global update is all that was needed',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const found: FamilyPkg[] = [...names].sort().map((pkg) => ({
|
|
102
|
+
pkg,
|
|
103
|
+
declared: declaredAll[pkg] ?? null,
|
|
104
|
+
installed: installedVersion(cwd, pkg),
|
|
105
|
+
}));
|
|
106
|
+
|
|
107
|
+
const specs: string[] = [];
|
|
108
|
+
const skipped: Array<{ pkg: string; why: string }> = [];
|
|
109
|
+
|
|
110
|
+
for (const entry of found) {
|
|
111
|
+
if (entry.declared && isLocalSpec(entry.declared)) {
|
|
112
|
+
skipped.push({ pkg: entry.pkg, why: `declared as ${entry.declared} (local checkout, not the registry)` });
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (entry.installed === 'link') {
|
|
116
|
+
skipped.push({ pkg: entry.pkg, why: 'symlinked (workspace or npm link)' });
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (entry.installed === core) continue; // already on the running core's exact version
|
|
120
|
+
specs.push(`${entry.pkg}@${resolve(entry.pkg, core)}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return { specs, found, skipped };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Reconcile the project's sungen family packages with the running core version.
|
|
128
|
+
*
|
|
129
|
+
* Runs ONE `npm install -D` for every stale package so the lockfile is rewritten in a single
|
|
130
|
+
* resolution pass. Best-effort by design: a failure here (offline, private registry, no write
|
|
131
|
+
* permission) must not fail `sungen update` — the global install already succeeded, and the user
|
|
132
|
+
* gets the exact command to finish the job by hand.
|
|
133
|
+
*
|
|
134
|
+
* @returns true when the project ended up in sync (or needed nothing), false when the install failed
|
|
135
|
+
*/
|
|
136
|
+
export function syncProjectSungenDeps(cwd: string, core: string, dryRun: boolean): boolean {
|
|
137
|
+
const plan = planProjectDepSync(cwd, core);
|
|
138
|
+
|
|
139
|
+
console.log('🔗 Syncing project-local sungen packages...');
|
|
140
|
+
if (plan.noop) {
|
|
141
|
+
console.log(` – ${plan.noop}\n`);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
for (const s of plan.skipped) console.log(` – skipped ${s.pkg} — ${s.why}`);
|
|
145
|
+
|
|
146
|
+
if (!plan.specs.length) {
|
|
147
|
+
console.log(` ✓ already on ${core}: ${plan.found.map((f) => f.pkg).join(', ')}\n`);
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
for (const f of plan.found) {
|
|
152
|
+
if (!plan.specs.some((s) => s.startsWith(`${f.pkg}@`))) continue;
|
|
153
|
+
const wasTransitive = f.declared ? '' : ' (was transitive — pinning explicitly)';
|
|
154
|
+
console.log(` • ${f.pkg} ${f.installed ?? '(not installed)'} → ${core}${wasTransitive}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const cmd = `npm install -D ${plan.specs.join(' ')}`;
|
|
158
|
+
if (dryRun) {
|
|
159
|
+
console.log(` [dry-run] would run: ${cmd}\n`);
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
console.log(` 📦 ${cmd}`);
|
|
164
|
+
const r = spawnSync('npm', ['install', '-D', ...plan.specs], { cwd, stdio: 'inherit', shell: true });
|
|
165
|
+
if (r.status !== 0) {
|
|
166
|
+
console.warn(
|
|
167
|
+
`⚠️ Project-local sync failed — the GLOBAL sungen is now ${core}, but this project still holds\n` +
|
|
168
|
+
' older copies, so `npx sungen` here would keep running the old compiler. Finish manually:\n' +
|
|
169
|
+
` ${cmd}\n`,
|
|
170
|
+
);
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
console.log(` ✓ project-local packages on ${core}\n`);
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
@@ -656,9 +656,12 @@ export class ProjectInitializer {
|
|
|
656
656
|
|
|
657
657
|
// Mobile-only drops @playwright/test (no web runner needed). `ajv`/`ajv-formats` back the API
|
|
658
658
|
// Driver's `expect {{name.body}} matches schema [Ref]` (loaded lazily; harmless if unused).
|
|
659
|
+
// fast-xml-parser: the API driver's XML request/response support lazy-requires it
|
|
660
|
+
// from the generated specs/api.ts — installed up front so an `encoding: xml`
|
|
661
|
+
// catalog entry works without a mid-suite MODULE_NOT_FOUND.
|
|
659
662
|
const requiredDeps = this.mobileOnly
|
|
660
|
-
? ['@types/node', 'yaml', 'ajv', 'ajv-formats']
|
|
661
|
-
: ['@playwright/test', '@types/node', 'yaml', 'ajv', 'ajv-formats'];
|
|
663
|
+
? ['@types/node', 'yaml', 'ajv', 'ajv-formats', 'fast-xml-parser']
|
|
664
|
+
: ['@playwright/test', '@types/node', 'yaml', 'ajv', 'ajv-formats', 'fast-xml-parser'];
|
|
662
665
|
|
|
663
666
|
let missingDeps: string[] = requiredDeps;
|
|
664
667
|
try {
|
|
@@ -88,6 +88,22 @@ qa/flows/${input:flow}/
|
|
|
88
88
|
|
|
89
89
|
### 1a. Define the flow's BOUNDARY, then its screens
|
|
90
90
|
|
|
91
|
+
**FIRST: look for a boundary the project has already decided.** Before applying the generic
|
|
92
|
+
checklist, search the spec/docs tree for a document that defines the flows —
|
|
93
|
+
`*System_Test*.md`, `*Flow*.md`, `BF-*`, a flow inventory, a test-plan section listing flows. If
|
|
94
|
+
one exists, **its boundary wins**: use the flows and scope it declares rather than re-deriving them
|
|
95
|
+
from a single user story.
|
|
96
|
+
|
|
97
|
+
This is not a preference, it is where a whole run went wrong. A project whose `System_Test_Flows.md`
|
|
98
|
+
defined `BF-B0 = Dashboard` as one flow covering six regions got a boundary re-derived from one
|
|
99
|
+
user story instead, narrowing the scope to "view dashboard" and pushing six of eleven requirements
|
|
100
|
+
out of the flow **before generation started**. Every later step — coverage map, audit, delivery —
|
|
101
|
+
then ran correctly on the wrong scope, and no gate could see it, because each of them measures
|
|
102
|
+
against the boundary rather than questioning it.
|
|
103
|
+
|
|
104
|
+
Apply the generic ISTQB checklist below only when NO project-specific boundary document exists.
|
|
105
|
+
|
|
106
|
+
|
|
91
107
|
> QA teams often call this level **System Test** — same thing: one fully-integrated business
|
|
92
108
|
> journey verified against the spec. Use whichever name the team knows; the boundary rules
|
|
93
109
|
> below are the ISTQB system-test design rules.
|
|
@@ -64,6 +64,15 @@ restated from screen specs the project does not hold, say so: the audit reports
|
|
|
64
64
|
`SPEC-RESTATED-UNVERIFIED` because `specFR 100%` over a hand-copied list certifies the copy, not
|
|
65
65
|
the source.
|
|
66
66
|
|
|
67
|
+
**The viewpoint carries BOTH classification axes.** `VP-LOGIC / VP-VAL / VP-SEC / VP-NAV` classify
|
|
68
|
+
by TECHNICAL RISK; `BF / AF / EF` classify by BUSINESS BRANCH. They answer different questions and
|
|
69
|
+
neither replaces the other — a reviewer needs to see both, so use the compound id
|
|
70
|
+
(`VP-SEC-EF02-001`) and give the viewpoint a section per flow as well as its priority table by
|
|
71
|
+
theme. And restate the use case's `successGuarantee` / `minimalGuarantee` at the top of the
|
|
72
|
+
viewpoint (a one-line quote of the contract is enough): every Exception Flow's own outcome is
|
|
73
|
+
written against the minimal guarantee, and a reader of the viewpoint alone cannot check that if the
|
|
74
|
+
anchor is only in another file.
|
|
75
|
+
|
|
67
76
|
**The contract does NOT replace `test-viewpoint.md` — author BOTH.** They answer different
|
|
68
77
|
questions and only one of them is a yardstick:
|
|
69
78
|
|
|
@@ -158,8 +158,15 @@ requirements:
|
|
|
158
158
|
saying "proven by DI-SEC-CSRF" is prose — nothing detects it when that scenario later changes. If a
|
|
159
159
|
scenario in THIS feature proves the requirement, **add `@spec:<id>` to that scenario** so the trace
|
|
160
160
|
is real, then drop the override (it derives as `covered` on its own). Use `covered_elsewhere` only
|
|
161
|
-
when another suite proves it, and name that suite
|
|
162
|
-
|
|
161
|
+
when another suite proves it, and name that suite.
|
|
162
|
+
|
|
163
|
+
**`not_applicable` is the narrowest status — do not reach for it because it needs no extra
|
|
164
|
+
information.** It means the requirement belongs to a different feature or module and will NEVER be
|
|
165
|
+
covered by this flow. Anything you intend to cover later — a next batch, a follow-up flow, work
|
|
166
|
+
deferred for time — is `planned`, and the note must say WHICH batch or flow. A field review hit
|
|
167
|
+
exactly this: six requirements narrowed out of scope were marked `not_applicable`, and the reviewer
|
|
168
|
+
read that as a permanent, deliberate exclusion and had to go back and ask. `planned` costs one more
|
|
169
|
+
sentence and answers the question before it is asked.
|
|
163
170
|
|
|
164
171
|
Then validate and fix any ERROR findings:
|
|
165
172
|
|
|
@@ -91,6 +91,11 @@ Stop here for mobile — do not continue to the web path.
|
|
|
91
91
|
Parse from `$ARGUMENTS`:
|
|
92
92
|
- **name** — screen or flow name. If missing, ask the user.
|
|
93
93
|
- **`--env <locale>`** — optional. Sets `SUNGEN_ENV=<locale>` for the test run so the runtime test-data resolver merges `<name>.<locale>.yaml` over the base, and `playwright.config.ts` writes results to `<name>-test-result.<locale>.json`. Accept `--locale <locale>` as an alias. The value can also be a plain ENVIRONMENT name (`dev`, `uat`, `stg`): the same overlay applies to test-data AND `.env.qa.<env>` overlays `.env.qa` (secrets, API/DB datasource URLs); when `SUNGEN_BASE_URL` is declared there, the web baseURL switches too — no config edit, no recompile.
|
|
94
|
+
- **`--change-only`** — re-run only the scenarios a selectors / test-data / feature edit obliges, then merge
|
|
95
|
+
the result over the previous run's file instead of replacing it. Passing it also **skips the scope gate**:
|
|
96
|
+
the flag IS the answer, so asking again would be noise. Without the flag the gate asks — but only when the
|
|
97
|
+
selection is narrower than the whole unit (see the table). Either way a subset run must follow the merge
|
|
98
|
+
order below; the backup has to exist BEFORE the run, because the report filename is fixed.
|
|
94
99
|
- **`--ios`**, **`--android`**, **`--both`** (mobile only) — optional. Sets `MOBILE_PLATFORM` for the test run. Default: `android` if not specified. Parsed into `MOBILE_PLATFORM=<value>` and used in WebdriverIO test runs.
|
|
95
100
|
|
|
96
101
|
If `--env` is passed but no value follows, ask the user which locale to use.
|
|
@@ -153,6 +158,53 @@ If the unit is **api-first**, skip every selector/capture phase (an API test has
|
|
|
153
158
|
5. **Integrity + trace** — `sungen script-check --area <name>` (1:1; on DRIFT re-`generate --area`, never hand-edit the spec) + `sungen trace --area <name>` (process map + HUMAN-LOOP FOCUS). Report + offer next steps.
|
|
154
159
|
{{/cap}}
|
|
155
160
|
|
|
161
|
+
## Scope gate — ask BEFORE running, only when there is a real choice
|
|
162
|
+
|
|
163
|
+
Run this immediately after platform detection, before Pre-run. It decides WHAT to run; skipping it
|
|
164
|
+
is how a QA ends up re-running 100 cases to re-check 3, or worse, re-running 3 and losing the 97.
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
[ -x ./bin/sungen.js ] && ./bin/sungen.js changed --screen <name> --json || npx sungen changed --screen <name> --json
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Read `summary.selected`, `summary.total`, `all`, and `reasons`, then follow this table exactly:
|
|
171
|
+
|
|
172
|
+
| Situation | What to do |
|
|
173
|
+
|---|---|
|
|
174
|
+
| `--change-only` was passed | **Do NOT ask.** Run the changed set, print the list + reasons for the record, follow the merge flow below. |
|
|
175
|
+
| `all: true` | **Do NOT ask** — there is nothing to choose. Print the reason (`no previous run recorded`, `the Background changed`, `project config changed`, `git reports …`) and run the FULL suite. |
|
|
176
|
+
| `selected == 0` | **Do NOT ask.** Say plainly that nothing changed since the last recorded run, and use `AskUserQuestion` to offer: re-run the full suite anyway / stop here. |
|
|
177
|
+
| `0 < selected < total` | **ASK** — this is the only case with two honest answers. |
|
|
178
|
+
|
|
179
|
+
**Close EVERY accepted run by recording the baseline** — full runs included, not just subsets:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
[ -x ./bin/sungen.js ] && ./bin/sungen.js changed --screen <name> --record || npx sungen changed --screen <name> --record
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Without this the first run leaves no baseline, so the next invocation reports `no previous run
|
|
186
|
+
recorded` and runs everything again — the narrowing never starts. Record it after the run whose
|
|
187
|
+
results you accept (a full run, or a subset once merged), and NOT after a run you are about to
|
|
188
|
+
redo: leaving the baseline where it is keeps the same scenarios selected on the next attempt.
|
|
189
|
+
|
|
190
|
+
When you ask, first PRINT the selection so the choice is informed — one line per scenario with its
|
|
191
|
+
reason, exactly as `sungen changed` renders it — then `AskUserQuestion` with two options:
|
|
192
|
+
|
|
193
|
+
- **`Chạy N case đã đổi` (Recommended)** — description: names the N scenarios (truncate past 3 with
|
|
194
|
+
"…và K case khác") and states that the previous results are preserved by the merge, so the report
|
|
195
|
+
stays complete.
|
|
196
|
+
- **`Chạy full M case`** — description: slower, and states WHEN it is the right call — before a
|
|
197
|
+
delivery/hand-off, or when the APP under test changed.
|
|
198
|
+
|
|
199
|
+
That second reason matters and must be said: the selection fingerprints **the QA's files only**
|
|
200
|
+
(feature, selectors, test-data, project config). It cannot see the app under test. If the build
|
|
201
|
+
changed, nothing in `qa/` moved, `changed` reports "nothing changed", and only a full run tells you
|
|
202
|
+
anything. Never present the changed set as proof the rest still passes — it is proof only that the
|
|
203
|
+
rest was not re-examined.
|
|
204
|
+
|
|
205
|
+
Do not add a third option. "Show me more detail" is not a choice — the detail is printed above the
|
|
206
|
+
question already.
|
|
207
|
+
|
|
156
208
|
## Pre-run (phased — per `sungen-selector-fix` skill)
|
|
157
209
|
|
|
158
210
|
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).
|
|
@@ -253,6 +305,22 @@ PLAYWRIGHT_JSON_OUTPUT_NAME=specs/generated/flows/<name>/<name>-test-result.json
|
|
|
253
305
|
|
|
254
306
|
**DO NOT** pass `--reporter=...` flag — it overrides the reporters from `playwright.config.ts` and disables the JSON reporter that `sungen delivery` depends on.
|
|
255
307
|
|
|
308
|
+
**Re-running a SUBSET (fix-and-retry, or a partially edited spec) when the per-target result file already exists:** the JSON reporter would OVERWRITE it and lose every result you didn't re-run. Write the subset's report to a temp file and MERGE it instead — same title wins, everything else is kept:
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
# re-run just the failed cases (or -g "<scenario title>") into a temp report…
|
|
312
|
+
PLAYWRIGHT_JSON_OUTPUT_NAME=specs/generated/<name>/subset-tmp.json \
|
|
313
|
+
npx playwright test specs/generated/<name>/<name>.spec.ts --last-failed
|
|
314
|
+
|
|
315
|
+
# …then fold it into the existing per-target file (delivery reads the merged result)
|
|
316
|
+
sungen results merge --screen <name> specs/generated/<name>/subset-tmp.json
|
|
317
|
+
rm specs/generated/<name>/subset-tmp.json
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
`--screen` takes the FEATURE basename — for a multi-feature screen (`home.spec.ts` + `home-modal.spec.ts`), each feature has its own result file: merge `home-modal`'s subset with `--screen home-modal`. If the previous run had zero failures, `--last-failed` runs nothing and writes no JSON — use `-g "<scenario title>"` to pick the cases instead.
|
|
321
|
+
|
|
322
|
+
For a locale run (`SUNGEN_ENV=vi`), the config appends `.vi` before `.json` to WHATEVER `PLAYWRIGHT_JSON_OUTPUT_NAME` says — the temp report lands at `subset-tmp.vi.json`; merge that file into the matching `<name>-test-result.vi.json` explicitly: `sungen results merge specs/generated/<name>/<name>-test-result.vi.json specs/generated/<name>/subset-tmp.vi.json`. A scenario RENAMED by a spec edit leaves its old-title result in the file — the merge warns and keeps it (delivery builds rows from the feature, so it never renders).
|
|
323
|
+
|
|
256
324
|
```bash
|
|
257
325
|
# ❌ Wrong — --reporter flag disables the config's JSON reporter
|
|
258
326
|
{{#cap parallel-subagents}}
|
|
@@ -281,6 +349,47 @@ If you want to filter scenarios, use `-g "<pattern>"` instead of a reporter over
|
|
|
281
349
|
`sungen delivery` reads per-feature `<basename>-test-result[.env].json` files (one per feature in the screen) and writes one CSV/XLSX per feature (e.g. `home-testcases.csv` + `home-modal-testcases.csv`). When `--env <locale>` was used here, run delivery with the same locale (`/sungen-delivery <name> --env <locale>`) so it picks the matching `*-test-result.<locale>.json` files and produces `*-testcases.<locale>.csv` / `.xlsx`.
|
|
282
350
|
{{/cap}}
|
|
283
351
|
|
|
352
|
+
|
|
353
|
+
## `--change-only` — re-run just what an edit obliges, without losing the rest
|
|
354
|
+
|
|
355
|
+
The JSON reporter names its file for the unit + platform + env ONLY, so **any** subset run
|
|
356
|
+
overwrites the full run's results. Re-running 3 cases of a 100-case screen used to throw away the
|
|
357
|
+
97 that still passed, which is why a partial run had to be followed by a full one.
|
|
358
|
+
|
|
359
|
+
Two commands close that, and the order matters — the backup must exist BEFORE the run, because by
|
|
360
|
+
the time the run finishes the old file is already gone:
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
# 1. what does the edit oblige? (empty output = nothing to run, do NOT fall through to a full run)
|
|
364
|
+
npx sungen changed --screen <name> # human-readable, with the reason per scenario
|
|
365
|
+
GREP=$(npx sungen changed --screen <name> --grep)
|
|
366
|
+
|
|
367
|
+
# 2. keep the previous results BEFORE the runner overwrites them
|
|
368
|
+
cp specs/generated/<name>/<name>-test-result.<os>.json /tmp/<name>.prev.json
|
|
369
|
+
|
|
370
|
+
# 3. run only the selected scenarios
|
|
371
|
+
SUNGEN_SPECS='./specs/generated/<name>/<name>.spec.ts' npm run test:mobile -- --mochaOpts.grep "$GREP"
|
|
372
|
+
# web: npx playwright test specs/generated/<name>/<name>.spec.ts -g "$GREP"
|
|
373
|
+
|
|
374
|
+
# 4. merge the subset back over the kept results (same title → new wins, others kept)
|
|
375
|
+
npx sungen results merge /tmp/<name>.prev.json specs/generated/<name>/<name>-test-result.<os>.json \
|
|
376
|
+
-o specs/generated/<name>/<name>-test-result.<os>.json
|
|
377
|
+
|
|
378
|
+
# 5. record the new baseline — ONLY after a run you accept
|
|
379
|
+
npx sungen changed --screen <name> --record
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
**Selection rules** (deterministic, computed by `sungen changed`): a scenario re-runs when its own
|
|
383
|
+
steps/tags changed, when a selectors.yaml key it references changed, or when a test-data key it
|
|
384
|
+
binds changed. A `Background` edit re-runs everything, because the Background runs before every
|
|
385
|
+
scenario. An edit that cannot be attributed — an unparseable YAML, a file that is not a key
|
|
386
|
+
mapping, or no baseline yet — selects **every** scenario and says so. That bias is deliberate:
|
|
387
|
+
over-running costs minutes, under-running reports a scenario green that never re-ran after the
|
|
388
|
+
change that broke it.
|
|
389
|
+
|
|
390
|
+
**Without `--change-only`** the full run still writes the file wholesale, which is correct — a full
|
|
391
|
+
run IS the complete picture. Merging only matters for subsets.
|
|
392
|
+
|
|
284
393
|
## Next steps
|
|
285
394
|
|
|
286
395
|
After showing results, use `AskUserQuestion` to offer next steps:
|
|
@@ -80,7 +80,7 @@ qa/deliverables/<name>-testcases.xlsx # Styled workbook for client hand-off
|
|
|
80
80
|
|
|
81
81
|
## Test Data
|
|
82
82
|
|
|
83
|
-
`{{variable}}` references in `.feature` map to keys in `test-data/<screen>.yaml`. Data is loaded **at runtime** — the same generated `.spec.ts` works across environments without recompiling.
|
|
83
|
+
`{{variable}}` references in `.feature` map to keys in `test-data/<screen>.yaml`. They belong in slots that take a value (`with {{v}}`, `as {{v}}`, `contains {{v}}`) — never inside a `[Target]`, which is a selector key matched literally. Data is loaded **at runtime** — the same generated `.spec.ts` works across environments without recompiling.
|
|
84
84
|
|
|
85
85
|
**Environment overrides**: `SUNGEN_ENV=staging npx playwright test` merges `<screen>.staging.yaml` on top of `<screen>.yaml`. Create `<screen>.<env>.yaml` for environment-specific values (different credentials, URLs, test users).
|
|
86
86
|
|
|
@@ -80,7 +80,7 @@ qa/deliverables/<name>-testcases.xlsx # Styled workbook for client hand-off
|
|
|
80
80
|
|
|
81
81
|
## Test Data
|
|
82
82
|
|
|
83
|
-
`{{variable}}` references in `.feature` map to keys in `test-data/<screen>.yaml`. Data is loaded **at runtime** — the same generated `.spec.ts` works across environments without recompiling.
|
|
83
|
+
`{{variable}}` references in `.feature` map to keys in `test-data/<screen>.yaml`. They belong in slots that take a value (`with {{v}}`, `as {{v}}`, `contains {{v}}`) — never inside a `[Target]`, which is a selector key matched literally. Data is loaded **at runtime** — the same generated `.spec.ts` works across environments without recompiling.
|
|
84
84
|
|
|
85
85
|
**Environment overrides**: `SUNGEN_ENV=staging npx playwright test` merges `<screen>.staging.yaml` on top of `<screen>.yaml`. Create `<screen>.<env>.yaml` for environment-specific values (different credentials, URLs, test users).
|
|
86
86
|
|
|
@@ -84,6 +84,41 @@ A flow (`create → login → delete`) is a **Functional integration** test, **n
|
|
|
84
84
|
|
|
85
85
|
Fixture path resolves cwd-relative/absolute first, else `qa/fixtures/<path>` (drop sample files there, reference by name from `test-data`). An empty resolved file param omits the part → use for missing-file `@cases` error rows. `files:`/`bodyFile:` are mutually exclusive. A **`GET`/`HEAD` entry may carry no body at all** (no `body:`/`files:`/`bodyFile:`) — HTTP forbids it and the catalog lint refuses it; put the inputs in the path or `query`, or use `POST`. **Automate the upload success case with `@api`** — don't defer it to `@manual`.
|
|
86
86
|
|
|
87
|
+
## XML APIs (`encoding: xml`)
|
|
88
|
+
|
|
89
|
+
For services that accept/return XML (OTA/SOAP-style backends). Works with ANY XML shape — attributes, namespaces, CDATA, nesting.
|
|
90
|
+
|
|
91
|
+
**Send** — the body is a raw string template (YAML block scalar); declared `params:` bind as `:name` tokens:
|
|
92
|
+
```yaml
|
|
93
|
+
stock_check:
|
|
94
|
+
method: POST
|
|
95
|
+
path: /agent-api
|
|
96
|
+
encoding: xml
|
|
97
|
+
params: [agent_code, signature, item_code]
|
|
98
|
+
body: |
|
|
99
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
|
100
|
+
<request>
|
|
101
|
+
<method>StockCheck</method>
|
|
102
|
+
<agent_code>:agent_code</agent_code>
|
|
103
|
+
<signature>:signature</signature>
|
|
104
|
+
<item_code>:item_code</item_code>
|
|
105
|
+
</request>
|
|
106
|
+
```
|
|
107
|
+
Only DECLARED params bind (namespace prefixes like `soapenv:Envelope` are never touched). Bound values are XML-escaped (`& < > " '`) so data can't corrupt the document; wrap a token as `<![CDATA[:param]]>` to bind raw. Content-Type defaults to `application/xml` (override via `headers:`).
|
|
108
|
+
|
|
109
|
+
**Read** — a response whose Content-Type contains `xml` (never `html`) parses to an object for `{{name.body.<path>}}`; a missing/octet-stream Content-Type is sniffed only when the body starts with `<?xml`. Charset comes from the Content-Type, else the XML declaration, else UTF-8 (Shift_JIS responses decode correctly). Mapping:
|
|
110
|
+
|
|
111
|
+
| XML | Bound value |
|
|
112
|
+
|---|---|
|
|
113
|
+
| `<error_code>01000001</error_code>` | `{{r.body.response.error_code}}` = `"01000001"` (always strings — no numeric coercion, leading zeros survive) |
|
|
114
|
+
| `<smok></smok>` / `<smok/>` | `""` |
|
|
115
|
+
| `<shop id="7">` | `{{r.body.response.shop.@_id}}` = `"7"` (attributes keep an `@_` prefix) |
|
|
116
|
+
| `<soapenv:Body>` | key stays `soapenv:Body` (`{{r.body.soapenv:Envelope.soapenv:Body...}}`) |
|
|
117
|
+
| `<memo><![CDATA[x & y]]></memo>` | plain text `x & y`; `&`-style entities decode (the 5 predefined only) |
|
|
118
|
+
| repeated `<item>` | declare `arrayPaths: [r.items.item]` (dot path INCLUDING the root element) — then 1 item and N items are BOTH arrays and `[0]` access never breaks. Without it, one item is an object and two are an array. |
|
|
119
|
+
|
|
120
|
+
Caveats: an element literally named `count`/`first`/`last`/`length` under an array parent is shadowed by the path helper's own accessors; parse failure keeps the raw string body (same as any non-JSON response); the 5 predefined entities decode even inside response CDATA sections (a deliberate deviation from strict XML — assert the decoded form). Field metadata / `matches schema` / OpenAPI import for XML are not supported yet. The project needs the `fast-xml-parser` dev dependency (new projects get it from `sungen init`; older ones: `npm install --save-dev fast-xml-parser` — generate warns when it's missing).
|
|
121
|
+
|
|
87
122
|
## Per-endpoint knobs & auth patterns
|
|
88
123
|
- **Timeout** — a slow endpoint can override the datasource default (15s) with `timeout_ms: 30000` on its catalog entry (else the datasource `timeout_ms` applies).
|
|
89
124
|
- **OAuth2 (pure API, no browser)** — no new grammar; it's `@api` chaining. Call the token endpoint, bind the token, thread it into later calls' `Bearer :token` header:
|
|
@@ -95,6 +130,20 @@ Fixture path resolves cwd-relative/absolute first, else `qa/fixtures/<path>` (dr
|
|
|
95
130
|
```
|
|
96
131
|
The token endpoint is a normal catalog entry (`POST /oauth/token`, form/JSON body); `list_orders` carries `headers: { authorization: "Bearer :token" }`. For a browser-authorized session instead, use `@hybrid` + `@auth:<role>`.
|
|
97
132
|
- **Pagination** — use query params (`?page=:page&limit=:limit`, imported into `params`) + assert the page shape with plain field access (`{{list.body.next_page_token}}`, `{{list.body.items.length}}`); no special primitive needed.
|
|
133
|
+
- **Download an absolute URL from a response** (export/report endpoints returning a `body.url` on blob storage): declare a catalog entry whose `path:` is EXACTLY one token, and thread the URL as its param — this is the only supported syntax:
|
|
134
|
+
```yaml
|
|
135
|
+
download_export:
|
|
136
|
+
method: GET
|
|
137
|
+
path: ":url"
|
|
138
|
+
params: [url]
|
|
139
|
+
```
|
|
140
|
+
```gherkin
|
|
141
|
+
@api:export_report @api:download_export(url={{export_report.body.url}})
|
|
142
|
+
Scenario: VP-API-FLOW exported file is downloadable
|
|
143
|
+
Then expect {{export_report.status}} is 200
|
|
144
|
+
And expect {{download_export.status}} is 200
|
|
145
|
+
```
|
|
146
|
+
The bound value must be an ABSOLUTE http(s) URL — an empty string, a relative path, or any other scheme (`file:`, `data:`, `mailto:`) fails the run loudly, so a broken export returning `{"url": ""}` can never false-pass. Presigned query escapes survive verbatim; a non-JSON body (CSV/text) is kept as text for `expect {{download_export.body}}` assertions. A catalog entry may also declare a literal absolute `path: "https://…"` (its `:params` bind with normal encoding). **Security:** when the REQUEST URL's origin differs from the datasource `base_url`, none of the datasource headers are sent (auth/tenant defaults must not leak to another origin) — a cross-origin download that needs a header must declare it in the entry's own `headers:`. This boundary applies to the initial request only: a same-origin request that REDIRECTS to another origin still carries the datasource headers (fetch auto-strips only `authorization`/`cookie`/`proxy-authorization` cross-origin) — prefer binding the final URL over following an API-origin redirect. The 15s default timeout may be short for large files (`timeout_ms:` on the entry).
|
|
98
147
|
|
|
99
148
|
## Rules
|
|
100
149
|
- **No HTTP, no selectors** — only `.feature` + the reviewed `apis.yaml` + `test-data`.
|
|
@@ -77,6 +77,7 @@ needs any of these, it is a **finding for QA** — surface it in the run summary
|
|
|
77
77
|
|---|---|---|
|
|
78
78
|
| strict mode violation | Multiple elements match | Add `nth: 0`, `exact: true`, or more specific `name` |
|
|
79
79
|
| strict mode violation on `see`/`hover [X] item\|option\|cell with {{v}}` (dynamic list) | Older builds didn't filter the locator by the value | Since 3.2.13 `see`/`hover ... with {{v}}` on list-member roles filters by `hasText` like `click` (existence/hover the matching item) — keep the step data-driven, don't add per-item selectors. Update sungen if still unfiltered |
|
|
80
|
+
| strict mode violation on a filtered list step whose value is a PREFIX of sibling texts (e.g. `1:00` also inside `11:00`) | The `hasText` filter matches by SUBSTRING by default (case-insensitive, like Playwright), so several members match | Add `exact: true` to the selector YAML entry — it anchors the filter to the full text and is honored for `type: locator` entries too |
|
|
80
81
|
| Timeout / not found | Element doesn't exist or name wrong | Re-snapshot → copy exact accessible name. Check iframe/dialog scope |
|
|
81
82
|
| Element is not an input | Wrong element type targeted | Change `type` or `value` to match actual element |
|
|
82
83
|
| 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` |
|
|
@@ -86,7 +87,7 @@ needs any of these, it is a **finding for QA** — surface it in the run summary
|
|
|
86
87
|
|
|
87
88
|
| Code | Meaning | Fix |
|
|
88
89
|
|---|---|---|
|
|
89
|
-
| `SG-W001` | Step matched no pattern —
|
|
90
|
+
| `SG-W001` | Step matched no pattern — sungen has no step form for the sentence. Compiles to `throw new Error("[sungen] …")` naming the step (it emitted a no-op `// TODO` before the unsupported-step gate, which let the scenario PASS asserting nothing) | Rephrase to a documented pattern (`sungen-gherkin-syntax`) or tag `@manual` with a reason. There is no silent fallback — the test goes red until one of those two is done |
|
|
90
91
|
| `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 |
|
|
91
92
|
| `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` |
|
|
92
93
|
| `SG-W005` | `qa/app.yaml` unknown key / unknown preset / unparseable | Fix the key, or `extends: native\|radix\|antd` |
|
|
@@ -95,14 +96,23 @@ needs any of these, it is a **finding for QA** — surface it in the run summary
|
|
|
95
96
|
| `SG-W008` | `@manual` scenario incomplete in either direction: (a) HAS Gherkin steps but NO `Then` — a dangling `When` is invalid Gherkin, comments can NOT substitute; (b) documentation-style (NO steps) but its comments lack numbered action lines and/or a labeled oracle line — prose paragraphs / rationale-only comments export EMPTY Steps + Expected cells | Two valid shapes only: complete the Gherkin with a `Then`, or write the FULL procedure in the `# Tester verifies:` block as NUMBERED lines — action steps first (`1. <action>`), then labeled `Observable:`/`Oracle:`/`Expected:` lines (the number is what fills Steps, the label is what fills Expected results) |
|
|
96
97
|
| `SG-W009` | test-data references `${QA_<NAME>}` (a SECRET env ref) but the name is declared in neither `.env.qa` nor `.env.qa.example` — the test throws "environment variable not set" the moment the key is used. (Only the `${QA_*}` shape is checked; any other `${...}` is literal data and never flagged.) | Add `QA_<NAME>=<value>` to `.env.qa` (gitignored) and list the NAME in `.env.qa.example` (the committed checklist). See `qa/TEST-DATA-GUIDE.md` |
|
|
97
98
|
| `SG-W010` | A row step's `[Ref]` looks POSITIONAL (`First…`/`Last…`/`Top…`/`nth`) but has no selectors.yaml entry — the label is display-only, so the step checks existence ANYWHERE in the table; an ordering/sort-stability rule would pass even when the row moved | Declare the row in selectors.yaml (a `type: locator` value RELATIVE to the table, e.g. `first contact row:` → `value: 'tbody tr:first-child'`) — the step then asserts `toContainText` on that exact row. This is a selectors.yaml-only fix — allowed inside the run-test auto-fix loop |
|
|
98
|
-
| `SG-W011` | A storage assertion written as `expect … in local/session storage` — `expect` steps only read `{{response}}` refs, so this
|
|
99
|
+
| `SG-W011` | A storage assertion written as `expect … in local/session storage` — `expect` steps only read `{{response}}` refs, so this matches no pattern. Compiles to a `throw` (was a silent no-op) | Rewrite using the supported grammar: `User see [KEY] in local storage exists/…` |
|
|
99
100
|
| `SG-W012` | A mock-install step written AFTER a navigation step in the same block — `page.route()` registered after `goto()` misses every request fired during page load | Move the mock-install step before the navigation, or into `Background` |
|
|
100
101
|
| `SG-W013` | A page assertion (`see [X] page` / `is on [X] page`) whose `[Ref]` has no `type: page` selector entry (or collides with a non-page entry) — the step falls back to the feature's own path (or `/<ref>/`) instead of `X`'s real URL, so the anchored assertion can never pass | Declare a `type: page` entry for `[Ref]` with its real URL; if the key collides with another type, disambiguate with a `--type` suffix (`sungen-selector-keys` § Collision rule) |
|
|
101
102
|
| `SG-W014` | `[X] page with {{v}}` where `{{v}}`'s base test-data value carries no query and no fragment — the step checks the PATH only, asserting less than it reads as | Informational — pass a value like `?q=…` if you meant to assert a query, or drop `with {{v}}` for a bare page |
|
|
103
|
+
| `SG-W015` | `see [X] card/item with {{n}}` where `{{n}}`'s test-data value is a **bare number** — this compiles to a TEXT assertion (`toHaveText('3')`), NOT a count. An author reaching for "3 cards are shown" gets an assertion that can only pass if the card's TEXT is literally the digit: a wrong test with no error. Scoped to container-ish element types (`card`/`item`/`option` + plurals); a text element asserting a numeric readout (`[Row Quantity] text with 4`) stays silent | For a COUNT use a form that has count semantics: `see [Ref] row with {{n}}` / `see [T] table with {{n}}`, or a list `should have count`. If the number really is the element's text, assert it on a **text/label** element instead |
|
|
102
104
|
| `SG-E020` | A step matched a pattern, but the **active adapter ships no template** for it (e.g. a web-only step compiled under `platform: mobile`). The feature file still generates — that one step compiles to `throw new Error("[sungen] …")` naming the step, feature, pattern, template and adapter, so the failure is loud and traceable rather than a crashed build | Rephrase to a step the target adapter actually ships (see `sungen-gherkin-syntax` Platform Support section / `sungen-mobile-gestures`), or tag the scenario `@manual` with the platform reason |
|
|
105
|
+
| `SG-E021` | The step carries a trailing **state clause** (`… is hidden`) that the pattern which matched it does not assert — the clause would be SILENTLY DROPPED. Worst shape in this table: it does not weaken an assertion, it can INVERT one. `see [X] page is hidden` matched `page-assertion` (priority 13) ahead of `is-hidden` (11), so the step compiled to `toHaveURL(...)` — asserting the page IS displayed, and passing. Also fires on `wait for [X] is checked` (a wait honours only `hidden`/`visible`/`disabled`/`enabled`; everything else silently fell back to `visible`) and on `row`/`column in [T] table … is <state>` for any state but `hidden` | Move the state onto the element that has it: `Then User see [X] checkbox is checked`. For a page, assert the page you expect (`Then User see [Other] page`) or a marker element (`Then User see [Banner] is hidden`). If sungen cannot express the check, tag the scenario `@manual`. The diagnostic names the pattern that swallowed the clause — that name tells you which form you actually hit |
|
|
103
106
|
| `SG-W020` | The matched pattern **declares `platforms`** (today: `@mock`) and the active platform isn't among them. Caught before template lookup, so the diagnostic can name the native alternative directly | Drop `@mock` from a mobile unit (there is no Mock Driver on Appium) — use `@api`/`@query` or a `@manual` note instead |
|
|
104
107
|
| `SG-W021` | `use dialog` / `User is on [X] dialog` scope under the **mobile** adapter — the scope is recorded but no Appium template reads `inDialog`, so every following step resolves against the whole screen, not just the dialog | Don't rely on `scope: dialog`/`use dialog` for disambiguation on mobile — give the element inside the dialog its own unique accessibility-id/testid instead |
|
|
105
108
|
| `SG-W022` | `Then User see [X] page` under the **mobile** adapter — a native app has no URL, so the step compiles to a bare COMMENT: it reads as an assertion, checks nothing, and the scenario passes whatever is on screen. Worse than a hard failure, because nothing ever goes red. (Its `is on [X] page` twin throws via `route-assertion`; `Given User is on [X] page` is the app-LAUNCH directive and correctly emits nothing) | Assert something actually on the screen — `Then User see [Some Header] text` / a screen-marker accessibility-id — instead of a page/URL check, or tag the scenario `@manual` |
|
|
109
|
+
| `SG-W023` | A mobile **setup** step that emitted NO executable code, so a declared PRECONDITION is never established. `Given the user is logged out` matches `user-not-logged-in` → the `clear-auth` template, which on Appium is a bare COMMENT: mobile auth lives in the keychain/prefs, not in cookies, so no per-step action can clear it. The scenario then runs against whatever the PREVIOUS scenario left signed in and passes regardless — the same false-pass class as `SG-W022`, arriving through setup instead of assertion. Deliberately NOT a `throw` (an unestablished precondition weakens setup, it does not INVERT an assertion) | Declare a feature-level reset tier — `@reset:data` on the Feature — so each scenario starts from a wiped data layer; app state is reset in the `beforeEach` hook, not mid-scenario by a step. For BACKEND state (`database is empty`) the remedy is different: reset through a test API/script, or bind a `@query:` precondition, the same way the web adapter has to |
|
|
110
|
+
| `SG-W024` | Two or more scenarios in one feature **share a name** and at least ONE carries a `@reset:` tag. The generated per-scenario tier lookup is keyed on the scenario NAME (a `@cases` title is built at runtime, so no compile-time key can match the rendered title), so every same-named scenario resolves to the SAME tier — including one that declared none, which then silently runs at its namesake's tier: a `@reset:none` chain broken, or a reinstall nobody asked for. Counted over EVERY scenario rather than the tagged subset, because the untagged twin is the one that gets the wrong tier and has no tag to be counted by | **Rename** the scenarios so each title is unique. Reported rather than resolved by precedence on purpose — two tests genuinely sharing one title cannot be told apart by a name-keyed lookup, so a rename is the only honest fix (silently picking a winner would be the same silent-discard shape as `SG-W021`) |
|
|
111
|
+
| `SG-E022` | The **generated spec file itself** is not parseable JS/TS, so NOTHING in it runs — Playwright reports one `SyntaxError` and every scenario in the unit is lost. A compiler self-check (`@babel/parser`) on the emitted code, added after a field report where the runtime-data marker post-processor mis-paired quote delimiters across two literals and the user only found out at `npx playwright test`. Reported, never thrown — the file is still written so you keep the artifact to inspect. **Unlike every other code in this table this one blames the COMPILER, not your `.feature`** | Nothing to fix in the Gherkin — this should be impossible. **Report it** with the `.feature` (and the step) the file was generated from, attaching the unparseable `.spec.ts`. Check first that you are running the project-local compiler (`npx sungen`), not an older global one |
|
|
112
|
+
| `SG-E023` | The step carries an **assertion clause** (`matches "…"`, `contains`, `has text`) but the generated code is a BARE VISIBILITY check — the clause was swallowed by a catch-all and the test would pass whatever the element says. The sibling of `SG-E021`, for the clauses that are not states: `SG-E021` closed the state case declaratively, but a dropped `matches`/`contains`/`has text` still slid through (reproduced live: a `matches "<format>"` step compiled by an older sungen lost the assertion entirely, emitted no warning, and left a `toBeVisible()` that passed whatever the label said). Four separate defects arrived this way, so the step now compiles to a runtime `throw`. Deliberately narrow — fires only on the bare-visibility shape, so a pattern that genuinely consumed the clause (`is-hidden`, `contains`, `matches-format`, the state family, or a `filter({ hasText })` on a list member) is untouched | Either the phrasing is not the supported one for that clause — check the exact form in `sungen-gherkin-syntax` — or this sungen predates the step: verify you are running the project-local compiler (`npx sungen`), not an older global one |
|
|
113
|
+
| `SG-E024` | A mobile **app-lifecycle** step (`User restart the app` / `User reinstall the app`) in a feature that declares no `Path:` app id. The lifecycle templates resolve the target app through `__resolveAppId()`, a helper the appium test-file template emits ONLY when the id is present, so the spec used to compile clean and then die at runtime with `ReferenceError: __resolveAppId is not defined` — taking the WHOLE spec down, not the one step, because the failure lands in a before hook. (Field case: a mobile unit scaffolded with the nav recipe in `--path` instead of `--reach`, so `Path` never carried an app id.) Now refused at compile time as a `throw` naming the missing line | Add the app id under the `Feature` line: `Path: com.example.app/.MainActivity \| com.example.app`. For a mobile unit `--path` carries the **APP ID** and `--reach` carries the navigation recipe — if the nav recipe ended up in `--path`, that is the mistake |
|
|
114
|
+
|
|
115
|
+
A diagnostic never changes generation's own exit code — `sungen generate --strict` turns any collected diagnostic into a non-zero exit for CI gates. They are not all inert, though: **`SG-W001`, `SG-W011`, `SG-E020`, `SG-E021`, `SG-W020`, `SG-E023` and `SG-E024` additionally compile the offending step to a runtime `throw`** — a step that reads as a check and silently checks nothing is worse than one that fails loudly. Generation always COMPLETES: the rest of the scenario and the rest of the file still compile. `SG-E022` is the odd one out — it blames the compiler, not your `.feature`, and throws nothing (the file is written so you can inspect it).
|
|
106
116
|
|
|
107
117
|
### Runtime error → `Test data "<key>" references ${QA_*} but the environment variable is not set`
|
|
108
118
|
|
|
@@ -123,7 +133,8 @@ The scenario uses a SECRET test-data key whose `QA_*` variable is missing in thi
|
|
|
123
133
|
### Assertion type rule
|
|
124
134
|
|
|
125
135
|
Sungen picks assertion based on element type:
|
|
126
|
-
- **Input** (`field`, `textarea`, `search`, `
|
|
136
|
+
- **Input** (`field`, `textarea`, `search`, `slider`) → `toHaveValue()`
|
|
137
|
+
- **Dropdown** (`dropdown`, `select`, `combobox`) → runtime tag probe: `toHaveValue()` on a native `<select>`/input, `toHaveText()` on a `<button role="combobox">` trigger (Radix/shadcn, MUI). Pin one branch with `variant: native` or an `assertValue` recipe in `qa/app.yaml`.
|
|
127
138
|
- **Text** (everything else: `message`, `heading`, `label`, `row`) → `toHaveText()`
|
|
128
139
|
- **Partial** (`contains` keyword) → `toContainText()`
|
|
129
140
|
|