@sun-asterisk/sungen 3.2.32 → 3.2.34-beta.1
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 +64 -11
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/capability.d.ts.map +1 -1
- package/dist/cli/commands/capability.js +40 -18
- 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/defect.d.ts +7 -0
- package/dist/cli/commands/defect.d.ts.map +1 -0
- package/dist/cli/commands/defect.js +68 -0
- package/dist/cli/commands/defect.js.map +1 -0
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +113 -59
- 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/triage.d.ts +7 -0
- package/dist/cli/commands/triage.d.ts.map +1 -0
- package/dist/cli/commands/triage.js +98 -0
- package/dist/cli/commands/triage.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/commands/viewpoint.d.ts +7 -0
- package/dist/cli/commands/viewpoint.d.ts.map +1 -0
- package/dist/cli/commands/viewpoint.js +70 -0
- package/dist/cli/commands/viewpoint.js.map +1 -0
- package/dist/cli/index.js +13 -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 +4 -0
- package/dist/exporters/csv-exporter.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.js +12 -11
- package/dist/exporters/csv-exporter.js.map +1 -1
- package/dist/exporters/feature-parser.d.ts +29 -0
- package/dist/exporters/feature-parser.d.ts.map +1 -1
- package/dist/exporters/feature-parser.js +44 -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 +68 -19
- package/dist/exporters/matrix/build.js.map +1 -1
- package/dist/exporters/matrix/carry-manual-results.d.ts +89 -0
- package/dist/exporters/matrix/carry-manual-results.d.ts.map +1 -0
- package/dist/exporters/matrix/carry-manual-results.js +302 -0
- package/dist/exporters/matrix/carry-manual-results.js.map +1 -0
- package/dist/exporters/matrix/export.d.ts +9 -1
- package/dist/exporters/matrix/export.d.ts.map +1 -1
- package/dist/exporters/matrix/export.js +21 -3
- 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-csv.js +1 -1
- package/dist/exporters/matrix/render-csv.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/render-xlsx.d.ts.map +1 -1
- package/dist/exporters/matrix/render-xlsx.js +3 -0
- package/dist/exporters/matrix/render-xlsx.js.map +1 -1
- package/dist/exporters/matrix/types.d.ts +13 -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/playwright-report-parser.d.ts.map +1 -1
- package/dist/exporters/playwright-report-parser.js +1 -0
- package/dist/exporters/playwright-report-parser.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 +49 -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/types.d.ts +5 -0
- package/dist/exporters/types.d.ts.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 +60 -19
- 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/counted-assertions.d.ts +2 -0
- package/dist/harness/counted-assertions.d.ts.map +1 -0
- package/dist/harness/counted-assertions.js +38 -0
- package/dist/harness/counted-assertions.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/defect-draft.d.ts +14 -0
- package/dist/harness/defect-draft.d.ts.map +1 -0
- package/dist/harness/defect-draft.js +143 -0
- package/dist/harness/defect-draft.js.map +1 -0
- package/dist/harness/defects.d.ts +24 -0
- package/dist/harness/defects.d.ts.map +1 -0
- package/dist/harness/defects.js +118 -0
- package/dist/harness/defects.js.map +1 -0
- 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/manifest.d.ts.map +1 -1
- package/dist/harness/manifest.js +2 -2
- package/dist/harness/manifest.js.map +1 -1
- package/dist/harness/next-step.d.ts +2 -4
- package/dist/harness/next-step.d.ts.map +1 -1
- package/dist/harness/next-step.js +50 -5
- package/dist/harness/next-step.js.map +1 -1
- package/dist/harness/parse.d.ts +45 -0
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +142 -24
- 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/script-check.d.ts.map +1 -1
- package/dist/harness/script-check.js +7 -1
- package/dist/harness/script-check.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 +133 -39
- 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/triage-signals.d.ts +20 -0
- package/dist/harness/triage-signals.d.ts.map +1 -0
- package/dist/harness/triage-signals.js +30 -0
- package/dist/harness/triage-signals.js.map +1 -0
- package/dist/harness/triage-unit.d.ts +23 -0
- package/dist/harness/triage-unit.d.ts.map +1 -0
- package/dist/harness/triage-unit.js +142 -0
- package/dist/harness/triage-unit.js.map +1 -0
- package/dist/harness/triage.d.ts +28 -0
- package/dist/harness/triage.d.ts.map +1 -0
- package/dist/harness/triage.js +99 -0
- package/dist/harness/triage.js.map +1 -0
- 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-baseline.d.ts.map +1 -1
- package/dist/harness/viewpoint-baseline.js +7 -1
- package/dist/harness/viewpoint-baseline.js.map +1 -1
- package/dist/harness/viewpoint-draft-header.d.ts +7 -0
- package/dist/harness/viewpoint-draft-header.d.ts.map +1 -0
- package/dist/harness/viewpoint-draft-header.js +100 -0
- package/dist/harness/viewpoint-draft-header.js.map +1 -0
- package/dist/harness/viewpoint-ledger.d.ts +13 -3
- package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
- package/dist/harness/viewpoint-ledger.js +106 -19
- package/dist/harness/viewpoint-ledger.js.map +1 -1
- package/dist/harness/viewpoint-mode.d.ts +9 -0
- package/dist/harness/viewpoint-mode.d.ts.map +1 -0
- package/dist/harness/viewpoint-mode.js +52 -0
- package/dist/harness/viewpoint-mode.js.map +1 -0
- package/dist/harness/viewpoint-priority.d.ts +19 -0
- package/dist/harness/viewpoint-priority.d.ts.map +1 -0
- package/dist/harness/viewpoint-priority.js +36 -0
- package/dist/harness/viewpoint-priority.js.map +1 -0
- package/dist/harness/viewpoint-review-open.d.ts +6 -0
- package/dist/harness/viewpoint-review-open.d.ts.map +1 -0
- package/dist/harness/viewpoint-review-open.js +90 -0
- package/dist/harness/viewpoint-review-open.js.map +1 -0
- package/dist/harness/viewpoint-scan-refs.d.ts +50 -0
- package/dist/harness/viewpoint-scan-refs.d.ts.map +1 -0
- package/dist/harness/viewpoint-scan-refs.js +200 -0
- package/dist/harness/viewpoint-scan-refs.js.map +1 -0
- package/dist/harness/viewpoint-scan.d.ts +41 -0
- package/dist/harness/viewpoint-scan.d.ts.map +1 -0
- package/dist/harness/viewpoint-scan.js +118 -0
- package/dist/harness/viewpoint-scan.js.map +1 -0
- 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/flow-manager.d.ts +0 -1
- package/dist/orchestrator/flow-manager.d.ts.map +1 -1
- package/dist/orchestrator/flow-manager.js +3 -52
- package/dist/orchestrator/flow-manager.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 +8 -62
- 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 +32 -24
- package/dist/orchestrator/templates/ai-src/commands/review.md +2 -2
- package/dist/orchestrator/templates/ai-src/commands/run-test.md +78 -15
- 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 +25 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +26 -8
- package/dist/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +5 -1
- 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 +42 -2
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +7 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +18 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/review-checklist.md +63 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/viewpoint-template.md +158 -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 +23 -0
- package/dist/orchestrator/viewpoint-pattern-catalog.d.ts.map +1 -0
- package/dist/orchestrator/viewpoint-pattern-catalog.js +48 -0
- package/dist/orchestrator/viewpoint-pattern-catalog.js.map +1 -0
- package/dist/orchestrator/viewpoint-scaffold.d.ts +3 -0
- package/dist/orchestrator/viewpoint-scaffold.d.ts.map +1 -0
- package/dist/orchestrator/viewpoint-scaffold.js +70 -0
- package/dist/orchestrator/viewpoint-scaffold.js.map +1 -0
- package/package.json +3 -3
- package/src/cli/commands/audit.ts +59 -12
- package/src/cli/commands/capability.ts +54 -19
- package/src/cli/commands/changed.ts +2 -4
- package/src/cli/commands/dashboard.ts +1 -26
- package/src/cli/commands/defect.ts +49 -0
- package/src/cli/commands/delivery.ts +106 -56
- 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/triage.ts +57 -0
- package/src/cli/commands/update.ts +3 -0
- package/src/cli/commands/viewpoint.ts +58 -0
- package/src/cli/index.ts +13 -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 +12 -11
- package/src/exporters/feature-parser.ts +44 -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 +71 -19
- package/src/exporters/matrix/carry-manual-results.ts +292 -0
- package/src/exporters/matrix/export.ts +24 -4
- package/src/exporters/matrix/gates.ts +6 -0
- package/src/exporters/matrix/render-csv.ts +1 -1
- package/src/exporters/matrix/render-testcase-sheet.ts +10 -4
- package/src/exporters/matrix/render-xlsx.ts +3 -0
- package/src/exporters/matrix/types.ts +13 -1
- package/src/exporters/matrix/wording.ts +36 -2
- package/src/exporters/playwright-report-parser.ts +1 -0
- package/src/exporters/run-environment.ts +89 -0
- package/src/exporters/scenario-merger.ts +51 -5
- package/src/exporters/test-data-resolver.ts +0 -6
- package/src/exporters/types.ts +5 -0
- 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 +69 -24
- 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/counted-assertions.ts +33 -0
- package/src/harness/data-driven-lint.ts +82 -24
- package/src/harness/defect-draft.ts +115 -0
- package/src/harness/defects.ts +94 -0
- 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/manifest.ts +2 -2
- package/src/harness/next-step.ts +47 -5
- package/src/harness/parse.ts +151 -27
- 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/script-check.ts +7 -1
- package/src/harness/secret-scan.ts +60 -4
- package/src/harness/select-by-ids.ts +83 -0
- package/src/harness/sensors.ts +141 -40
- package/src/harness/spec-branches.ts +6 -3
- package/src/harness/spec-coverage.ts +14 -4
- package/src/harness/triage-signals.ts +46 -0
- package/src/harness/triage-unit.ts +113 -0
- package/src/harness/triage.ts +127 -0
- package/src/harness/ui-inventory.ts +164 -0
- package/src/harness/viewpoint-baseline.ts +7 -1
- package/src/harness/viewpoint-draft-header.ts +55 -0
- package/src/harness/viewpoint-ledger.ts +113 -25
- package/src/harness/viewpoint-mode.ts +49 -0
- package/src/harness/viewpoint-priority.ts +38 -0
- package/src/harness/viewpoint-review-open.ts +51 -0
- package/src/harness/viewpoint-scan-refs.ts +154 -0
- package/src/harness/viewpoint-scan.ts +111 -0
- 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/flow-manager.ts +3 -53
- package/src/orchestrator/project-deps-sync.ts +23 -1
- package/src/orchestrator/project-initializer.ts +54 -6
- package/src/orchestrator/screen-manager.ts +7 -61
- 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 +32 -24
- package/src/orchestrator/templates/ai-src/commands/review.md +2 -2
- package/src/orchestrator/templates/ai-src/commands/run-test.md +78 -15
- 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 +25 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +26 -8
- package/src/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +5 -1
- 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 +42 -2
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +7 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +18 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/review-checklist.md +63 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/viewpoint-template.md +158 -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 +61 -0
- package/src/orchestrator/viewpoint-scaffold.ts +36 -0
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
import { getCasesDatasetRows, resolveResultVariants } from '../result-variants';
|
|
20
20
|
import { renderDynamicTokens } from '../test-data-resolver';
|
|
21
21
|
import { maskCredentialValue } from '../sensitive-key';
|
|
22
|
-
import { classifyManualComments, renderAction, renderExpected, renderPrecondition, renderSetupInstruction, runtimeVarsOf } from './wording';
|
|
22
|
+
import { afterSubstitution, classifyManualComments, renderAction, renderExpected, renderPrecondition, renderSetupInstruction, runtimeVarsOf } from './wording';
|
|
23
23
|
import { scenarioFingerprint, combinedFingerprint, mapContentFingerprint } from './fingerprint';
|
|
24
24
|
import {
|
|
25
25
|
CoverageVariant,
|
|
@@ -96,6 +96,7 @@ function resolveDataPairs(
|
|
|
96
96
|
vars: string[],
|
|
97
97
|
testData: Record<string, string> | null,
|
|
98
98
|
row?: Record<string, unknown>,
|
|
99
|
+
runtimeVars: Set<string> = new Set(),
|
|
99
100
|
): string[] {
|
|
100
101
|
const pairs: string[] = [];
|
|
101
102
|
if (row) {
|
|
@@ -107,7 +108,10 @@ function resolveDataPairs(
|
|
|
107
108
|
for (const v of vars) {
|
|
108
109
|
const val = row && v in row ? undefined : testData?.[v]; // row columns already listed
|
|
109
110
|
// Credential-named refs (`accounts.0.password`) render `***` — this column is read outside the run.
|
|
110
|
-
|
|
111
|
+
// A value may embed a var the scenario captures at run time (`Total ({{stat}})`) or a
|
|
112
|
+
// dataset column — expanded with the same vars as the cells, so both columns agree (#879).
|
|
113
|
+
const scope = row ? { ...(testData ?? {}), ...rowAsStrings(row) } : (testData ?? {});
|
|
114
|
+
if (val !== undefined) pairs.push(`${v}: ${displayValue(maskCredentialValue(v, expandValue(String(val), scope, runtimeVars, new Set([v]))))}`);
|
|
111
115
|
}
|
|
112
116
|
return pairs;
|
|
113
117
|
}
|
|
@@ -136,6 +140,10 @@ export function deriveVariants(inputs: Pick<BuildInputs, 'feature' | 'merged' |
|
|
|
136
140
|
// Test-data values may cross-reference other keys (email_padded: " {{valid_email}} ") —
|
|
137
141
|
// resolve one level so display cells never leak a template token (review B-04).
|
|
138
142
|
const testData = resolveCrossRefs(inputs.testData);
|
|
143
|
+
// Display cells expand from the RAW values, per scenario — with its row and its runtime vars —
|
|
144
|
+
// in the runtime's order (captured var > row column > test-data key). The pre-resolved copy above
|
|
145
|
+
// only serves row discovery and result matching, which render nothing (#879).
|
|
146
|
+
const rawTestData = inputs.testData ?? {};
|
|
139
147
|
const apiCatalog = inputs.apiCatalog ?? {};
|
|
140
148
|
const variants: CoverageVariant[] = [];
|
|
141
149
|
|
|
@@ -158,11 +166,14 @@ export function deriveVariants(inputs: Pick<BuildInputs, 'feature' | 'merged' |
|
|
|
158
166
|
// fields — Setup → precondition (review B-02), Action → trigger, Observable →
|
|
159
167
|
// expected, Oracle → verification method. Labels never remain inside prose.
|
|
160
168
|
const manual = mode === 'manual' && m.feature.comments?.length
|
|
161
|
-
? classifyManualComments(m.feature.comments)
|
|
169
|
+
? classifyManualComments(m.feature.comments, bucketTexts(m.feature.orderedSteps, 'then'))
|
|
162
170
|
: null;
|
|
163
171
|
|
|
164
172
|
// Raw (pre-wording) step texts — these feed shapes + Gate C, never the cells.
|
|
165
|
-
|
|
173
|
+
// A manual with no numbered action lines keeps its Gherkin When steps as the trigger — the
|
|
174
|
+
// same fallback the oracle uses for Then. Taking comment actions only rendered "No action —
|
|
175
|
+
// the state is checked on page load." for a manual whose When clicks something (#810).
|
|
176
|
+
const rawTrigger = manual && manual.actions.length > 0 ? manual.actions : bucketTexts(m.feature.orderedSteps, 'when');
|
|
166
177
|
const rawOracle = manual
|
|
167
178
|
? (manual.expected.length > 0 ? manual.expected : bucketTexts(m.feature.orderedSteps, 'then'))
|
|
168
179
|
: m.resolvedExpected.filter((s) => s.bucket === 'then').map((s) => s.text);
|
|
@@ -251,10 +262,16 @@ export function deriveVariants(inputs: Pick<BuildInputs, 'feature' | 'merged' |
|
|
|
251
262
|
verification: manual?.verification.map((t) => sentenceOf(t)) ?? [],
|
|
252
263
|
};
|
|
253
264
|
|
|
254
|
-
/**
|
|
265
|
+
/**
|
|
266
|
+
* Render the display cells for one data context. Steps are worded FIRST and values
|
|
267
|
+
* substituted AFTER (#772): wording strips the DSL's `[Ref]` brackets, and a value
|
|
268
|
+
* such as `^[A-Za-z]{3}` or `[E2E-01] title` must never be mistaken for one.
|
|
269
|
+
* Preconditions (rendered at build time above) already went in this order.
|
|
270
|
+
*/
|
|
255
271
|
const renderCells = (vars: Record<string, string>): { trigger: string[]; oracle: string[]; precondition: string[]; verification: string[] } => {
|
|
256
272
|
const sub = (s: string): string => substituteDisplayVars(s, vars, runtimeVars);
|
|
257
|
-
const
|
|
273
|
+
const fill = (rendered: string): string => afterSubstitution(sub(rendered));
|
|
274
|
+
const preconditionOut = precondition.map(fill);
|
|
258
275
|
const verificationOut = base.verification.map(sub);
|
|
259
276
|
if (procedureProfile === 'sequence' && !manual) {
|
|
260
277
|
// Ordered flow (review B-03): keep the event order — actions stay numbered in
|
|
@@ -266,9 +283,9 @@ export function deriveVariants(inputs: Pick<BuildInputs, 'feature' | 'merged' |
|
|
|
266
283
|
const trigger: string[] = [];
|
|
267
284
|
const oracle: string[] = [];
|
|
268
285
|
steps.forEach((s, i) => {
|
|
269
|
-
if (i > lastActionIdx) oracle.push(renderExpected(
|
|
270
|
-
else if (s.bucket === 'then') trigger.push(`Verify: ${renderExpected(
|
|
271
|
-
else if (s.bucket === 'when') trigger.push(renderAction(
|
|
286
|
+
if (i > lastActionIdx) oracle.push(fill(renderExpected(s.text))); // trailing Then block
|
|
287
|
+
else if (s.bucket === 'then') trigger.push(`Verify: ${fill(renderExpected(s.text))}`);
|
|
288
|
+
else if (s.bucket === 'when') trigger.push(fill(renderAction(s.text)));
|
|
272
289
|
// leading given steps already live in the precondition
|
|
273
290
|
});
|
|
274
291
|
return { trigger, oracle, precondition: preconditionOut, verification: verificationOut };
|
|
@@ -277,8 +294,8 @@ export function deriveVariants(inputs: Pick<BuildInputs, 'feature' | 'merged' |
|
|
|
277
294
|
// Method/API/Header/Body lines are already reader-facing, not step DSL).
|
|
278
295
|
const trigger = apiCallsAreTrigger
|
|
279
296
|
? apiRequestLines.map(sub)
|
|
280
|
-
: rawTrigger.map((s) => renderAction(
|
|
281
|
-
const oracle = rawOracle.map((s) => renderExpected(
|
|
297
|
+
: rawTrigger.map((s) => fill(renderAction(s)));
|
|
298
|
+
const oracle = rawOracle.map((s) => fill(renderExpected(s)));
|
|
282
299
|
return {
|
|
283
300
|
// A pure-render check (Given + Then only) is checked on page load — say so
|
|
284
301
|
// in words instead of a placeholder token (review §10.3).
|
|
@@ -296,14 +313,14 @@ export function deriveVariants(inputs: Pick<BuildInputs, 'feature' | 'merged' |
|
|
|
296
313
|
const resultByLabel = new Map(resultVariants.map((rv) => [rv.nameSuffix.replace(/^ — /, ''), rv.result]));
|
|
297
314
|
rows.forEach((row, i) => {
|
|
298
315
|
const label = String(row.case ?? row.name ?? row.label ?? `row ${i + 1}`);
|
|
299
|
-
const cells = renderCells({ ...
|
|
316
|
+
const cells = renderCells({ ...rawTestData, ...rowAsStrings(row) });
|
|
300
317
|
variants.push({
|
|
301
318
|
...base,
|
|
302
319
|
ref: `${vpId}#${label}`,
|
|
303
320
|
caseLabel: label,
|
|
304
321
|
title: `${category1} — ${label}`,
|
|
305
322
|
condition: label,
|
|
306
|
-
data: resolveDataPairs(m.feature.referencedVars,
|
|
323
|
+
data: resolveDataPairs(m.feature.referencedVars, rawTestData, row, runtimeVars),
|
|
307
324
|
trigger: cells.trigger,
|
|
308
325
|
oracle: cells.oracle,
|
|
309
326
|
precondition: cells.precondition,
|
|
@@ -313,13 +330,13 @@ export function deriveVariants(inputs: Pick<BuildInputs, 'feature' | 'merged' |
|
|
|
313
330
|
});
|
|
314
331
|
});
|
|
315
332
|
} else {
|
|
316
|
-
const cells = renderCells(
|
|
333
|
+
const cells = renderCells(rawTestData);
|
|
317
334
|
variants.push({
|
|
318
335
|
...base,
|
|
319
336
|
ref: vpId,
|
|
320
337
|
title: category1,
|
|
321
338
|
condition: category1,
|
|
322
|
-
data: resolveDataPairs(m.feature.referencedVars,
|
|
339
|
+
data: resolveDataPairs(m.feature.referencedVars, rawTestData, undefined, runtimeVars),
|
|
323
340
|
trigger: cells.trigger,
|
|
324
341
|
oracle: cells.oracle,
|
|
325
342
|
precondition: cells.precondition,
|
|
@@ -351,9 +368,9 @@ function substituteDisplayVars(
|
|
|
351
368
|
if (isCapture && /\bas\s+$/i.test(text.slice(0, offset))) return m;
|
|
352
369
|
// A value the test produces at run time has no static form — name it so the
|
|
353
370
|
// tester knows to compare against what the earlier step captured.
|
|
354
|
-
if (
|
|
371
|
+
if (isRuntimeVar(key, runtimeVars)) return `the captured ${key}`;
|
|
355
372
|
if (!(key in vars)) return m; // unknown stays literal → Gate D flags it
|
|
356
|
-
return displayValue(vars[key]);
|
|
373
|
+
return displayValue(expandValue(vars[key], vars, runtimeVars, new Set([key])));
|
|
357
374
|
});
|
|
358
375
|
// A dynamic token written straight into a step (`{{$uuid}}`) never reaches
|
|
359
376
|
// displayValue — name it here so the same wording covers both routes.
|
|
@@ -423,12 +440,45 @@ export function computeRequirements(
|
|
|
423
440
|
});
|
|
424
441
|
}
|
|
425
442
|
|
|
426
|
-
|
|
443
|
+
const isRuntimeVar = (key: string, runtimeVars: Set<string>): boolean =>
|
|
444
|
+
runtimeVars.has(key) || runtimeVars.has(key.split(/[.[]/)[0]);
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Expand the `{{refs}}` inside a test-data VALUE the way the runtime's `interpolate()` does
|
|
448
|
+
* (specs/generated/test-data.ts): recursively, depth-capped against reference cycles, and a var
|
|
449
|
+
* the scenario captures at run time is named, not looked up. An unknown ref (or a cycle) stays
|
|
450
|
+
* literal, so Gate D still refuses it; a nested value renders through displayValue, as it did
|
|
451
|
+
* when this resolved one level (an empty one reads `(empty)`).
|
|
452
|
+
*
|
|
453
|
+
* Delivery used to resolve ONE level, before any scenario's runtime vars were known: both
|
|
454
|
+
* `heading_expected: "Total ({{stat}})"` with `remember … as {{stat}}`, and a plain chain
|
|
455
|
+
* (`a: "{{b}}"`, `b: "x {{c}}"`), left a token that blocked the export of a suite the runtime
|
|
456
|
+
* runs fine (#879).
|
|
457
|
+
*/
|
|
458
|
+
function expandValue(
|
|
459
|
+
value: string, vars: Record<string, string>, runtimeVars: Set<string>,
|
|
460
|
+
expanding: ReadonlySet<string> = new Set(), depth = 0,
|
|
461
|
+
): string {
|
|
462
|
+
if (depth > 10 || !value.includes('{{')) return value;
|
|
463
|
+
return value.replace(/\{\{\s*([^}\s]+)\s*\}\}/g, (m, key: string) => {
|
|
464
|
+
if (isRuntimeVar(key, runtimeVars)) return `the captured ${key}`;
|
|
465
|
+
const v = vars[key];
|
|
466
|
+
// A key already being expanded is a cycle: keep the token (Gate D names it) instead of
|
|
467
|
+
// re-entering — `a: "{{a}}{{a}}"` doubled per level and exhausted the heap.
|
|
468
|
+
if (v === undefined || v === null || expanding.has(key)) return m;
|
|
469
|
+
return displayValue(expandValue(String(v), vars, runtimeVars, new Set([...expanding, key]), depth + 1));
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Pre-resolve test-data cross-references before any scenario is known — every static level.
|
|
475
|
+
* A runtime var is not known yet and stays a token here; each scenario's cells expand it.
|
|
476
|
+
*/
|
|
427
477
|
function resolveCrossRefs(testData: Record<string, string> | null): Record<string, string> | null {
|
|
428
478
|
if (!testData) return null;
|
|
429
479
|
const out: Record<string, string> = {};
|
|
430
480
|
for (const [k, v] of Object.entries(testData)) {
|
|
431
|
-
out[k] = typeof v === 'string' && v.includes('{{') ?
|
|
481
|
+
out[k] = typeof v === 'string' && v.includes('{{') ? renderDynamicTokens(expandValue(v, testData, new Set(), new Set([k]))) : v;
|
|
432
482
|
}
|
|
433
483
|
return out;
|
|
434
484
|
}
|
|
@@ -454,6 +504,7 @@ function rowAsStrings(row: Record<string, unknown>): Record<string, string> {
|
|
|
454
504
|
* as blocked), which silently skewed the derived parent result.
|
|
455
505
|
*/
|
|
456
506
|
export function variantState(v: CoverageVariant): VariantState {
|
|
507
|
+
if (v.manualState) return v.manualState; // a carried Manual result (#891)
|
|
457
508
|
const r = v.result;
|
|
458
509
|
if (!r) return 'Pending';
|
|
459
510
|
switch (r.status) {
|
|
@@ -587,6 +638,7 @@ export function buildMatrix(inputs: BuildInputs): MatrixModel {
|
|
|
587
638
|
[v.ref, v.precondition.filter((line) => !commonPre.includes(line))])),
|
|
588
639
|
triggerDeltas: Object.fromEntries(groupVariants.map((v) =>
|
|
589
640
|
[v.ref, v.trigger.slice(prefixLen)])),
|
|
641
|
+
sharedTriggerSteps: prefixLen,
|
|
590
642
|
// Nothing shared to hoist → say what varies rather than leaving the cell blank.
|
|
591
643
|
trigger: prefixLen > 0 || groupVariants.length === 0
|
|
592
644
|
? (first?.trigger ?? []).slice(0, prefixLen)
|
|
@@ -0,0 +1,292 @@
|
|
|
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, VariantState } from './types';
|
|
23
|
+
import { rollUp } from './build';
|
|
24
|
+
import { HEADER_ROW, RESULT_COL } from './render-testcase-sheet';
|
|
25
|
+
|
|
26
|
+
const MANUAL_SHEET = 'Manual';
|
|
27
|
+
/** veryHidden: Excel's UI cannot unhide it, so a tester cannot edit the guard by accident. */
|
|
28
|
+
export const STAMP_SHEET = '_sungen_manual';
|
|
29
|
+
/** L..P — the tester-owned columns of a variant sub-row. */
|
|
30
|
+
const CARRIED_COLS = [RESULT_COL, RESULT_COL + 1, RESULT_COL + 2, RESULT_COL + 3, RESULT_COL + 4];
|
|
31
|
+
const NOTE_INDEX = 4;
|
|
32
|
+
const RESET_MARKER = '[sungen] Procedure changed since this result was recorded';
|
|
33
|
+
|
|
34
|
+
interface CarriedCell { value: ExcelJS.CellValue; numFmt?: string }
|
|
35
|
+
|
|
36
|
+
interface PriorRow { cells: CarriedCell[] }
|
|
37
|
+
|
|
38
|
+
export interface PriorManualResults {
|
|
39
|
+
rows: Map<string, PriorRow>;
|
|
40
|
+
/** Later copies of a ref already read — only the first is carried, so these are reported. */
|
|
41
|
+
duplicates: { ref: string; result: string }[];
|
|
42
|
+
/** `ref → fingerprint` from the stamp sheet; null when the file predates #756. */
|
|
43
|
+
stamps: Map<string, string> | null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ManualCarryReport {
|
|
47
|
+
/** Carried with a matching fingerprint. */
|
|
48
|
+
carried: number;
|
|
49
|
+
/** Carried from a file with no stamp — no drift check was possible. */
|
|
50
|
+
unchecked: number;
|
|
51
|
+
/** Result reset to Pending because the procedure changed. */
|
|
52
|
+
reset: string[];
|
|
53
|
+
/** Prior rows with an entry that no longer match any manual variant. */
|
|
54
|
+
notPlaced: { ref: string; result: string }[];
|
|
55
|
+
/** Prior rows with an entry that repeat a ref above them — not carried. */
|
|
56
|
+
duplicates: { ref: string; result: string }[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isFormula(v: ExcelJS.CellValue): boolean {
|
|
60
|
+
return typeof v === 'object' && v !== null && ('formula' in v || 'sharedFormula' in v);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function cellText(v: ExcelJS.CellValue): string {
|
|
64
|
+
if (v === null || v === undefined) return '';
|
|
65
|
+
if (v instanceof Date) return isNaN(v.getTime()) ? '' : v.toISOString().slice(0, 10);
|
|
66
|
+
if (typeof v === 'object') {
|
|
67
|
+
if ('richText' in v) return v.richText.map((r) => r.text).join('').trim();
|
|
68
|
+
if ('text' in v) return String(v.text).trim();
|
|
69
|
+
if ('result' in v) return cellText(v.result as ExcelJS.CellValue);
|
|
70
|
+
}
|
|
71
|
+
return String(v).trim();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* ExcelJS reads a formula's text result under a date format as an Invalid Date. Drop that
|
|
76
|
+
* cached result — Excel recomputes it on open — instead of writing NaN back into the file.
|
|
77
|
+
*/
|
|
78
|
+
function readable(v: ExcelJS.CellValue): ExcelJS.CellValue {
|
|
79
|
+
if (isFormula(v)) {
|
|
80
|
+
const { result, ...formula } = v as ExcelJS.CellFormulaValue;
|
|
81
|
+
if (result instanceof Date && isNaN(result.getTime())) return formula as ExcelJS.CellValue;
|
|
82
|
+
}
|
|
83
|
+
return v;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** A row still at the rendered default carries nothing. */
|
|
87
|
+
function isUntouched(cells: CarriedCell[]): boolean {
|
|
88
|
+
const [result, ...rest] = cells.map((c) => cellText(c.value));
|
|
89
|
+
return (result === '' || result === 'Pending') && rest.every((t) => t === '');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Variant sub-rows of a Manual sheet, in row order: `[ref, row]`. Either signal is enough:
|
|
94
|
+
* the renderer puts variants on outline level 1, and a parent's Result is a roll-up formula.
|
|
95
|
+
* Outline levels are lost by some spreadsheet tools on save, and a tester can paste a
|
|
96
|
+
* formula into a variant's Result, so relying on one alone would hide the row silently.
|
|
97
|
+
*/
|
|
98
|
+
function variantRows(ws: ExcelJS.Worksheet): [string, ExcelJS.Row][] {
|
|
99
|
+
const out: [string, ExcelJS.Row][] = [];
|
|
100
|
+
for (let r = HEADER_ROW + 1; r <= ws.rowCount; r++) {
|
|
101
|
+
const row = ws.getRow(r);
|
|
102
|
+
const ref = cellText(row.getCell(1).value);
|
|
103
|
+
if (ref && (row.outlineLevel === 1 || !isFormula(row.getCell(RESULT_COL).value))) out.push([ref, row]);
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Read the Manual results of the workbook about to be overwritten. Returns null when there is
|
|
110
|
+
* no such file. A file that exists but cannot be read THROWS: overwriting it would destroy the
|
|
111
|
+
* very record this step exists to keep.
|
|
112
|
+
*/
|
|
113
|
+
export async function readPriorManualResults(file: string): Promise<PriorManualResults | null> {
|
|
114
|
+
if (!fs.existsSync(file)) return null;
|
|
115
|
+
const wb = new ExcelJS.Workbook();
|
|
116
|
+
try {
|
|
117
|
+
await wb.xlsx.readFile(file);
|
|
118
|
+
} catch (e) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`cannot read ${file} to carry its Manual results (${(e as Error).message}). ` +
|
|
121
|
+
'Close it in Excel, or move it aside to start a fresh workbook, then re-run.',
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Every workbook sungen writes has a Manual sheet, even an empty one. Without it the
|
|
126
|
+
// tester's results cannot be located — carrying nothing would look like a clean run.
|
|
127
|
+
const ws = wb.getWorksheet(MANUAL_SHEET);
|
|
128
|
+
if (!ws) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`${file} has no "${MANUAL_SHEET}" sheet, so its Manual results cannot be carried. ` +
|
|
131
|
+
`Rename the sheet back to "${MANUAL_SHEET}", or move the file aside to start a fresh workbook, then re-run.`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const rows = new Map<string, PriorRow>();
|
|
136
|
+
const duplicates: PriorManualResults['duplicates'] = [];
|
|
137
|
+
for (const [ref, row] of variantRows(ws)) {
|
|
138
|
+
const cells = CARRIED_COLS.map((c) => {
|
|
139
|
+
const cell = row.getCell(c);
|
|
140
|
+
return { value: readable(cell.value), ...(cell.numFmt ? { numFmt: cell.numFmt } : {}) };
|
|
141
|
+
});
|
|
142
|
+
if (!rows.has(ref)) rows.set(ref, { cells });
|
|
143
|
+
else if (!isUntouched(cells)) duplicates.push({ ref, result: cellText(cells[0].value) || '(no result)' });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const stampWs = wb.getWorksheet(STAMP_SHEET);
|
|
147
|
+
let stamps: Map<string, string> | null = null;
|
|
148
|
+
if (stampWs) {
|
|
149
|
+
stamps = new Map();
|
|
150
|
+
stampWs.eachRow((row, n) => {
|
|
151
|
+
if (n > 1) stamps!.set(cellText(row.getCell(1).value), cellText(row.getCell(2).value));
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return { rows, duplicates, stamps };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Note for a reset row: what was invalidated, then whatever the tester had written. A note
|
|
159
|
+
* that is a hyperlink (evidence, a defect ticket) stays one — only its text gains the marker.
|
|
160
|
+
*/
|
|
161
|
+
function resetNote(prior: CarriedCell[]): ExcelJS.CellValue {
|
|
162
|
+
const [result, date, executor] = prior.map((c) => cellText(c.value));
|
|
163
|
+
const was = [result, date, executor].filter(Boolean).join(' · ');
|
|
164
|
+
const header = `${RESET_MARKER} — previous: ${was}. Re-run required.`;
|
|
165
|
+
const note = prior[NOTE_INDEX].value;
|
|
166
|
+
const own = cellText(note);
|
|
167
|
+
if (typeof note === 'object' && note !== null && 'hyperlink' in note) {
|
|
168
|
+
return { ...note, text: `${header}\n${own}` };
|
|
169
|
+
}
|
|
170
|
+
return own ? `${header}\n${own}` : header;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* What happens to each Manual variant's prior row: `carry` its cells, or `reset` it to Pending
|
|
175
|
+
* because the procedure changed since the result was recorded. A row with nothing recorded has
|
|
176
|
+
* no entry. The ONE decision both the model (totals, #891) and the workbook cells read.
|
|
177
|
+
*/
|
|
178
|
+
export function planManualCarry(model: MatrixModel, prior: PriorManualResults | null): Map<string, 'carry' | 'reset'> {
|
|
179
|
+
const plan = new Map<string, 'carry' | 'reset'>();
|
|
180
|
+
if (!prior) return plan;
|
|
181
|
+
for (const v of model.items.flatMap((i) => i.variants)) {
|
|
182
|
+
if (v.mode !== 'manual') continue;
|
|
183
|
+
const before = prior.rows.get(v.ref);
|
|
184
|
+
if (!before || isUntouched(before.cells)) continue;
|
|
185
|
+
const stamped = prior.stamps?.get(v.ref);
|
|
186
|
+
// Only a recorded result is a claim about the procedure. A row with no result (a
|
|
187
|
+
// blocker note, or one already reset) is carried as-is, so a second procedure
|
|
188
|
+
// change does not stack a second marker on it.
|
|
189
|
+
const hasResult = !['', 'Pending'].includes(cellText(before.cells[0].value));
|
|
190
|
+
plan.set(v.ref, stamped !== undefined && stamped !== v.fingerprint && hasResult ? 'reset' : 'carry');
|
|
191
|
+
}
|
|
192
|
+
return plan;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** A typed Result → its state word, case-insensitively (Excel's COUNTIF ignores case too). */
|
|
196
|
+
const STATE_BY_LOWER = new Map<string, VariantState>(
|
|
197
|
+
(['Passed', 'Failed', 'Blocked', 'Pending', 'N/A'] as VariantState[]).map((s) => [s.toLowerCase(), s]),
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Put the carried Manual results into the MODEL, before anything is rendered or counted.
|
|
202
|
+
*
|
|
203
|
+
* The workbook's totals, Overview and group roll-ups are formulas with a cached result computed
|
|
204
|
+
* from the model, and the CSV and the CLI summary read the model too. Carrying only the cells
|
|
205
|
+
* after rendering left all of them counting the recorded Manual results as Pending: a preview
|
|
206
|
+
* of the file, and the CLI, reported "5 Pending" for cases the tester had run (#891).
|
|
207
|
+
* A reset row counts as Pending, as its cell reads. A Result that is not a state word (a tester's
|
|
208
|
+
* "OK") counts as Pending too; the cell keeps the text, and its date still reaches the CSV.
|
|
209
|
+
* Idempotent: every Manual variant's carried fields are cleared first, so the result depends only
|
|
210
|
+
* on `prior`.
|
|
211
|
+
*/
|
|
212
|
+
export function applyManualCarry(model: MatrixModel, prior: PriorManualResults | null): void {
|
|
213
|
+
const plan = planManualCarry(model, prior);
|
|
214
|
+
for (const item of model.items) {
|
|
215
|
+
let touched = false;
|
|
216
|
+
for (const v of item.variants) {
|
|
217
|
+
if (v.mode !== 'manual') continue;
|
|
218
|
+
if (v.manualState !== undefined || v.manualDate !== undefined) touched = true;
|
|
219
|
+
delete v.manualState;
|
|
220
|
+
delete v.manualDate;
|
|
221
|
+
const decision = plan.get(v.ref);
|
|
222
|
+
if (!decision) continue;
|
|
223
|
+
touched = true;
|
|
224
|
+
if (decision === 'reset') { v.manualState = 'Pending'; continue; }
|
|
225
|
+
const [result, date] = prior!.rows.get(v.ref)!.cells.map((c) => cellText(c.value));
|
|
226
|
+
if (date) v.manualDate = date;
|
|
227
|
+
v.manualState = STATE_BY_LOWER.get(result.toLowerCase()) ?? 'Pending';
|
|
228
|
+
}
|
|
229
|
+
if (touched) {
|
|
230
|
+
const { result, counts } = rollUp(item.variants);
|
|
231
|
+
item.result = result;
|
|
232
|
+
item.resultCounts = counts;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Carry `prior` into the freshly rendered `wb`, then stamp every manual variant's fingerprint.
|
|
239
|
+
* Only the Manual sheet's variant sub-rows are touched.
|
|
240
|
+
*/
|
|
241
|
+
export function carryManualResults(
|
|
242
|
+
wb: ExcelJS.Workbook,
|
|
243
|
+
model: MatrixModel,
|
|
244
|
+
prior: PriorManualResults | null,
|
|
245
|
+
): ManualCarryReport {
|
|
246
|
+
const plan = planManualCarry(model, prior);
|
|
247
|
+
const report: ManualCarryReport = { carried: 0, unchecked: 0, reset: [], notPlaced: [], duplicates: prior?.duplicates ?? [] };
|
|
248
|
+
const fingerprints = new Map(
|
|
249
|
+
model.items.flatMap((i) => i.variants).filter((v) => v.mode === 'manual').map((v) => [v.ref, v.fingerprint]),
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
const ws = wb.getWorksheet(MANUAL_SHEET);
|
|
253
|
+
const placed = new Set<string>();
|
|
254
|
+
if (ws && prior) {
|
|
255
|
+
for (const [ref, row] of variantRows(ws)) {
|
|
256
|
+
const before = prior.rows.get(ref);
|
|
257
|
+
if (!before) continue;
|
|
258
|
+
placed.add(ref);
|
|
259
|
+
const decision = plan.get(ref);
|
|
260
|
+
if (!decision) continue;
|
|
261
|
+
|
|
262
|
+
const stamped = prior.stamps?.get(ref);
|
|
263
|
+
if (decision === 'reset') {
|
|
264
|
+
row.getCell(RESULT_COL).value = 'Pending';
|
|
265
|
+
row.getCell(CARRIED_COLS[NOTE_INDEX]).value = resetNote(before.cells);
|
|
266
|
+
report.reset.push(ref);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
CARRIED_COLS.forEach((c, i) => {
|
|
271
|
+
const cell = row.getCell(c);
|
|
272
|
+
cell.value = before.cells[i].value;
|
|
273
|
+
if (before.cells[i].numFmt) cell.numFmt = before.cells[i].numFmt!;
|
|
274
|
+
});
|
|
275
|
+
if (stamped === undefined) report.unchecked++;
|
|
276
|
+
else report.carried++;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (prior) {
|
|
281
|
+
for (const [ref, before] of prior.rows) {
|
|
282
|
+
if (!placed.has(ref) && !isUntouched(before.cells)) {
|
|
283
|
+
report.notPlaced.push({ ref, result: cellText(before.cells[0].value) || '(no result)' });
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const stampWs = wb.addWorksheet(STAMP_SHEET, { state: 'veryHidden' });
|
|
289
|
+
stampWs.addRow(['ref', 'fingerprint']);
|
|
290
|
+
for (const [ref, fp] of fingerprints) stampWs.addRow([ref, fp]);
|
|
291
|
+
return report;
|
|
292
|
+
}
|
|
@@ -16,7 +16,9 @@ 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 { applyManualCarry, carryManualResults, ManualCarryReport, PriorManualResults, readPriorManualResults } from './carry-manual-results';
|
|
21
|
+
export { readPriorManualResults } from './carry-manual-results';
|
|
20
22
|
import { loadPerfConfig, perfVerdict } from '../../harness/perf';
|
|
21
23
|
import { loadDeliveryMap, writeDeliveryMap } from './map-loader';
|
|
22
24
|
import { mapContentFingerprint } from './fingerprint';
|
|
@@ -25,6 +27,7 @@ import { renderMatrixXlsx } from './render-xlsx';
|
|
|
25
27
|
import { renderMatrixCsv } from './render-csv';
|
|
26
28
|
import { DeliveryMap, MatrixFinding, MatrixModel } from './types';
|
|
27
29
|
import { ApiCatalogEntry } from '../types';
|
|
30
|
+
import { RunEnvironment } from '../run-environment';
|
|
28
31
|
|
|
29
32
|
export interface MatrixTargetPaths {
|
|
30
33
|
cwd: string;
|
|
@@ -45,6 +48,8 @@ export interface MatrixTargetPaths {
|
|
|
45
48
|
specMdFile?: string;
|
|
46
49
|
resultsPath: string | null;
|
|
47
50
|
mapFile: string;
|
|
51
|
+
/** Executor + environment of the run (#850) — absent for gates-only callers (inspect, approve). */
|
|
52
|
+
runEnv?: RunEnvironment;
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
function readSpecText(paths: MatrixTargetPaths): string {
|
|
@@ -90,6 +95,7 @@ export function loadMatrixModel(paths: MatrixTargetPaths): MatrixLoadResult {
|
|
|
90
95
|
specText: readSpecText(paths),
|
|
91
96
|
apiCatalog: catalogFor(paths),
|
|
92
97
|
});
|
|
98
|
+
model.runEnv = paths.runEnv;
|
|
93
99
|
// Perf verdict (#569): budgets are config (qa/perf.yaml), samples are the run's
|
|
94
100
|
// per-test durations. Advisory — reported here because delivery is where runs
|
|
95
101
|
// are read; the audit's score stays a pure function of the design artifacts.
|
|
@@ -163,13 +169,27 @@ export async function writeMatrixDeliverables(
|
|
|
163
169
|
paths: MatrixTargetPaths,
|
|
164
170
|
model: MatrixModel,
|
|
165
171
|
format: MatrixFormat = 'xlsx',
|
|
166
|
-
|
|
167
|
-
|
|
172
|
+
/** The prior workbook's Manual results, when the caller already read them; read here if absent. */
|
|
173
|
+
priorRead?: PriorManualResults | null,
|
|
174
|
+
): Promise<{ csvPath?: string; xlsxPath?: string; manualCarry?: ManualCarryReport }> {
|
|
175
|
+
const out: { csvPath?: string; xlsxPath?: string; manualCarry?: ManualCarryReport } = {};
|
|
176
|
+
const xlsx = format === 'xlsx' || format === 'both';
|
|
177
|
+
// Read the workbook about to be overwritten BEFORE writing anything, so the tester's Manual
|
|
178
|
+
// results survive (#756) and a refusal leaves no half-written target behind.
|
|
179
|
+
// A CSV-only export overwrites no workbook, so an unreadable one is no reason to refuse it — but a
|
|
180
|
+
// readable one still holds the Manual results the CSV should show.
|
|
181
|
+
const prior = priorRead !== undefined ? priorRead
|
|
182
|
+
: xlsx ? await readPriorManualResults(deliverableXlsxPath(paths.cwd, paths.unit))
|
|
183
|
+
: await readPriorManualResults(deliverableXlsxPath(paths.cwd, paths.unit)).catch(() => null);
|
|
184
|
+
// Into the model first: the CSV, every cached total and roll-up, and the caller's summary
|
|
185
|
+
// all read it (#891).
|
|
186
|
+
applyManualCarry(model, prior);
|
|
168
187
|
if (format === 'csv' || format === 'both') {
|
|
169
188
|
out.csvPath = writeCsv(paths.cwd, paths.unit, renderMatrixCsv(model));
|
|
170
189
|
}
|
|
171
|
-
if (
|
|
190
|
+
if (xlsx) {
|
|
172
191
|
const wb = renderMatrixXlsx(model, getPackageVersion());
|
|
192
|
+
out.manualCarry = carryManualResults(wb, model, prior);
|
|
173
193
|
out.xlsxPath = await writeXlsx(paths.cwd, paths.unit, wb);
|
|
174
194
|
}
|
|
175
195
|
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)` });
|
|
@@ -78,7 +78,7 @@ export function renderMatrixCsv(model: MatrixModel): string {
|
|
|
78
78
|
v.mode === 'manual' ? 'Manual' : 'Auto',
|
|
79
79
|
v.traces.join(' '),
|
|
80
80
|
variantState(v),
|
|
81
|
-
isoDate(v.result?.startTime),
|
|
81
|
+
v.manualDate ?? isoDate(v.result?.startTime),
|
|
82
82
|
// Redact BEFORE clipping — a clip boundary must never split a secret in
|
|
83
83
|
// a way that lets its prefix through. Same denylist as the Note column.
|
|
84
84
|
v.result?.error ? clipNoteField(redactSecrets(String(v.result.error), model.secretValues)) : '',
|
|
@@ -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
|
];
|
|
@@ -74,6 +74,9 @@ export function projectItems(model: MatrixModel, mode: 'auto' | 'manual'): Deliv
|
|
|
74
74
|
|
|
75
75
|
export function renderMatrixXlsx(model: MatrixModel, sungenVersion?: string): ExcelJS.Workbook {
|
|
76
76
|
const wb = new ExcelJS.Workbook();
|
|
77
|
+
// Every total and roll-up is a formula with a cached result. The cache is what a preview
|
|
78
|
+
// (GitHub, Drive, QuickLook) shows; a spreadsheet app recomputes on open as well (#891).
|
|
79
|
+
wb.calcProperties.fullCalcOnLoad = true;
|
|
77
80
|
const sheets: SheetProjection[] = [
|
|
78
81
|
{ sheetName: 'Auto', items: projectItems(model, 'auto') },
|
|
79
82
|
{ sheetName: 'Manual', items: projectItems(model, 'manual') },
|