@sun-asterisk/sungen 3.2.32 → 3.2.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/audit.d.ts +5 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +26 -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 +35 -17
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/changed.d.ts.map +1 -1
- package/dist/cli/commands/changed.js +2 -3
- package/dist/cli/commands/changed.js.map +1 -1
- package/dist/cli/commands/dashboard.d.ts.map +1 -1
- package/dist/cli/commands/dashboard.js +2 -28
- package/dist/cli/commands/dashboard.js.map +1 -1
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +79 -53
- 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 +14 -7
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/results-merge.d.ts +9 -0
- package/dist/cli/commands/results-merge.d.ts.map +1 -1
- package/dist/cli/commands/results-merge.js +9 -12
- package/dist/cli/commands/results-merge.js.map +1 -1
- package/dist/cli/commands/results-sanitize.d.ts.map +1 -1
- package/dist/cli/commands/results-sanitize.js +2 -13
- package/dist/cli/commands/results-sanitize.js.map +1 -1
- package/dist/cli/commands/select.d.ts +12 -0
- package/dist/cli/commands/select.d.ts.map +1 -0
- package/dist/cli/commands/select.js +122 -0
- package/dist/cli/commands/select.js.map +1 -0
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +3 -0
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/index.js +7 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
- package/dist/dashboard/snapshot-builder.js +6 -10
- package/dist/dashboard/snapshot-builder.js.map +1 -1
- package/dist/dashboard/templates/index.html +1 -1
- package/dist/dashboard/types.d.ts +1 -0
- package/dist/dashboard/types.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.js +11 -10
- package/dist/exporters/csv-exporter.js.map +1 -1
- package/dist/exporters/feature-parser.d.ts +21 -0
- package/dist/exporters/feature-parser.d.ts.map +1 -1
- package/dist/exporters/feature-parser.js +32 -1
- package/dist/exporters/feature-parser.js.map +1 -1
- package/dist/exporters/json-exporter.d.ts.map +1 -1
- package/dist/exporters/json-exporter.js +7 -0
- package/dist/exporters/json-exporter.js.map +1 -1
- package/dist/exporters/locale-variants.d.ts +7 -1
- package/dist/exporters/locale-variants.d.ts.map +1 -1
- package/dist/exporters/locale-variants.js +23 -16
- package/dist/exporters/locale-variants.js.map +1 -1
- package/dist/exporters/manual-hybrid-oracle.d.ts +54 -0
- package/dist/exporters/manual-hybrid-oracle.d.ts.map +1 -0
- package/dist/exporters/manual-hybrid-oracle.js +114 -0
- package/dist/exporters/manual-hybrid-oracle.js.map +1 -0
- package/dist/exporters/matrix/build.d.ts.map +1 -1
- package/dist/exporters/matrix/build.js +19 -9
- package/dist/exporters/matrix/build.js.map +1 -1
- package/dist/exporters/matrix/carry-manual-results.d.ts +70 -0
- package/dist/exporters/matrix/carry-manual-results.d.ts.map +1 -0
- package/dist/exporters/matrix/carry-manual-results.js +232 -0
- package/dist/exporters/matrix/carry-manual-results.js.map +1 -0
- package/dist/exporters/matrix/export.d.ts +5 -0
- package/dist/exporters/matrix/export.d.ts.map +1 -1
- package/dist/exporters/matrix/export.js +8 -1
- package/dist/exporters/matrix/export.js.map +1 -1
- package/dist/exporters/matrix/gates.js +6 -0
- package/dist/exporters/matrix/gates.js.map +1 -1
- package/dist/exporters/matrix/render-testcase-sheet.d.ts.map +1 -1
- package/dist/exporters/matrix/render-testcase-sheet.js +10 -4
- package/dist/exporters/matrix/render-testcase-sheet.js.map +1 -1
- package/dist/exporters/matrix/types.d.ts +8 -1
- package/dist/exporters/matrix/types.d.ts.map +1 -1
- package/dist/exporters/matrix/types.js.map +1 -1
- package/dist/exporters/matrix/wording.d.ts +8 -1
- package/dist/exporters/matrix/wording.d.ts.map +1 -1
- package/dist/exporters/matrix/wording.js +37 -2
- package/dist/exporters/matrix/wording.js.map +1 -1
- package/dist/exporters/run-environment.d.ts +32 -0
- package/dist/exporters/run-environment.d.ts.map +1 -0
- package/dist/exporters/run-environment.js +125 -0
- package/dist/exporters/run-environment.js.map +1 -0
- package/dist/exporters/scenario-merger.d.ts +18 -0
- package/dist/exporters/scenario-merger.d.ts.map +1 -1
- package/dist/exporters/scenario-merger.js +47 -3
- package/dist/exporters/scenario-merger.js.map +1 -1
- package/dist/exporters/test-data-resolver.d.ts +0 -4
- package/dist/exporters/test-data-resolver.d.ts.map +1 -1
- package/dist/exporters/test-data-resolver.js +0 -7
- package/dist/exporters/test-data-resolver.js.map +1 -1
- package/dist/exporters/xlsx-exporter.d.ts +2 -0
- package/dist/exporters/xlsx-exporter.d.ts.map +1 -1
- package/dist/exporters/xlsx-exporter.js +7 -2
- package/dist/exporters/xlsx-exporter.js.map +1 -1
- package/dist/generators/gherkin-parser/index.d.ts +11 -4
- package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
- package/dist/generators/gherkin-parser/index.js +49 -19
- package/dist/generators/gherkin-parser/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts +4 -0
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/reinstall-app-action.hbs +2 -2
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +8 -2
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-with-role-variable-assertion.hbs +7 -3
- package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +68 -4
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/click-select-action.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/label-value-assertion.hbs +7 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +8 -4
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/pick-option.hbs +13 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +5 -1
- package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +16 -0
- package/dist/generators/test-generator/code-generator.d.ts +2 -1
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +90 -5
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/diagnostics.d.ts +36 -5
- package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
- package/dist/generators/test-generator/diagnostics.js +35 -4
- package/dist/generators/test-generator/diagnostics.js.map +1 -1
- package/dist/generators/test-generator/patterns/index.d.ts +12 -2
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +15 -9
- package/dist/generators/test-generator/patterns/index.js.map +1 -1
- package/dist/generators/test-generator/patterns/types.d.ts +15 -0
- package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/with-parent-scope.d.ts +5 -0
- package/dist/generators/test-generator/patterns/with-parent-scope.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/with-parent-scope.js +6 -0
- package/dist/generators/test-generator/patterns/with-parent-scope.js.map +1 -1
- package/dist/generators/test-generator/step-mapper.d.ts +46 -0
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +84 -9
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.d.ts +25 -0
- package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.js +39 -8
- package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-entry-fields.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-entry-fields.js +1 -0
- package/dist/generators/test-generator/utils/selector-entry-fields.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts +37 -4
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +212 -79
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/harness/audit.d.ts +3 -0
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +29 -12
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/capability.d.ts +7 -1
- package/dist/harness/capability.d.ts.map +1 -1
- package/dist/harness/capability.js +15 -12
- package/dist/harness/capability.js.map +1 -1
- package/dist/harness/catalog/universal-viewpoints.yaml +5 -5
- package/dist/harness/config-file.d.ts +17 -0
- package/dist/harness/config-file.d.ts.map +1 -0
- package/dist/harness/config-file.js +100 -0
- package/dist/harness/config-file.js.map +1 -0
- package/dist/harness/data-driven-lint.d.ts.map +1 -1
- package/dist/harness/data-driven-lint.js +69 -27
- package/dist/harness/data-driven-lint.js.map +1 -1
- package/dist/harness/depth-lint.d.ts.map +1 -1
- package/dist/harness/depth-lint.js +2 -1
- package/dist/harness/depth-lint.js.map +1 -1
- package/dist/harness/flow-check.d.ts.map +1 -1
- package/dist/harness/flow-check.js +6 -2
- package/dist/harness/flow-check.js.map +1 -1
- package/dist/harness/keyword-match.d.ts +59 -0
- package/dist/harness/keyword-match.d.ts.map +1 -0
- package/dist/harness/keyword-match.js +86 -0
- package/dist/harness/keyword-match.js.map +1 -0
- package/dist/harness/next-step.d.ts.map +1 -1
- package/dist/harness/next-step.js +1 -0
- package/dist/harness/next-step.js.map +1 -1
- package/dist/harness/parse.d.ts +24 -0
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +117 -13
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/perf.d.ts +7 -1
- package/dist/harness/perf.d.ts.map +1 -1
- package/dist/harness/perf.js +13 -10
- package/dist/harness/perf.js.map +1 -1
- package/dist/harness/quality-gates.d.ts +9 -2
- package/dist/harness/quality-gates.d.ts.map +1 -1
- package/dist/harness/quality-gates.js +22 -8
- package/dist/harness/quality-gates.js.map +1 -1
- package/dist/harness/query-catalog.js +0 -0
- package/dist/harness/query-catalog.js.map +1 -1
- package/dist/harness/read-text.d.ts +21 -0
- package/dist/harness/read-text.d.ts.map +1 -1
- package/dist/harness/read-text.js +32 -1
- package/dist/harness/read-text.js.map +1 -1
- package/dist/harness/secret-scan.d.ts +27 -1
- package/dist/harness/secret-scan.d.ts.map +1 -1
- package/dist/harness/secret-scan.js +57 -4
- package/dist/harness/secret-scan.js.map +1 -1
- package/dist/harness/select-by-ids.d.ts +31 -0
- package/dist/harness/select-by-ids.d.ts.map +1 -0
- package/dist/harness/select-by-ids.js +106 -0
- package/dist/harness/select-by-ids.js.map +1 -0
- package/dist/harness/sensors.d.ts +20 -1
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +102 -27
- package/dist/harness/sensors.js.map +1 -1
- package/dist/harness/spec-branches.d.ts +0 -18
- package/dist/harness/spec-branches.d.ts.map +1 -1
- package/dist/harness/spec-branches.js +24 -3
- package/dist/harness/spec-branches.js.map +1 -1
- package/dist/harness/spec-coverage.d.ts.map +1 -1
- package/dist/harness/spec-coverage.js +14 -4
- package/dist/harness/spec-coverage.js.map +1 -1
- package/dist/harness/ui-inventory.d.ts +40 -0
- package/dist/harness/ui-inventory.d.ts.map +1 -0
- package/dist/harness/ui-inventory.js +191 -0
- package/dist/harness/ui-inventory.js.map +1 -0
- package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
- package/dist/harness/viewpoint-ledger.js +66 -10
- package/dist/harness/viewpoint-ledger.js.map +1 -1
- package/dist/harness/write-atomic.d.ts +2 -0
- package/dist/harness/write-atomic.d.ts.map +1 -0
- package/dist/harness/write-atomic.js +62 -0
- package/dist/harness/write-atomic.js.map +1 -0
- package/dist/orchestrator/ai-skills/writer.d.ts.map +1 -1
- package/dist/orchestrator/ai-skills/writer.js +15 -7
- package/dist/orchestrator/ai-skills/writer.js.map +1 -1
- package/dist/orchestrator/assets-drift.d.ts.map +1 -1
- package/dist/orchestrator/assets-drift.js +11 -6
- package/dist/orchestrator/assets-drift.js.map +1 -1
- package/dist/orchestrator/project-deps-sync.d.ts.map +1 -1
- package/dist/orchestrator/project-deps-sync.js +22 -1
- package/dist/orchestrator/project-deps-sync.js.map +1 -1
- package/dist/orchestrator/project-initializer.d.ts +8 -0
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +49 -7
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/screen-manager.d.ts.map +1 -1
- package/dist/orchestrator/screen-manager.js +3 -11
- package/dist/orchestrator/screen-manager.js.map +1 -1
- package/dist/orchestrator/security-overrides-catalog.d.ts +39 -0
- package/dist/orchestrator/security-overrides-catalog.d.ts.map +1 -0
- package/dist/orchestrator/security-overrides-catalog.js +63 -0
- package/dist/orchestrator/security-overrides-catalog.js.map +1 -0
- package/dist/orchestrator/security-overrides.d.ts +51 -0
- package/dist/orchestrator/security-overrides.d.ts.map +1 -0
- package/dist/orchestrator/security-overrides.js +165 -0
- package/dist/orchestrator/security-overrides.js.map +1 -0
- package/dist/orchestrator/templates/ai-src/agents/discovery.md +11 -3
- package/dist/orchestrator/templates/ai-src/agents/reviewer.md +8 -0
- package/dist/orchestrator/templates/ai-src/commands/create-test.md +2 -2
- package/dist/orchestrator/templates/ai-src/commands/review.md +2 -2
- package/dist/orchestrator/templates/ai-src/commands/run-test.md +70 -13
- package/dist/orchestrator/templates/ai-src/skills/sungen-capture/mode-local.md +2 -2
- package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +23 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +26 -8
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +58 -14
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +13 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +41 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +7 -0
- package/dist/orchestrator/templates/playwright.config.ts +4 -2
- package/dist/orchestrator/templates/specs-locale-fixture.ts +21 -9
- package/dist/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
- package/dist/orchestrator/templates/specs-url-assert.d.ts +17 -1
- package/dist/orchestrator/templates/specs-url-assert.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-url-assert.js +38 -4
- package/dist/orchestrator/templates/specs-url-assert.js.map +1 -1
- package/dist/orchestrator/templates/specs-url-assert.ts +47 -4
- package/dist/orchestrator/templates/wdio.conf.ts +24 -1
- package/dist/orchestrator/trace-mode-install.d.ts +14 -0
- package/dist/orchestrator/trace-mode-install.d.ts.map +1 -0
- package/dist/orchestrator/trace-mode-install.js +132 -0
- package/dist/orchestrator/trace-mode-install.js.map +1 -0
- package/dist/orchestrator/viewpoint-pattern-catalog.d.ts +20 -0
- package/dist/orchestrator/viewpoint-pattern-catalog.d.ts.map +1 -0
- package/dist/orchestrator/viewpoint-pattern-catalog.js +45 -0
- package/dist/orchestrator/viewpoint-pattern-catalog.js.map +1 -0
- package/package.json +3 -3
- package/src/cli/commands/audit.ts +24 -5
- package/src/cli/commands/capability.ts +51 -18
- package/src/cli/commands/changed.ts +2 -4
- package/src/cli/commands/dashboard.ts +1 -26
- package/src/cli/commands/delivery.ts +75 -48
- package/src/cli/commands/generate.ts +15 -7
- package/src/cli/commands/results-merge.ts +9 -9
- package/src/cli/commands/results-sanitize.ts +2 -9
- package/src/cli/commands/select.ts +88 -0
- package/src/cli/commands/update.ts +3 -0
- package/src/cli/index.ts +7 -2
- package/src/dashboard/snapshot-builder.ts +6 -8
- package/src/dashboard/templates/index.html +1 -1
- package/src/dashboard/types.ts +1 -0
- package/src/exporters/csv-exporter.ts +7 -10
- package/src/exporters/feature-parser.ts +32 -2
- package/src/exporters/json-exporter.ts +6 -1
- package/src/exporters/locale-variants.ts +23 -17
- package/src/exporters/manual-hybrid-oracle.ts +112 -0
- package/src/exporters/matrix/build.ts +20 -10
- package/src/exporters/matrix/carry-manual-results.ts +229 -0
- package/src/exporters/matrix/export.ts +14 -4
- package/src/exporters/matrix/gates.ts +6 -0
- package/src/exporters/matrix/render-testcase-sheet.ts +10 -4
- package/src/exporters/matrix/types.ts +8 -1
- package/src/exporters/matrix/wording.ts +36 -2
- package/src/exporters/run-environment.ts +89 -0
- package/src/exporters/scenario-merger.ts +49 -4
- package/src/exporters/test-data-resolver.ts +0 -6
- package/src/exporters/xlsx-exporter.ts +7 -2
- package/src/generators/gherkin-parser/index.ts +50 -18
- package/src/generators/test-generator/adapters/adapter-interface.ts +6 -2
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/reinstall-app-action.hbs +2 -2
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +8 -2
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-with-role-variable-assertion.hbs +7 -3
- package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +68 -4
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/click-select-action.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/label-value-assertion.hbs +7 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +8 -4
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/pick-option.hbs +13 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +5 -1
- package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +16 -0
- package/src/generators/test-generator/code-generator.ts +77 -5
- package/src/generators/test-generator/diagnostics.ts +36 -5
- package/src/generators/test-generator/patterns/index.ts +15 -8
- package/src/generators/test-generator/patterns/types.ts +15 -0
- package/src/generators/test-generator/patterns/with-parent-scope.ts +6 -0
- package/src/generators/test-generator/step-mapper.ts +90 -8
- package/src/generators/test-generator/utils/data-resolver.ts +41 -12
- package/src/generators/test-generator/utils/selector-entry-fields.ts +1 -0
- package/src/generators/test-generator/utils/selector-resolver.ts +238 -82
- package/src/harness/audit.ts +37 -16
- package/src/harness/capability.ts +23 -12
- package/src/harness/catalog/universal-viewpoints.yaml +5 -5
- package/src/harness/config-file.ts +66 -0
- package/src/harness/data-driven-lint.ts +82 -24
- package/src/harness/depth-lint.ts +2 -1
- package/src/harness/flow-check.ts +6 -2
- package/src/harness/keyword-match.ts +85 -0
- package/src/harness/next-step.ts +1 -0
- package/src/harness/parse.ts +123 -16
- package/src/harness/perf.ts +15 -7
- package/src/harness/quality-gates.ts +22 -8
- package/src/harness/query-catalog.ts +0 -0
- package/src/harness/read-text.ts +32 -1
- package/src/harness/secret-scan.ts +60 -4
- package/src/harness/select-by-ids.ts +83 -0
- package/src/harness/sensors.ts +109 -28
- package/src/harness/spec-branches.ts +6 -3
- package/src/harness/spec-coverage.ts +14 -4
- package/src/harness/ui-inventory.ts +164 -0
- package/src/harness/viewpoint-ledger.ts +59 -12
- package/src/harness/write-atomic.ts +22 -0
- package/src/orchestrator/ai-skills/writer.ts +18 -3
- package/src/orchestrator/assets-drift.ts +10 -5
- package/src/orchestrator/project-deps-sync.ts +23 -1
- package/src/orchestrator/project-initializer.ts +54 -6
- package/src/orchestrator/screen-manager.ts +3 -11
- package/src/orchestrator/security-overrides-catalog.ts +69 -0
- package/src/orchestrator/security-overrides.ts +149 -0
- package/src/orchestrator/templates/ai-src/agents/discovery.md +11 -3
- package/src/orchestrator/templates/ai-src/agents/reviewer.md +8 -0
- package/src/orchestrator/templates/ai-src/commands/create-test.md +2 -2
- package/src/orchestrator/templates/ai-src/commands/review.md +2 -2
- package/src/orchestrator/templates/ai-src/commands/run-test.md +70 -13
- package/src/orchestrator/templates/ai-src/skills/sungen-capture/mode-local.md +2 -2
- package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +23 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +26 -8
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +58 -14
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +13 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +41 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +7 -0
- package/src/orchestrator/templates/playwright.config.ts +4 -2
- package/src/orchestrator/templates/specs-locale-fixture.ts +21 -9
- package/src/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
- package/src/orchestrator/templates/specs-url-assert.ts +47 -4
- package/src/orchestrator/templates/wdio.conf.ts +24 -1
- package/src/orchestrator/trace-mode-install.ts +94 -0
- package/src/orchestrator/viewpoint-pattern-catalog.ts +58 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Carry a tester's hand-entered Manual results across a `sungen delivery` re-run (#756).
|
|
3
|
+
*
|
|
4
|
+
* The matrix workbook is rebuilt from scratch on every run, and a manual variant has no
|
|
5
|
+
* run-report entry, so its Result cell renders `Pending` and M–P render blank. Without
|
|
6
|
+
* this step the re-run overwrote the file and the tester's record was gone.
|
|
7
|
+
*
|
|
8
|
+
* Before the new workbook is written, the existing file's Manual sheet is read, and each
|
|
9
|
+
* variant sub-row's L–P cells (Test Result, Executed Date, Test Executor, Test Environment,
|
|
10
|
+
* Note) are carried into the new workbook by `v.ref` — column A.
|
|
11
|
+
*
|
|
12
|
+
* A result is only true of the procedure it was executed against, so every write stamps
|
|
13
|
+
* `ref → fingerprint` into a veryHidden sheet. The next run carries a result only while the
|
|
14
|
+
* stamp still matches; a changed procedure resets it to `Pending` and keeps the previous
|
|
15
|
+
* values in Note. Spec: docs/spec/delivery-coverage-matrix-spec.md §8.4.
|
|
16
|
+
*
|
|
17
|
+
* Manual sheet only: on Auto the run report is the source of truth for Result.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import * as fs from 'fs';
|
|
21
|
+
import * as ExcelJS from 'exceljs';
|
|
22
|
+
import { MatrixModel } from './types';
|
|
23
|
+
import { HEADER_ROW, RESULT_COL } from './render-testcase-sheet';
|
|
24
|
+
|
|
25
|
+
const MANUAL_SHEET = 'Manual';
|
|
26
|
+
/** veryHidden: Excel's UI cannot unhide it, so a tester cannot edit the guard by accident. */
|
|
27
|
+
export const STAMP_SHEET = '_sungen_manual';
|
|
28
|
+
/** L..P — the tester-owned columns of a variant sub-row. */
|
|
29
|
+
const CARRIED_COLS = [RESULT_COL, RESULT_COL + 1, RESULT_COL + 2, RESULT_COL + 3, RESULT_COL + 4];
|
|
30
|
+
const NOTE_INDEX = 4;
|
|
31
|
+
const RESET_MARKER = '[sungen] Procedure changed since this result was recorded';
|
|
32
|
+
|
|
33
|
+
interface CarriedCell { value: ExcelJS.CellValue; numFmt?: string }
|
|
34
|
+
|
|
35
|
+
interface PriorRow { cells: CarriedCell[] }
|
|
36
|
+
|
|
37
|
+
export interface PriorManualResults {
|
|
38
|
+
rows: Map<string, PriorRow>;
|
|
39
|
+
/** Later copies of a ref already read — only the first is carried, so these are reported. */
|
|
40
|
+
duplicates: { ref: string; result: string }[];
|
|
41
|
+
/** `ref → fingerprint` from the stamp sheet; null when the file predates #756. */
|
|
42
|
+
stamps: Map<string, string> | null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ManualCarryReport {
|
|
46
|
+
/** Carried with a matching fingerprint. */
|
|
47
|
+
carried: number;
|
|
48
|
+
/** Carried from a file with no stamp — no drift check was possible. */
|
|
49
|
+
unchecked: number;
|
|
50
|
+
/** Result reset to Pending because the procedure changed. */
|
|
51
|
+
reset: string[];
|
|
52
|
+
/** Prior rows with an entry that no longer match any manual variant. */
|
|
53
|
+
notPlaced: { ref: string; result: string }[];
|
|
54
|
+
/** Prior rows with an entry that repeat a ref above them — not carried. */
|
|
55
|
+
duplicates: { ref: string; result: string }[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isFormula(v: ExcelJS.CellValue): boolean {
|
|
59
|
+
return typeof v === 'object' && v !== null && ('formula' in v || 'sharedFormula' in v);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function cellText(v: ExcelJS.CellValue): string {
|
|
63
|
+
if (v === null || v === undefined) return '';
|
|
64
|
+
if (v instanceof Date) return isNaN(v.getTime()) ? '' : v.toISOString().slice(0, 10);
|
|
65
|
+
if (typeof v === 'object') {
|
|
66
|
+
if ('richText' in v) return v.richText.map((r) => r.text).join('').trim();
|
|
67
|
+
if ('text' in v) return String(v.text).trim();
|
|
68
|
+
if ('result' in v) return cellText(v.result as ExcelJS.CellValue);
|
|
69
|
+
}
|
|
70
|
+
return String(v).trim();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* ExcelJS reads a formula's text result under a date format as an Invalid Date. Drop that
|
|
75
|
+
* cached result — Excel recomputes it on open — instead of writing NaN back into the file.
|
|
76
|
+
*/
|
|
77
|
+
function readable(v: ExcelJS.CellValue): ExcelJS.CellValue {
|
|
78
|
+
if (isFormula(v)) {
|
|
79
|
+
const { result, ...formula } = v as ExcelJS.CellFormulaValue;
|
|
80
|
+
if (result instanceof Date && isNaN(result.getTime())) return formula as ExcelJS.CellValue;
|
|
81
|
+
}
|
|
82
|
+
return v;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** A row still at the rendered default carries nothing. */
|
|
86
|
+
function isUntouched(cells: CarriedCell[]): boolean {
|
|
87
|
+
const [result, ...rest] = cells.map((c) => cellText(c.value));
|
|
88
|
+
return (result === '' || result === 'Pending') && rest.every((t) => t === '');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Variant sub-rows of a Manual sheet, in row order: `[ref, row]`. Either signal is enough:
|
|
93
|
+
* the renderer puts variants on outline level 1, and a parent's Result is a roll-up formula.
|
|
94
|
+
* Outline levels are lost by some spreadsheet tools on save, and a tester can paste a
|
|
95
|
+
* formula into a variant's Result, so relying on one alone would hide the row silently.
|
|
96
|
+
*/
|
|
97
|
+
function variantRows(ws: ExcelJS.Worksheet): [string, ExcelJS.Row][] {
|
|
98
|
+
const out: [string, ExcelJS.Row][] = [];
|
|
99
|
+
for (let r = HEADER_ROW + 1; r <= ws.rowCount; r++) {
|
|
100
|
+
const row = ws.getRow(r);
|
|
101
|
+
const ref = cellText(row.getCell(1).value);
|
|
102
|
+
if (ref && (row.outlineLevel === 1 || !isFormula(row.getCell(RESULT_COL).value))) out.push([ref, row]);
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Read the Manual results of the workbook about to be overwritten. Returns null when there is
|
|
109
|
+
* no such file. A file that exists but cannot be read THROWS: overwriting it would destroy the
|
|
110
|
+
* very record this step exists to keep.
|
|
111
|
+
*/
|
|
112
|
+
export async function readPriorManualResults(file: string): Promise<PriorManualResults | null> {
|
|
113
|
+
if (!fs.existsSync(file)) return null;
|
|
114
|
+
const wb = new ExcelJS.Workbook();
|
|
115
|
+
try {
|
|
116
|
+
await wb.xlsx.readFile(file);
|
|
117
|
+
} catch (e) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`cannot read ${file} to carry its Manual results (${(e as Error).message}). ` +
|
|
120
|
+
'Close it in Excel, or move it aside to start a fresh workbook, then re-run.',
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Every workbook sungen writes has a Manual sheet, even an empty one. Without it the
|
|
125
|
+
// tester's results cannot be located — carrying nothing would look like a clean run.
|
|
126
|
+
const ws = wb.getWorksheet(MANUAL_SHEET);
|
|
127
|
+
if (!ws) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`${file} has no "${MANUAL_SHEET}" sheet, so its Manual results cannot be carried. ` +
|
|
130
|
+
`Rename the sheet back to "${MANUAL_SHEET}", or move the file aside to start a fresh workbook, then re-run.`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const rows = new Map<string, PriorRow>();
|
|
135
|
+
const duplicates: PriorManualResults['duplicates'] = [];
|
|
136
|
+
for (const [ref, row] of variantRows(ws)) {
|
|
137
|
+
const cells = CARRIED_COLS.map((c) => {
|
|
138
|
+
const cell = row.getCell(c);
|
|
139
|
+
return { value: readable(cell.value), ...(cell.numFmt ? { numFmt: cell.numFmt } : {}) };
|
|
140
|
+
});
|
|
141
|
+
if (!rows.has(ref)) rows.set(ref, { cells });
|
|
142
|
+
else if (!isUntouched(cells)) duplicates.push({ ref, result: cellText(cells[0].value) || '(no result)' });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const stampWs = wb.getWorksheet(STAMP_SHEET);
|
|
146
|
+
let stamps: Map<string, string> | null = null;
|
|
147
|
+
if (stampWs) {
|
|
148
|
+
stamps = new Map();
|
|
149
|
+
stampWs.eachRow((row, n) => {
|
|
150
|
+
if (n > 1) stamps!.set(cellText(row.getCell(1).value), cellText(row.getCell(2).value));
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return { rows, duplicates, stamps };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Note for a reset row: what was invalidated, then whatever the tester had written. A note
|
|
158
|
+
* that is a hyperlink (evidence, a defect ticket) stays one — only its text gains the marker.
|
|
159
|
+
*/
|
|
160
|
+
function resetNote(prior: CarriedCell[]): ExcelJS.CellValue {
|
|
161
|
+
const [result, date, executor] = prior.map((c) => cellText(c.value));
|
|
162
|
+
const was = [result, date, executor].filter(Boolean).join(' · ');
|
|
163
|
+
const header = `${RESET_MARKER} — previous: ${was}. Re-run required.`;
|
|
164
|
+
const note = prior[NOTE_INDEX].value;
|
|
165
|
+
const own = cellText(note);
|
|
166
|
+
if (typeof note === 'object' && note !== null && 'hyperlink' in note) {
|
|
167
|
+
return { ...note, text: `${header}\n${own}` };
|
|
168
|
+
}
|
|
169
|
+
return own ? `${header}\n${own}` : header;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Carry `prior` into the freshly rendered `wb`, then stamp every manual variant's fingerprint.
|
|
174
|
+
* Only the Manual sheet's variant sub-rows are touched.
|
|
175
|
+
*/
|
|
176
|
+
export function carryManualResults(
|
|
177
|
+
wb: ExcelJS.Workbook,
|
|
178
|
+
model: MatrixModel,
|
|
179
|
+
prior: PriorManualResults | null,
|
|
180
|
+
): ManualCarryReport {
|
|
181
|
+
const report: ManualCarryReport = { carried: 0, unchecked: 0, reset: [], notPlaced: [], duplicates: prior?.duplicates ?? [] };
|
|
182
|
+
const fingerprints = new Map(
|
|
183
|
+
model.items.flatMap((i) => i.variants).filter((v) => v.mode === 'manual').map((v) => [v.ref, v.fingerprint]),
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const ws = wb.getWorksheet(MANUAL_SHEET);
|
|
187
|
+
const placed = new Set<string>();
|
|
188
|
+
if (ws && prior) {
|
|
189
|
+
for (const [ref, row] of variantRows(ws)) {
|
|
190
|
+
const before = prior.rows.get(ref);
|
|
191
|
+
if (!before) continue;
|
|
192
|
+
placed.add(ref);
|
|
193
|
+
if (isUntouched(before.cells)) continue;
|
|
194
|
+
|
|
195
|
+
const stamped = prior.stamps?.get(ref);
|
|
196
|
+
// Only a recorded result is a claim about the procedure. A row with no result (a
|
|
197
|
+
// blocker note, or one already reset) is carried as-is, so a second procedure
|
|
198
|
+
// change does not stack a second marker on it.
|
|
199
|
+
const hasResult = !['', 'Pending'].includes(cellText(before.cells[0].value));
|
|
200
|
+
if (stamped !== undefined && stamped !== fingerprints.get(ref) && hasResult) {
|
|
201
|
+
row.getCell(RESULT_COL).value = 'Pending';
|
|
202
|
+
row.getCell(CARRIED_COLS[NOTE_INDEX]).value = resetNote(before.cells);
|
|
203
|
+
report.reset.push(ref);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
CARRIED_COLS.forEach((c, i) => {
|
|
208
|
+
const cell = row.getCell(c);
|
|
209
|
+
cell.value = before.cells[i].value;
|
|
210
|
+
if (before.cells[i].numFmt) cell.numFmt = before.cells[i].numFmt!;
|
|
211
|
+
});
|
|
212
|
+
if (stamped === undefined) report.unchecked++;
|
|
213
|
+
else report.carried++;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (prior) {
|
|
218
|
+
for (const [ref, before] of prior.rows) {
|
|
219
|
+
if (!placed.has(ref) && !isUntouched(before.cells)) {
|
|
220
|
+
report.notPlaced.push({ ref, result: cellText(before.cells[0].value) || '(no result)' });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const stampWs = wb.addWorksheet(STAMP_SHEET, { state: 'veryHidden' });
|
|
226
|
+
stampWs.addRow(['ref', 'fingerprint']);
|
|
227
|
+
for (const [ref, fp] of fingerprints) stampWs.addRow([ref, fp]);
|
|
228
|
+
return report;
|
|
229
|
+
}
|
|
@@ -16,7 +16,8 @@ import { loadPlaywrightReport } from '../playwright-report-parser';
|
|
|
16
16
|
import { mergeFeatureAndSpec } from '../scenario-merger';
|
|
17
17
|
import { getPackageVersion } from '../package-info';
|
|
18
18
|
import { writeCsv } from '../csv-exporter';
|
|
19
|
-
import { writeXlsx } from '../xlsx-exporter';
|
|
19
|
+
import { deliverableXlsxPath, writeXlsx } from '../xlsx-exporter';
|
|
20
|
+
import { carryManualResults, ManualCarryReport, readPriorManualResults } from './carry-manual-results';
|
|
20
21
|
import { loadPerfConfig, perfVerdict } from '../../harness/perf';
|
|
21
22
|
import { loadDeliveryMap, writeDeliveryMap } from './map-loader';
|
|
22
23
|
import { mapContentFingerprint } from './fingerprint';
|
|
@@ -25,6 +26,7 @@ import { renderMatrixXlsx } from './render-xlsx';
|
|
|
25
26
|
import { renderMatrixCsv } from './render-csv';
|
|
26
27
|
import { DeliveryMap, MatrixFinding, MatrixModel } from './types';
|
|
27
28
|
import { ApiCatalogEntry } from '../types';
|
|
29
|
+
import { RunEnvironment } from '../run-environment';
|
|
28
30
|
|
|
29
31
|
export interface MatrixTargetPaths {
|
|
30
32
|
cwd: string;
|
|
@@ -45,6 +47,8 @@ export interface MatrixTargetPaths {
|
|
|
45
47
|
specMdFile?: string;
|
|
46
48
|
resultsPath: string | null;
|
|
47
49
|
mapFile: string;
|
|
50
|
+
/** Executor + environment of the run (#850) — absent for gates-only callers (inspect, approve). */
|
|
51
|
+
runEnv?: RunEnvironment;
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
function readSpecText(paths: MatrixTargetPaths): string {
|
|
@@ -90,6 +94,7 @@ export function loadMatrixModel(paths: MatrixTargetPaths): MatrixLoadResult {
|
|
|
90
94
|
specText: readSpecText(paths),
|
|
91
95
|
apiCatalog: catalogFor(paths),
|
|
92
96
|
});
|
|
97
|
+
model.runEnv = paths.runEnv;
|
|
93
98
|
// Perf verdict (#569): budgets are config (qa/perf.yaml), samples are the run's
|
|
94
99
|
// per-test durations. Advisory — reported here because delivery is where runs
|
|
95
100
|
// are read; the audit's score stays a pure function of the design artifacts.
|
|
@@ -163,13 +168,18 @@ export async function writeMatrixDeliverables(
|
|
|
163
168
|
paths: MatrixTargetPaths,
|
|
164
169
|
model: MatrixModel,
|
|
165
170
|
format: MatrixFormat = 'xlsx',
|
|
166
|
-
): Promise<{ csvPath?: string; xlsxPath?: string }> {
|
|
167
|
-
const out: { csvPath?: string; xlsxPath?: string } = {};
|
|
171
|
+
): Promise<{ csvPath?: string; xlsxPath?: string; manualCarry?: ManualCarryReport }> {
|
|
172
|
+
const out: { csvPath?: string; xlsxPath?: string; manualCarry?: ManualCarryReport } = {};
|
|
173
|
+
const xlsx = format === 'xlsx' || format === 'both';
|
|
174
|
+
// Read the workbook about to be overwritten BEFORE writing anything, so the tester's Manual
|
|
175
|
+
// results survive (#756) and a refusal leaves no half-written target behind.
|
|
176
|
+
const prior = xlsx ? await readPriorManualResults(deliverableXlsxPath(paths.cwd, paths.unit)) : null;
|
|
168
177
|
if (format === 'csv' || format === 'both') {
|
|
169
178
|
out.csvPath = writeCsv(paths.cwd, paths.unit, renderMatrixCsv(model));
|
|
170
179
|
}
|
|
171
|
-
if (
|
|
180
|
+
if (xlsx) {
|
|
172
181
|
const wb = renderMatrixXlsx(model, getPackageVersion());
|
|
182
|
+
out.manualCarry = carryManualResults(wb, model, prior);
|
|
173
183
|
out.xlsxPath = await writeXlsx(paths.cwd, paths.unit, wb);
|
|
174
184
|
}
|
|
175
185
|
return out;
|
|
@@ -102,6 +102,12 @@ function gateASource(ctx: GateContext, findings: MatrixFinding[]): void {
|
|
|
102
102
|
: `scenario ${vpId} has no Then step — a variant without an observable oracle cannot be delivered`,
|
|
103
103
|
});
|
|
104
104
|
}
|
|
105
|
+
if (m.unlabeledManualOracle) {
|
|
106
|
+
findings.push({
|
|
107
|
+
gate: 'A', severity: 'warning', ref: vpId,
|
|
108
|
+
message: `manual oracle found only in an unlabeled comment — exported verbatim into Expected; write the \`# Tester verifies:\` block as numbered "N. Observable: …" lines`,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
105
111
|
}
|
|
106
112
|
for (const [vpId, n] of seen) {
|
|
107
113
|
if (n > 1) findings.push({ gate: 'A', severity: 'error', ref: vpId, message: `duplicate VP-id ${vpId} (${n} scenarios)` });
|
|
@@ -17,6 +17,7 @@ import * as ExcelJS from 'exceljs';
|
|
|
17
17
|
import { renderReportHeaderBand, ARIAL_FONT } from '../xlsx-report-builder';
|
|
18
18
|
import { redactSecrets } from '../api-testcase-formatter';
|
|
19
19
|
import { clipNoteField } from '../playwright-report-parser';
|
|
20
|
+
import { runCells } from '../run-environment';
|
|
20
21
|
import { variantState } from './build';
|
|
21
22
|
import { DeliveryItem, MatrixModel } from './types';
|
|
22
23
|
import { SOFT_BLUE, DRAFT_RED, RESULT_STATES, dataCell, headerCell, draftBanner } from './xlsx-cells';
|
|
@@ -42,6 +43,9 @@ export const RESULT_COL = 12;
|
|
|
42
43
|
|
|
43
44
|
/** Variant sub-row — only what distinguishes it from its parent (review GAP-01). */
|
|
44
45
|
function variantValues(item: DeliveryItem, v: DeliveryItem['variants'][number], model: MatrixModel): ExcelJS.CellValue[] {
|
|
46
|
+
// Who ran it and against what — the same cells the legacy workbook fills (#850); blank
|
|
47
|
+
// until the variant has actually run.
|
|
48
|
+
const run = runCells(model.runEnv, v.result);
|
|
45
49
|
return [
|
|
46
50
|
v.ref,
|
|
47
51
|
'',
|
|
@@ -50,15 +54,17 @@ function variantValues(item: DeliveryItem, v: DeliveryItem['variants'][number],
|
|
|
50
54
|
numbered(item.preconditionDeltas[v.ref] ?? []),
|
|
51
55
|
numbered(v.data),
|
|
52
56
|
// Only the steps after the parent's shared prefix, numbered so the parent's
|
|
53
|
-
// steps + these read as one continuous procedure.
|
|
54
|
-
|
|
57
|
+
// steps + these read as one continuous procedure. Counted from the REAL shared
|
|
58
|
+
// steps, not `trigger.length`: with nothing shared the parent shows a generated
|
|
59
|
+
// "the action differs…" note, and the variant's first step read "2." (#848).
|
|
60
|
+
numbered(item.triggerDeltas[v.ref] ?? [], item.sharedTriggerSteps + 1),
|
|
55
61
|
expectedWithVerification(v.oracle, v.verification),
|
|
56
62
|
v.traces.join(', '),
|
|
57
63
|
variantTypeLabel(v),
|
|
58
64
|
variantState(v),
|
|
59
65
|
isoDate(v.result?.startTime),
|
|
60
|
-
|
|
61
|
-
|
|
66
|
+
run.executor,
|
|
67
|
+
run.environment,
|
|
62
68
|
// Redact BEFORE clipping — same denylist as the testcases CSV Note column.
|
|
63
69
|
v.result?.error ? clipNoteField(redactSecrets(String(v.result.error), model.secretValues)) : '',
|
|
64
70
|
];
|
|
@@ -176,8 +176,12 @@ export interface DeliveryItem {
|
|
|
176
176
|
/** Preconditions shared by EVERY variant (the intersection) — written once here. */
|
|
177
177
|
precondition: string[];
|
|
178
178
|
/** Leading trigger steps shared by every variant — written once here; each
|
|
179
|
-
* variant renders only its remaining steps (`triggerDeltas`).
|
|
179
|
+
* variant renders only its remaining steps (`triggerDeltas`). When nothing is shared this
|
|
180
|
+
* holds a generated "the action differs…" note instead, which is NOT a step. */
|
|
180
181
|
trigger: string[];
|
|
182
|
+
/** How many of `trigger` are real steps hoisted from the variants (0 when it is only the
|
|
183
|
+
* note) — the variants' steps are numbered from this + 1 (#848). */
|
|
184
|
+
sharedTriggerSteps: number;
|
|
181
185
|
variants: CoverageVariant[];
|
|
182
186
|
/** variant ref → its precondition lines that are not on the parent. */
|
|
183
187
|
preconditionDeltas: Record<string, string[]>;
|
|
@@ -253,6 +257,9 @@ export interface MatrixModel {
|
|
|
253
257
|
* the deliverable ships to the client.
|
|
254
258
|
*/
|
|
255
259
|
secretValues: string[];
|
|
260
|
+
/** Who ran the suite and against what — fills Test Executor / Test Environment on rows that
|
|
261
|
+
* ran (#850). Absent when the caller has no run context (gates-only paths). */
|
|
262
|
+
runEnv?: import('../run-environment').RunEnvironment;
|
|
256
263
|
}
|
|
257
264
|
|
|
258
265
|
/** Complexity warning threshold (rules draft Gate I; raised for compact grouping) — echoed in the manifest. */
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* Action → action, Observable → expected, Oracle → verification method.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { extractUnlabeledOracle, onlyPresenceAssertions } from '../manual-hybrid-oracle';
|
|
17
|
+
|
|
16
18
|
/** A step that CAPTURES a runtime value: `User remember [X] text as {{var}}`. */
|
|
17
19
|
export const RUNTIME_CAPTURE = /\bremember\b/i;
|
|
18
20
|
|
|
@@ -40,8 +42,25 @@ export function runtimeVarsOf(stepTexts: string[], tags: string[]): Set<string>
|
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
// `[Email] field` → `Email field` (the visible label + its element type).
|
|
45
|
+
// Only the DSL's own refs: a `"quoted literal"` and a `{{token}}` are data, so their
|
|
46
|
+
// brackets stay (#772) — `"[DRAFT] report"`, `{{q.rows[0].id}}`. Callers render BEFORE
|
|
47
|
+
// substituting values, so a value's brackets (`^[A-Za-z]{3}`) never reach this.
|
|
48
|
+
// A literal opens only where a word does not end: in free text (manual procedures) `5"`
|
|
49
|
+
// is an inch mark, and two of them must not shield the `[Ref]` between them.
|
|
43
50
|
function deRef(text: string): string {
|
|
44
|
-
return text.replace(
|
|
51
|
+
return text.replace(/(?<![\p{L}\p{N}])"[^"]*"|\{\{[^}]*\}\}|\[([^\]]+)\]/gu, (m, ref?: string) => ref ?? m);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Finish a cell rendered with `{{tokens}}` still in it, once the values are substituted
|
|
56
|
+
* (#772: render first, substitute after, so deRef never sees a value). Keeps what
|
|
57
|
+
* `sentence()` did when values went in first: a leading value is capitalised, and a
|
|
58
|
+
* value that already ends a sentence does not gain a second mark (`value..`).
|
|
59
|
+
*/
|
|
60
|
+
export function afterSubstitution(text: string): string {
|
|
61
|
+
if (!text) return text;
|
|
62
|
+
const s = text.charAt(0).toUpperCase() + text.slice(1);
|
|
63
|
+
return s.replace(/([.!?…"])\.$/, '$1');
|
|
45
64
|
}
|
|
46
65
|
|
|
47
66
|
function sentence(text: string): string {
|
|
@@ -115,7 +134,15 @@ export function renderExpected(raw: string): string {
|
|
|
115
134
|
if (RUNTIME_CAPTURE.test(raw) && /\bas\s+\{\{/.test(raw)) return renderAction(raw);
|
|
116
135
|
let s = raw.trim().replace(/^(User|The user)\s+/i, '');
|
|
117
136
|
|
|
137
|
+
// `table match data:` rows (folded in by feature-parser, #771) are the EXPECTED VALUES —
|
|
138
|
+
// `[E2E-01] Q1` is data, not a ref — so only the table's own ref is de-ref'd (#772).
|
|
139
|
+
const md = s.match(/^sees? \[([^\]]+)\]\s*table match(?:es)? data: (.+)$/i);
|
|
140
|
+
if (md) return sentence(`The ${md[1]} table shows these rows: ${md[2]}`);
|
|
141
|
+
|
|
118
142
|
const rules: Array<[RegExp, (m: RegExpMatchArray) => string]> = [
|
|
143
|
+
// tab order: (#771) row 1 is the focused origin, each later row is one Tab press —
|
|
144
|
+
// its cells ARE refs, so they are de-ref'd like any step's.
|
|
145
|
+
[/^sees? tab order: (.+)$/i, (m) => `Pressing Tab moves focus in this order: ${m[1]}`],
|
|
119
146
|
// expect A is (not) B — comparing two captured values (order matters: "is not" first)
|
|
120
147
|
[/^expects? (.+?) is not (.+)$/i, (m) => `${m[1]} differs from ${m[2]}`],
|
|
121
148
|
[/^expects? (.+?) is (.+)$/i, (m) => `${m[1]} equals ${m[2]}`],
|
|
@@ -204,7 +231,7 @@ export interface ManualProcedure {
|
|
|
204
231
|
* review feedback §10.2(3). Continuation lines append to the previous item;
|
|
205
232
|
* pre-amble (rationale/header/dividers) is skipped.
|
|
206
233
|
*/
|
|
207
|
-
export function classifyManualComments(comments: string[]): ManualProcedure {
|
|
234
|
+
export function classifyManualComments(comments: string[], thenTexts: string[] = []): ManualProcedure {
|
|
208
235
|
const out: ManualProcedure = { preconditions: [], actions: [], expected: [], verification: [] };
|
|
209
236
|
let last: { list: string[]; idx: number } | null = null;
|
|
210
237
|
|
|
@@ -230,5 +257,12 @@ export function classifyManualComments(comments: string[]): ManualProcedure {
|
|
|
230
257
|
last.list[last.idx] += ' ' + line;
|
|
231
258
|
}
|
|
232
259
|
}
|
|
260
|
+
// #810 — an oracle written as unlabelled `Tester verifies:` prose is still the oracle: with no
|
|
261
|
+
// labelled Observable line and no Then beyond presence (`thenTexts`), export it rather than fall
|
|
262
|
+
// back to a presence-placeholder Then. A real Then stays the oracle.
|
|
263
|
+
if (!out.expected.length && onlyPresenceAssertions(thenTexts)) {
|
|
264
|
+
const prose = extractUnlabeledOracle(comments);
|
|
265
|
+
if (prose) out.expected.push(prose);
|
|
266
|
+
}
|
|
233
267
|
return out;
|
|
234
268
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who ran the tests, and against what — the Test Executor / Test Environment columns of every
|
|
3
|
+
* deliverable (legacy workbook, matrix workbook) and the dashboard.
|
|
4
|
+
*
|
|
5
|
+
* One module so the surfaces cannot drift: the matrix format shipped with both cells hard-coded
|
|
6
|
+
* blank on every Auto row while the legacy exporter filled them (#850).
|
|
7
|
+
*/
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import { execSync } from 'child_process';
|
|
11
|
+
import { resolveWebBaseUrl } from './env-qa';
|
|
12
|
+
import { statusToTestResult } from './playwright-report-parser';
|
|
13
|
+
import { EnvironmentInfo, PlaywrightResult } from './types';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The executor, most explicit source first: `SUNGEN_EXECUTOR` (a CI job names the person or
|
|
17
|
+
* pipeline that ran the suite — whoever runs `sungen delivery` there is not that person), then
|
|
18
|
+
* git user.name, then `CI_USER` / `USER`.
|
|
19
|
+
*/
|
|
20
|
+
export function resolveExecutor(cwd: string): string {
|
|
21
|
+
const explicit = process.env.SUNGEN_EXECUTOR?.trim();
|
|
22
|
+
if (explicit) return explicit;
|
|
23
|
+
try {
|
|
24
|
+
const gitUser = execSync('git config user.name', { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
25
|
+
if (gitUser) return gitUser;
|
|
26
|
+
} catch { /* not a git checkout, or no user.name */ }
|
|
27
|
+
return process.env.CI_USER || process.env.USER || '';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** baseURL + browser project from playwright.config.ts, executor from {@link resolveExecutor}. */
|
|
31
|
+
export function getEnvironment(cwd: string): EnvironmentInfo {
|
|
32
|
+
let baseURL = '';
|
|
33
|
+
let projectName = 'chromium';
|
|
34
|
+
const configPath = path.join(cwd, 'playwright.config.ts');
|
|
35
|
+
if (fs.existsSync(configPath)) {
|
|
36
|
+
const c = fs.readFileSync(configPath, 'utf-8');
|
|
37
|
+
// Every config shape sungen has scaffolded: the literal `baseURL: 'x'`, the env-aware
|
|
38
|
+
// `baseURL: resolveBaseUrl('x')`, and the older `baseURL: process.env.SUNGEN_BASE_URL || 'x'`
|
|
39
|
+
// (the quoted value is the static fallback in both wrappers).
|
|
40
|
+
const baseMatch = c.match(/baseURL:\s*(?:resolveBaseUrl\(\s*|process\.env\.\w+\s*\|\|\s*)?['"]([^'"]+)['"]/);
|
|
41
|
+
if (baseMatch) baseURL = baseMatch[1];
|
|
42
|
+
const projMatch = c.match(/name:\s*['"]([^'"]+)['"]/);
|
|
43
|
+
if (projMatch) projectName = projMatch[1];
|
|
44
|
+
}
|
|
45
|
+
// SUNGEN_BASE_URL (shell > .env.qa.<SUNGEN_ENV> > .env.qa) overrides the static
|
|
46
|
+
// value — the Test Environment column must show the host the run actually hit.
|
|
47
|
+
baseURL = resolveWebBaseUrl(cwd, baseURL);
|
|
48
|
+
return { baseURL, projectName, executor: resolveExecutor(cwd) };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** `https://host/path (project)` — or, on mobile (no baseURL), the bare app id `(project)`. */
|
|
52
|
+
export function formatEnv(baseURL: string, featurePath: string | undefined, projectName: string): string {
|
|
53
|
+
const base = (baseURL || '').replace(/\/+$/, '');
|
|
54
|
+
const p = (featurePath || '').replace(/^\/+/, '');
|
|
55
|
+
// Join with '/' only when there is a baseURL (web). For mobile baseURL is empty
|
|
56
|
+
// and `p` is an app id (e.g. com.kngroup.media.converter) — emit it bare, no '/'.
|
|
57
|
+
const url = base && p ? `${base}/${p}` : (p || base);
|
|
58
|
+
return `${url} (${projectName})`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The platform a result file belongs to — `<f>-test-result.<os>[.<env>].json` → `<os>`. It names
|
|
63
|
+
* the run actually being read, so it outranks `MOBILE_PLATFORM` / `SUNGEN_ENV`, and SUNGEN_ENV
|
|
64
|
+
* is usually an environment (`staging`, `ja`), not an OS. A web locale file yields its locale,
|
|
65
|
+
* which is harmless: only a dual-id mobile `Path:` (`android | ios`) reads the platform.
|
|
66
|
+
*/
|
|
67
|
+
export function platformOfResultFile(resultsPath: string | null | undefined): string | undefined {
|
|
68
|
+
return resultsPath ? path.basename(resultsPath).match(/-test-result\.([^.]+)\./)?.[1] : undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** What a run-result row needs to fill its executor/environment cells. */
|
|
72
|
+
export interface RunEnvironment extends EnvironmentInfo {
|
|
73
|
+
/** The unit's `Path:` (flows: their page selector; mobile: the OS-precise app id). */
|
|
74
|
+
featurePath?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The Test Executor / Test Environment cells for one result — blank unless the test actually
|
|
79
|
+
* ran (a Pending or N/A row has nobody who executed it and no environment it ran against).
|
|
80
|
+
*/
|
|
81
|
+
export function runCells(env: RunEnvironment | undefined, result: PlaywrightResult | undefined): { executor: string; environment: string } {
|
|
82
|
+
if (!env || !result) return { executor: '', environment: '' };
|
|
83
|
+
const outcome = statusToTestResult(result.status);
|
|
84
|
+
if (outcome === 'Pending' || outcome === 'N/A') return { executor: '', environment: '' };
|
|
85
|
+
return {
|
|
86
|
+
executor: env.executor,
|
|
87
|
+
environment: formatEnv(env.baseURL, env.featurePath, result.projectName || env.projectName),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { FeatureMetadata, OrderedStep, ScenarioMetadata, SpecFileData, SpecTest } from './types';
|
|
12
12
|
import { isStepsBaseScenario, isSampleScaffoldScenario, extractTestcaseType } from './feature-parser';
|
|
13
13
|
import { SCENARIO_ID } from './feature-parser';
|
|
14
|
+
import { extractUnlabeledOracle, onlyPresenceAssertions } from './manual-hybrid-oracle';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Parse a @manual scenario's `# Tester verifies:` comment block into procedure vs
|
|
@@ -21,8 +22,12 @@ import { SCENARIO_ID } from './feature-parser';
|
|
|
21
22
|
* (Setup/Action/Precondition/unlabelled) → Steps. A non-numbered line following a
|
|
22
23
|
* numbered item is a continuation (e.g. a wrapped URL) and is appended to it. Pre-amble
|
|
23
24
|
* lines (the `MANUAL:` rationale, the `Tester verifies:` header, separators) are ignored.
|
|
25
|
+
*
|
|
26
|
+
* `unlabeledOracle` is the prose a `Tester verifies:` header carries OUTSIDE numbered lines
|
|
27
|
+
* (#810). It is not part of `expected` — the gates judge the numbered shape — but the exporter
|
|
28
|
+
* must never drop it: when no labelled oracle line exists it becomes the Expected cell.
|
|
24
29
|
*/
|
|
25
|
-
export function parseManualComments(comments: string[]): { steps: OrderedStep[]; expected: OrderedStep[] } {
|
|
30
|
+
export function parseManualComments(comments: string[]): { steps: OrderedStep[]; expected: OrderedStep[]; unlabeledOracle: string | null } {
|
|
26
31
|
const steps: OrderedStep[] = [];
|
|
27
32
|
const expected: OrderedStep[] = [];
|
|
28
33
|
let last: OrderedStep | null = null;
|
|
@@ -45,7 +50,31 @@ export function parseManualComments(comments: string[]): { steps: OrderedStep[];
|
|
|
45
50
|
}
|
|
46
51
|
// else: pre-amble before any numbered item (rationale / header) → skip
|
|
47
52
|
}
|
|
48
|
-
return { steps, expected };
|
|
53
|
+
return { steps, expected, unlabeledOracle: extractUnlabeledOracle(comments) };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The reason a @manual scenario is manual — the text of its `# MANUAL…: <reason>` comment
|
|
58
|
+
* (`MANUAL:`, `MANUAL (2026-09-16, run-test):`, `MANUAL/ENV:` …) plus its indented continuation
|
|
59
|
+
* lines, up to the `Tester verifies:` header, a numbered item or a divider. '' when absent.
|
|
60
|
+
* Rendered in the deliverable's Note so a tester sees why the case is not automated (#810).
|
|
61
|
+
*/
|
|
62
|
+
export function extractManualReason(comments: string[]): string {
|
|
63
|
+
const parts: string[] = [];
|
|
64
|
+
let inReason = false;
|
|
65
|
+
for (const raw of comments) {
|
|
66
|
+
const line = raw.trim();
|
|
67
|
+
if (!inReason) {
|
|
68
|
+
const m = line.match(/^MANUAL\b[^:]*:\s*(.*)$/i);
|
|
69
|
+
if (m) { inReason = true; if (m[1].trim()) parts.push(m[1].trim()); }
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
// A wrapped reason continues on an INDENTED comment line (`# automate once seeded.`); the
|
|
73
|
+
// comment list carries no line numbers, so an unindented line is a separate note, not more reason.
|
|
74
|
+
if (!line || !/^\s/.test(raw) || /^tester verifies\b/i.test(line) || /^\d+[.)]\s/.test(line) || /^[-=*_]{2,}/.test(line)) break;
|
|
75
|
+
parts.push(line);
|
|
76
|
+
}
|
|
77
|
+
return parts.join(' ').replace(/\s+/g, ' ').trim();
|
|
49
78
|
}
|
|
50
79
|
|
|
51
80
|
export interface MergedScenario {
|
|
@@ -84,6 +113,12 @@ export interface MergedScenario {
|
|
|
84
113
|
* so counting them would falsely mark @manual scenarios as interleaved.
|
|
85
114
|
*/
|
|
86
115
|
resolvedAnchorActionCount: number;
|
|
116
|
+
/**
|
|
117
|
+
* Set when this @manual scenario's oracle exists ONLY as unlabelled `Tester verifies:` prose
|
|
118
|
+
* (#810). The prose is exported verbatim into `resolvedExpected`; delivery warns so the author
|
|
119
|
+
* rewrites it as numbered `Observable:` lines.
|
|
120
|
+
*/
|
|
121
|
+
unlabeledManualOracle?: string;
|
|
87
122
|
}
|
|
88
123
|
|
|
89
124
|
/**
|
|
@@ -147,7 +182,16 @@ export function mergeFeatureAndSpec(
|
|
|
147
182
|
const manualParsed =
|
|
148
183
|
extractTestcaseType(scenario.tags) === 'Manual' && scenario.comments?.length
|
|
149
184
|
? parseManualComments(scenario.comments)
|
|
150
|
-
: { steps: [] as OrderedStep[], expected: [] as OrderedStep[] };
|
|
185
|
+
: { steps: [] as OrderedStep[], expected: [] as OrderedStep[], unlabeledOracle: null };
|
|
186
|
+
// Never drop a manual oracle (#810): with no labelled Observable/Oracle line and no Then that
|
|
187
|
+
// checks more than presence, the unlabelled `Tester verifies:` prose IS what the case checks —
|
|
188
|
+
// export it rather than let a presence placeholder stand in for it. A real Then keeps the cell.
|
|
189
|
+
const unlabeledOracle = !manualParsed.expected.length && onlyPresenceAssertions(ownAssertions.map((s) => s.text))
|
|
190
|
+
? manualParsed.unlabeledOracle
|
|
191
|
+
: null;
|
|
192
|
+
const manualExpected: OrderedStep[] = unlabeledOracle
|
|
193
|
+
? [{ text: unlabeledOracle, bucket: 'then' }]
|
|
194
|
+
: manualParsed.expected;
|
|
151
195
|
|
|
152
196
|
result.push({
|
|
153
197
|
feature: scenario,
|
|
@@ -158,8 +202,9 @@ export function mergeFeatureAndSpec(
|
|
|
158
202
|
...ownActions,
|
|
159
203
|
...manualParsed.steps,
|
|
160
204
|
],
|
|
161
|
-
resolvedExpected: [...ownAssertions, ...
|
|
205
|
+
resolvedExpected: [...ownAssertions, ...manualExpected],
|
|
162
206
|
resolvedAnchorActionCount: baseLen + ownActions.length,
|
|
207
|
+
...(unlabeledOracle ? { unlabeledManualOracle: unlabeledOracle } : {}),
|
|
163
208
|
});
|
|
164
209
|
}
|
|
165
210
|
|
|
@@ -190,9 +190,3 @@ export function renderDynamicTokens(text: string): string {
|
|
|
190
190
|
});
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
/**
|
|
194
|
-
* Resolve the test-data file path for a given screen.
|
|
195
|
-
*/
|
|
196
|
-
export function resolveTestDataPath(cwd: string, screen: string): string {
|
|
197
|
-
return path.join(cwd, 'qa', 'screens', screen, 'test-data', `${screen}.yaml`);
|
|
198
|
-
}
|
|
@@ -113,15 +113,20 @@ export function buildApiDetailRows(
|
|
|
113
113
|
* used by the multi-locale delivery aggregator. Pass `null` / empty string
|
|
114
114
|
* to force the base (no suffix) variant.
|
|
115
115
|
*/
|
|
116
|
+
/** Where `writeXlsx` puts a unit's workbook — shared so a reader targets the identical file. */
|
|
117
|
+
export function deliverableXlsxPath(cwd: string, screen: string, locale?: string | null): string {
|
|
118
|
+
return path.join(cwd, 'qa', 'deliverables', `${deliverableBasename(screen, locale)}.xlsx`);
|
|
119
|
+
}
|
|
120
|
+
|
|
116
121
|
export async function writeXlsx(
|
|
117
122
|
cwd: string,
|
|
118
123
|
screen: string,
|
|
119
124
|
wb: ExcelJS.Workbook,
|
|
120
125
|
locale?: string | null,
|
|
121
126
|
): Promise<string> {
|
|
122
|
-
const
|
|
127
|
+
const outPath = deliverableXlsxPath(cwd, screen, locale);
|
|
128
|
+
const outDir = path.dirname(outPath);
|
|
123
129
|
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
|
|
124
|
-
const outPath = path.join(outDir, `${deliverableBasename(screen, locale)}.xlsx`);
|
|
125
130
|
const buffer = await wb.xlsx.writeBuffer();
|
|
126
131
|
const locked = await lockEmbeddedImages(Buffer.from(buffer as ArrayBuffer));
|
|
127
132
|
fs.writeFileSync(outPath, locked);
|