@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
|
@@ -147,6 +147,11 @@ export interface CoverageVariant {
|
|
|
147
147
|
fingerprint: string;
|
|
148
148
|
/** Execution result, when the run report has one for this variant. */
|
|
149
149
|
result?: PlaywrightResult;
|
|
150
|
+
/** A Manual variant's result as the tester recorded it in the previous workbook, carried
|
|
151
|
+
* BEFORE rendering so every total, roll-up, the CSV and the CLI count it (#891). */
|
|
152
|
+
manualState?: VariantState;
|
|
153
|
+
/** The Executed Date the tester recorded with it (yyyy-mm-dd, or as typed). */
|
|
154
|
+
manualDate?: string;
|
|
150
155
|
}
|
|
151
156
|
|
|
152
157
|
export type ItemResult = 'passed' | 'failed' | 'blocked' | 'partial' | 'not_run' | 'na';
|
|
@@ -176,8 +181,12 @@ export interface DeliveryItem {
|
|
|
176
181
|
/** Preconditions shared by EVERY variant (the intersection) — written once here. */
|
|
177
182
|
precondition: string[];
|
|
178
183
|
/** Leading trigger steps shared by every variant — written once here; each
|
|
179
|
-
* variant renders only its remaining steps (`triggerDeltas`).
|
|
184
|
+
* variant renders only its remaining steps (`triggerDeltas`). When nothing is shared this
|
|
185
|
+
* holds a generated "the action differs…" note instead, which is NOT a step. */
|
|
180
186
|
trigger: string[];
|
|
187
|
+
/** How many of `trigger` are real steps hoisted from the variants (0 when it is only the
|
|
188
|
+
* note) — the variants' steps are numbered from this + 1 (#848). */
|
|
189
|
+
sharedTriggerSteps: number;
|
|
181
190
|
variants: CoverageVariant[];
|
|
182
191
|
/** variant ref → its precondition lines that are not on the parent. */
|
|
183
192
|
preconditionDeltas: Record<string, string[]>;
|
|
@@ -253,6 +262,9 @@ export interface MatrixModel {
|
|
|
253
262
|
* the deliverable ships to the client.
|
|
254
263
|
*/
|
|
255
264
|
secretValues: string[];
|
|
265
|
+
/** Who ran the suite and against what — fills Test Executor / Test Environment on rows that
|
|
266
|
+
* ran (#850). Absent when the caller has no run context (gates-only paths). */
|
|
267
|
+
runEnv?: import('../run-environment').RunEnvironment;
|
|
256
268
|
}
|
|
257
269
|
|
|
258
270
|
/** 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
|
}
|
|
@@ -161,6 +161,7 @@ export function loadPlaywrightReport(reportPath: string): Map<string, Playwright
|
|
|
161
161
|
errorMessages: distinct.length > 0 ? distinct : undefined,
|
|
162
162
|
tracePath: trace,
|
|
163
163
|
projectName: test?.projectName,
|
|
164
|
+
attempts: results.length || undefined,
|
|
164
165
|
});
|
|
165
166
|
}
|
|
166
167
|
return result;
|
|
@@ -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
|
+
}
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { FeatureMetadata, OrderedStep, ScenarioMetadata, SpecFileData, SpecTest } from './types';
|
|
12
|
-
import { isStepsBaseScenario, isSampleScaffoldScenario, extractTestcaseType } from './feature-parser';
|
|
12
|
+
import { isStepsBaseScenario, isSampleScaffoldScenario, isHookScenario, 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
|
/**
|
|
@@ -104,6 +139,7 @@ export function mergeFeatureAndSpec(
|
|
|
104
139
|
for (const scenario of feature.scenarios) {
|
|
105
140
|
if (isStepsBaseScenario(scenario)) continue; // skip @steps base scenarios
|
|
106
141
|
if (isSampleScaffoldScenario(scenario)) continue; // skip default sample
|
|
142
|
+
if (isHookScenario(scenario)) continue; // skip @beforeAll/@afterEach/@afterAll hooks
|
|
107
143
|
|
|
108
144
|
const specTest = findMatchingSpecTest(scenario, spec.tests, feature.scenarios);
|
|
109
145
|
const base = scenario.extendsName ? stepsBaseByName.get(scenario.extendsName) : undefined;
|
|
@@ -147,7 +183,16 @@ export function mergeFeatureAndSpec(
|
|
|
147
183
|
const manualParsed =
|
|
148
184
|
extractTestcaseType(scenario.tags) === 'Manual' && scenario.comments?.length
|
|
149
185
|
? parseManualComments(scenario.comments)
|
|
150
|
-
: { steps: [] as OrderedStep[], expected: [] as OrderedStep[] };
|
|
186
|
+
: { steps: [] as OrderedStep[], expected: [] as OrderedStep[], unlabeledOracle: null };
|
|
187
|
+
// Never drop a manual oracle (#810): with no labelled Observable/Oracle line and no Then that
|
|
188
|
+
// checks more than presence, the unlabelled `Tester verifies:` prose IS what the case checks —
|
|
189
|
+
// export it rather than let a presence placeholder stand in for it. A real Then keeps the cell.
|
|
190
|
+
const unlabeledOracle = !manualParsed.expected.length && onlyPresenceAssertions(ownAssertions.map((s) => s.text))
|
|
191
|
+
? manualParsed.unlabeledOracle
|
|
192
|
+
: null;
|
|
193
|
+
const manualExpected: OrderedStep[] = unlabeledOracle
|
|
194
|
+
? [{ text: unlabeledOracle, bucket: 'then' }]
|
|
195
|
+
: manualParsed.expected;
|
|
151
196
|
|
|
152
197
|
result.push({
|
|
153
198
|
feature: scenario,
|
|
@@ -158,8 +203,9 @@ export function mergeFeatureAndSpec(
|
|
|
158
203
|
...ownActions,
|
|
159
204
|
...manualParsed.steps,
|
|
160
205
|
],
|
|
161
|
-
resolvedExpected: [...ownAssertions, ...
|
|
206
|
+
resolvedExpected: [...ownAssertions, ...manualExpected],
|
|
162
207
|
resolvedAnchorActionCount: baseLen + ownActions.length,
|
|
208
|
+
...(unlabeledOracle ? { unlabeledManualOracle: unlabeledOracle } : {}),
|
|
163
209
|
});
|
|
164
210
|
}
|
|
165
211
|
|
|
@@ -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
|
-
}
|
package/src/exporters/types.ts
CHANGED
|
@@ -95,6 +95,9 @@ export interface ScenarioMetadata {
|
|
|
95
95
|
* as Gherkin steps — the merger parses these into Steps/Expected for the report.
|
|
96
96
|
*/
|
|
97
97
|
comments?: string[];
|
|
98
|
+
/** Set for a `@beforeAll` / `@afterEach` / `@afterAll` scenario — a hook the compiler emits as
|
|
99
|
+
* `test.beforeAll()` etc., never a test case (#880). */
|
|
100
|
+
hookType?: 'beforeAll' | 'afterEach' | 'afterAll';
|
|
98
101
|
}
|
|
99
102
|
|
|
100
103
|
/**
|
|
@@ -134,6 +137,8 @@ export interface PlaywrightResult {
|
|
|
134
137
|
errorMessages?: string[];
|
|
135
138
|
tracePath?: string;
|
|
136
139
|
projectName?: string;
|
|
140
|
+
/** How many attempts the test took (retries + 1). A pass with attempts > 1 is flaky. */
|
|
141
|
+
attempts?: number;
|
|
137
142
|
}
|
|
138
143
|
|
|
139
144
|
/**
|
|
@@ -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);
|
|
@@ -154,11 +154,18 @@ export class GherkinParser {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
* and before scenario i+1's header (its first tag line, or its `Scenario:` line). The
|
|
160
|
-
* leading `# ` is stripped. Used to surface a @manual scenario's tester-procedure comments
|
|
157
|
+
* Attach top-level Gherkin comments to the scenario they describe. The leading `# ` is
|
|
158
|
+
* stripped. Used to surface a @manual scenario's tester procedure and `# MANUAL:` reason
|
|
161
159
|
* (its only "steps") in the delivery report.
|
|
160
|
+
*
|
|
161
|
+
* A scenario owns:
|
|
162
|
+
* • its LEADING block — the comment lines directly above its header with no blank line in
|
|
163
|
+
* between (and any comment between its tag lines and `Scenario:`), as long as they sit
|
|
164
|
+
* after the previous scenario's last step. `# MANUAL (…): reason` written above the tags
|
|
165
|
+
* belongs to the scenario below it; it used to range onto the scenario ABOVE, where the
|
|
166
|
+
* manual parser appended it to that scenario's last oracle line (#810);
|
|
167
|
+
* • its BODY — every other comment after its `Scenario:` line and before the next
|
|
168
|
+
* scenario's leading block / header.
|
|
162
169
|
*/
|
|
163
170
|
private attachScenarioComments(
|
|
164
171
|
doc: Messages.GherkinDocument,
|
|
@@ -167,22 +174,32 @@ export class GherkinParser {
|
|
|
167
174
|
): void {
|
|
168
175
|
const comments = doc.comments || [];
|
|
169
176
|
if (!comments.length) return;
|
|
170
|
-
const
|
|
171
|
-
.
|
|
172
|
-
|
|
177
|
+
const lineOf = new Map<number, string>(
|
|
178
|
+
comments.map((cm) => [cm.location.line, cm.text.replace(/^\s*#\s?/, '').replace(/\s+$/, '')]),
|
|
179
|
+
);
|
|
173
180
|
// Same filter/order as parseScenarios → indices line up with `scenarios`.
|
|
181
|
+
const scenChildren = feature.children.filter((c) => c.scenario).map((c) => c.scenario!);
|
|
182
|
+
const background = feature.children.find((c) => c.background)?.background;
|
|
183
|
+
const headerLine = (sc: Messages.Scenario): number =>
|
|
184
|
+
Math.min(sc.location.line, ...(sc.tags || []).map((t) => t.location.line));
|
|
185
|
+
|
|
186
|
+
// Leading block of scenario i: [firstLeading, sc.location.line).
|
|
187
|
+
const leadingStart = scenChildren.map((sc, i) => {
|
|
188
|
+
const prevEnd = i > 0 ? bodyEndLine(scenChildren[i - 1]) : background ? bodyEndLine(background) : feature.location.line;
|
|
189
|
+
let first = headerLine(sc);
|
|
190
|
+
while (lineOf.has(first - 1) && first - 1 > prevEnd) first--;
|
|
191
|
+
return first;
|
|
192
|
+
});
|
|
193
|
+
|
|
174
194
|
for (let i = 0; i < scenChildren.length && i < scenarios.length; i++) {
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
.filter((cm) => cm.location.line > startLine && cm.location.line < endLine)
|
|
184
|
-
.map((cm) => cm.text.replace(/^\s*#\s?/, '').replace(/\s+$/, ''));
|
|
185
|
-
if (body.length) scenarios[i].comments = body;
|
|
195
|
+
const from = leadingStart[i];
|
|
196
|
+
const to = i + 1 < scenChildren.length ? leadingStart[i + 1] : Number.POSITIVE_INFINITY;
|
|
197
|
+
const own = comments
|
|
198
|
+
.map((cm) => cm.location.line)
|
|
199
|
+
.filter((line) => line >= from && line < to)
|
|
200
|
+
.sort((a, b) => a - b)
|
|
201
|
+
.map((line) => lineOf.get(line)!);
|
|
202
|
+
if (own.length) scenarios[i].comments = own;
|
|
186
203
|
}
|
|
187
204
|
}
|
|
188
205
|
|
|
@@ -360,3 +377,18 @@ export class GherkinParser {
|
|
|
360
377
|
return files;
|
|
361
378
|
}
|
|
362
379
|
}
|
|
380
|
+
|
|
381
|
+
/** Last source line a scenario/background occupies: its steps, their data tables/doc strings, examples. */
|
|
382
|
+
function bodyEndLine(block: Messages.Scenario | Messages.Background): number {
|
|
383
|
+
let end = block.location.line;
|
|
384
|
+
for (const step of block.steps) {
|
|
385
|
+
end = Math.max(end, step.location.line);
|
|
386
|
+
for (const row of step.dataTable?.rows ?? []) end = Math.max(end, row.location.line);
|
|
387
|
+
if (step.docString) end = Math.max(end, step.docString.location.line + step.docString.content.split('\n').length + 1);
|
|
388
|
+
}
|
|
389
|
+
for (const ex of ('examples' in block ? block.examples : [])) {
|
|
390
|
+
end = Math.max(end, ex.location.line);
|
|
391
|
+
for (const row of [...(ex.tableHeader ? [ex.tableHeader] : []), ...ex.tableBody]) end = Math.max(end, row.location.line);
|
|
392
|
+
}
|
|
393
|
+
return end;
|
|
394
|
+
}
|
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
export interface AuthGroup {
|
|
7
7
|
authRole?: string;
|
|
8
|
+
noAuth?: boolean;
|
|
8
9
|
scenarios: string[];
|
|
10
|
+
/** The feature's `@afterEach`, scoped to this group — set when the feature mixes `@no-auth` with
|
|
11
|
+
* other groups, so the hook never runs on an anonymous test's empty session (#815). */
|
|
12
|
+
afterEach?: string;
|
|
9
13
|
}
|
|
10
14
|
|
|
11
15
|
/**
|
|
@@ -42,7 +46,7 @@ export interface TestFileData {
|
|
|
42
46
|
runtimeData?: boolean; // --runtime-data flag: testData.get() instead of hardcoded values
|
|
43
47
|
screenName?: string; // Screen name for TestDataLoader.load()
|
|
44
48
|
featureFileName?: string; // Feature file name for TestDataLoader.load()
|
|
45
|
-
isParallel?: boolean; //
|
|
49
|
+
isParallel?: boolean; // !@serial: fresh page per test. Parallel is the default; @parallel only names it
|
|
46
50
|
flowMode?: boolean; // flow: cross-screen → longer per-test timeout (bug #243)
|
|
47
51
|
platform?: string; // mobile @platform:* value (mobile|android|ios) → `// sungen:platform=` marker for wdio per-OS routing
|
|
48
52
|
cleanup?: { overlay?: boolean; forms?: boolean; scroll?: boolean; storage?: boolean };
|
|
@@ -65,7 +69,7 @@ export interface ScenarioData {
|
|
|
65
69
|
scenarioName: string;
|
|
66
70
|
steps: Array<{ comment?: string; code: string }>;
|
|
67
71
|
authRole?: string; // Auth role for storage state
|
|
68
|
-
isParallel?: boolean; //
|
|
72
|
+
isParallel?: boolean; // !@serial: use fresh page from fixture
|
|
69
73
|
tags?: string; // Pass-through tags for Playwright { tag: [...] }, e.g. "'@smoke', '@high'"
|
|
70
74
|
casesDataset?: string; // @cases:<dataset> — emit one test() per row of the runtime dataset
|
|
71
75
|
resetTier?: string; // Mobile (appium): scenario-level `@reset:` override; raises (never lowers) the feature tier for this scenario only
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// Persistence oracles (dismissals, pins) hinge on what survives a reinstall.
|
|
3
3
|
await (async () => {
|
|
4
4
|
const __pkg = __resolveAppId();
|
|
5
|
-
|
|
6
|
-
const __appPath =
|
|
5
|
+
// Same reader the reset tiers use — one place decides what "the session has a build file" means.
|
|
6
|
+
const __appPath = __appBuildPath();
|
|
7
7
|
if (!__appPath) {
|
|
8
8
|
throw new Error('reinstall the app: the session must install from a build file — set APP_APK (Android) / IOS_APP (iOS) so the appium:app capability is present.');
|
|
9
9
|
}
|
|
@@ -9,12 +9,18 @@
|
|
|
9
9
|
Flutter screen; UiSelector has no OR, hence the xpath. iOS scatters it across
|
|
10
10
|
label/value, which the predicate already ORs.
|
|
11
11
|
|
|
12
|
+
SCOPE (#747): an `in [Parent] <type>` clause chains this under the parent element. The android
|
|
13
|
+
xpath must go RELATIVE (`.//`) to do so — a child xpath beginning with `//` is ABSOLUTE in
|
|
14
|
+
Appium even when chained, so keeping it would emit code that LOOKS scoped and searches the whole
|
|
15
|
+
screen, which is the same silent widening the scope clause exists to prevent. The iOS predicate
|
|
16
|
+
carries no such trap and chains as written.
|
|
17
|
+
|
|
12
18
|
The expected value must stay a standalone quoted literal so the runtime-data transform
|
|
13
19
|
can swap it for testData.get(...). --}}
|
|
14
20
|
{
|
|
15
21
|
const __expected = String('{{escapeQuotes dataValue}}').trim();
|
|
16
22
|
const __sel = driver.isIOS
|
|
17
23
|
? `-ios predicate string:label == "${__expected}" OR value == "${__expected}"`
|
|
18
|
-
:
|
|
19
|
-
await expect($(__sel)).toBeDisplayed();
|
|
24
|
+
: `{{#if parentLocator}}.{{/if}}//*[@text="${__expected}" or @content-desc="${__expected}"]`;
|
|
25
|
+
await expect({{#if parentLocator}}{{parentLocator}}.$(__sel){{else}}$(__sel){{/if}}).toBeDisplayed();
|
|
20
26
|
}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
{{!-- Role variable, visible (mobile best-effort): ARIA role has no native-mobile analog — reuse
|
|
2
2
|
the role→accessibility-id mapping in appium-selector.hbs (`~name`) when a static name
|
|
3
3
|
exists; the resolver already folds dataValue into name when there is none, so this is only
|
|
4
|
-
the fallback for a caller that omits both.
|
|
4
|
+
the fallback for a caller that omits both.
|
|
5
|
+
|
|
6
|
+
SCOPE (#747): an `in [Parent] <type>` clause chains this under the parent element. Both
|
|
7
|
+
branches locate by accessibility id / predicate, neither of which carries the absolute-xpath
|
|
8
|
+
trap the label-value twin has to work around. --}}
|
|
5
9
|
{{#if name}}
|
|
6
|
-
await expect($('~{{escapeQuotes name}}')).toBeDisplayed();
|
|
10
|
+
await expect({{#if parentLocator}}{{parentLocator}}.$('~{{escapeQuotes name}}'){{else}}$('~{{escapeQuotes name}}'){{/if}}).toBeDisplayed();
|
|
7
11
|
{{else}}
|
|
8
12
|
{
|
|
9
13
|
const __expected = String('{{escapeQuotes dataValue}}').trim();
|
|
10
14
|
const __sel = driver.isIOS ? `-ios predicate string:label == "${__expected}" OR value == "${__expected}"` : `android=new UiSelector().text("${__expected}")`;
|
|
11
|
-
await expect($(__sel)).toBeDisplayed();
|
|
15
|
+
await expect({{#if parentLocator}}{{parentLocator}}.$(__sel){{else}}$(__sel){{/if}}).toBeDisplayed();
|
|
12
16
|
}
|
|
13
17
|
{{/if}}
|
|
@@ -502,9 +502,36 @@ describe('{{featureName}}', () => {
|
|
|
502
502
|
// lose the escalation between them — SG-W024 already reports that shape when tiers are involved.)
|
|
503
503
|
let __escalateAfter: string | null = null;
|
|
504
504
|
|
|
505
|
+
// `@known-defect` marks a scenario that is EXPECTED to be red — it asserts a live defect. The
|
|
506
|
+
// scaffolded /sungen:run-test command tells authors exactly that ("expected-red scenarios stay red;
|
|
507
|
+
// never 'fix' them"), so the ladder honours it here: such a failure is the suite working as
|
|
508
|
+
// designed, not evidence of leftover state. Arming escalation off it would make the NEXT scenario
|
|
509
|
+
// pay a harder reset for nothing — on every run, forever, because the defect is known.
|
|
510
|
+
//
|
|
511
|
+
// Read off the TITLE because `@known-defect` is a pass-through tag: appium appends it there as the
|
|
512
|
+
// `[@a @b]` suffix (which is what keeps it greppable via `--grep`), and that is how the field
|
|
513
|
+
// report shows it. Anchored to that trailing group, so a scenario merely NAMED `… known-defect …`
|
|
514
|
+
// cannot exempt itself — the same reason __tierFor refuses to strip a trailing `[Selector]` ref.
|
|
515
|
+
//
|
|
516
|
+
// Matched as a whole TAG TOKEN, never as a substring. `@known-defect-list` is a DIFFERENT tag, and
|
|
517
|
+
// reading it as this one would silently stop the next scenario's real dirty-state failure from
|
|
518
|
+
// getting the escalated reset it needs — with no log line at all, because "never armed" is not the
|
|
519
|
+
// "capped" path and prints nothing. The `:<ref>` parameter form is accepted because that is what
|
|
520
|
+
// the rest of the vocabulary does (`@manual:M10`, `@reset:data`).
|
|
521
|
+
//
|
|
522
|
+
// Caveat, inherited rather than introduced: a @cases scenario builds its title at runtime as
|
|
523
|
+
// `name — <row label>`, so a row LABEL that itself ends in `[@…]` would be read as the tag group.
|
|
524
|
+
// Same trailing-bracket ambiguity __tierFor accepts for a `[Selector]` ref.
|
|
525
|
+
function __isExpectedFail(title: string): boolean {
|
|
526
|
+
const tagGroup = title.match(/ \[@[^\]]*\]$/);
|
|
527
|
+
if (!tagGroup) return false;
|
|
528
|
+
return tagGroup[0].slice(2, -1).split(/\s+/)
|
|
529
|
+
.some((t) => t === '@known-defect' || t.startsWith('@known-defect:'));
|
|
530
|
+
}
|
|
531
|
+
|
|
505
532
|
// Registered BEFORE any user @afterEach block so a throwing user hook cannot skip the bookkeeping.
|
|
506
533
|
afterEach(async function () {
|
|
507
|
-
__escalateAfter = this.currentTest?.state === 'failed' ? (this.currentTest?.title ?? '') : null;
|
|
534
|
+
__escalateAfter = this.currentTest?.state === 'failed' && !__isExpectedFail(this.currentTest?.title ?? '') ? (this.currentTest?.title ?? '') : null;
|
|
508
535
|
});
|
|
509
536
|
|
|
510
537
|
{{#if scenarioResetTiers}}
|
|
@@ -583,9 +610,31 @@ describe('{{featureName}}', () => {
|
|
|
583
610
|
await driver.execute('mobile: clearApp', { appId: pkg });
|
|
584
611
|
}
|
|
585
612
|
|
|
586
|
-
|
|
613
|
+
/** The app's build file, when the session was given one. Absent when the app was attached BY PACKAGE. */
|
|
614
|
+
function __appBuildPath(): string | undefined {
|
|
587
615
|
const caps = (driver.requestedCapabilities || {}) as Record<string, string>;
|
|
588
|
-
|
|
616
|
+
return caps['appium:app'] || caps['app'];
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* The strongest tier THIS SESSION can actually carry out.
|
|
621
|
+
*
|
|
622
|
+
* Reinstalling needs the app's build file, and `data` on iOS IS a reinstall (no `mobile: clearApp`
|
|
623
|
+
* outside UiAutomator2). Attaching the app by package — the common local setup — leaves no build
|
|
624
|
+
* file, so those tiers cannot run at all.
|
|
625
|
+
*
|
|
626
|
+
* This exists so ESCALATION can cap itself. Escalation is a recovery the runner CHOSE; nothing
|
|
627
|
+
* asked for it. Escalating into a tier that throws turns one failed scenario into a dead spec file,
|
|
628
|
+
* because the throw lands in `beforeEach` and mocha then skips every remaining test in the file.
|
|
629
|
+
* A DECLARED tier is never capped — that is an unmet contract and must still throw by name.
|
|
630
|
+
*/
|
|
631
|
+
function __performableTier(): string {
|
|
632
|
+
if (__appBuildPath()) return 'full';
|
|
633
|
+
return driver.isIOS ? 'soft' : 'data';
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
async function __reinstallApp(pkg: string): Promise<void> {
|
|
637
|
+
const appPath = __appBuildPath();
|
|
589
638
|
if (!appPath) {
|
|
590
639
|
throw new Error(
|
|
591
640
|
`reset tier needs the app's build file to reinstall — set APP_APK (Android) / IOS_APP (iOS) so the appium:app capability is present, or drop to @reset:soft.`,
|
|
@@ -637,9 +686,24 @@ describe('{{featureName}}', () => {
|
|
|
637
686
|
// Same title ⟹ this is a retry of the scenario that just failed: keep its declared tier so a real
|
|
638
687
|
// failure fails both attempts (see __escalateAfter above).
|
|
639
688
|
const __escalate = __escalateAfter !== null && __escalateAfter !== (this.currentTest?.title ?? '');
|
|
640
|
-
|
|
689
|
+
// Escalation caps at what this session can perform, with the DECLARED tier as the FLOOR.
|
|
690
|
+
// The cap stops a tier NOBODY ASKED FOR from throwing in this hook and taking the rest of the
|
|
691
|
+
// spec file with it. The floor stops the cap from silently WEAKENING what the feature declared:
|
|
692
|
+
// a declared tier this session cannot perform still reaches __resetApp and still throws by name,
|
|
693
|
+
// because that one IS an unmet contract.
|
|
694
|
+
const __want = __escalate ? __ESCALATE[__base] : __base;
|
|
695
|
+
const __tier = __escalate
|
|
696
|
+
? __TIER_ORDER[Math.max(
|
|
697
|
+
__TIER_ORDER.indexOf(__base),
|
|
698
|
+
Math.min(__TIER_ORDER.indexOf(__want), __TIER_ORDER.indexOf(__performableTier())),
|
|
699
|
+
)]
|
|
700
|
+
: __want;
|
|
641
701
|
if (__tier !== __base) {
|
|
642
702
|
console.log(` \u2191 reset escalated ${__base} \u2192 ${__tier} (previous scenario failed: "${__escalateAfter}")`);
|
|
703
|
+
} else if (__want !== __base) {
|
|
704
|
+
// Said per occurrence, not once per run: this names the scenario that did NOT get the stronger
|
|
705
|
+
// reset it was owed, which is a fact about THIS scenario, not just about the session.
|
|
706
|
+
console.warn(` ! reset could not escalate ${__base} \u2192 ${__want} — this session has no app build file to reinstall from (the app was attached by package). Staying at ${__base}; set APP_APK (Android) / IOS_APP (iOS) to enable the stronger reset.`);
|
|
643
707
|
}
|
|
644
708
|
await __resetApp(__tier);
|
|
645
709
|
{{#if backgroundSteps}}
|