@sun-asterisk/sungen 3.2.28 → 3.2.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capabilities/discover.d.ts +29 -0
- package/dist/capabilities/discover.d.ts.map +1 -1
- package/dist/capabilities/discover.js +144 -10
- package/dist/capabilities/discover.js.map +1 -1
- package/dist/capabilities/registry.d.ts +18 -0
- package/dist/capabilities/registry.d.ts.map +1 -1
- package/dist/capabilities/registry.js +25 -0
- package/dist/capabilities/registry.js.map +1 -1
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +9 -0
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/capability.js +1 -1
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +2 -1
- 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 +6 -2
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/inspect.js +1 -1
- package/dist/cli/commands/inspect.js.map +1 -1
- package/dist/cli/commands/results-merge.d.ts.map +1 -1
- package/dist/cli/commands/results-merge.js +9 -2
- package/dist/cli/commands/results-merge.js.map +1 -1
- package/dist/cli/commands/results-sanitize.d.ts +24 -0
- package/dist/cli/commands/results-sanitize.d.ts.map +1 -0
- package/dist/cli/commands/results-sanitize.js +126 -0
- package/dist/cli/commands/results-sanitize.js.map +1 -0
- package/dist/cli/commands/script-check.d.ts.map +1 -1
- package/dist/cli/commands/script-check.js +3 -1
- package/dist/cli/commands/script-check.js.map +1 -1
- 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/dashboard/templates/index.html +1 -1
- package/dist/exporters/api-testcase-formatter.d.ts +3 -6
- package/dist/exporters/api-testcase-formatter.d.ts.map +1 -1
- package/dist/exporters/api-testcase-formatter.js +7 -10
- package/dist/exporters/api-testcase-formatter.js.map +1 -1
- package/dist/exporters/feature-parser.d.ts +1 -1
- package/dist/exporters/feature-parser.js +1 -1
- package/dist/exporters/matrix/build.d.ts.map +1 -1
- package/dist/exporters/matrix/build.js +18 -3
- package/dist/exporters/matrix/build.js.map +1 -1
- package/dist/exporters/matrix/item-labels.d.ts +35 -0
- package/dist/exporters/matrix/item-labels.d.ts.map +1 -0
- package/dist/exporters/matrix/item-labels.js +90 -0
- package/dist/exporters/matrix/item-labels.js.map +1 -0
- package/dist/exporters/matrix/render-coverage-sheet.d.ts +14 -0
- package/dist/exporters/matrix/render-coverage-sheet.d.ts.map +1 -0
- package/dist/exporters/matrix/render-coverage-sheet.js +162 -0
- package/dist/exporters/matrix/render-coverage-sheet.js.map +1 -0
- package/dist/exporters/matrix/render-csv.d.ts.map +1 -1
- package/dist/exporters/matrix/render-csv.js +3 -1
- package/dist/exporters/matrix/render-csv.js.map +1 -1
- package/dist/exporters/matrix/render-overview-sheet.d.ts +20 -0
- package/dist/exporters/matrix/render-overview-sheet.d.ts.map +1 -0
- package/dist/exporters/matrix/render-overview-sheet.js +32 -0
- package/dist/exporters/matrix/render-overview-sheet.js.map +1 -0
- package/dist/exporters/matrix/render-testcase-sheet.d.ts +30 -0
- package/dist/exporters/matrix/render-testcase-sheet.d.ts.map +1 -0
- package/dist/exporters/matrix/render-testcase-sheet.js +156 -0
- package/dist/exporters/matrix/render-testcase-sheet.js.map +1 -0
- package/dist/exporters/matrix/render-xlsx.d.ts +41 -31
- package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
- package/dist/exporters/matrix/render-xlsx.js +72 -425
- package/dist/exporters/matrix/render-xlsx.js.map +1 -1
- package/dist/exporters/matrix/result-totals.d.ts +23 -0
- package/dist/exporters/matrix/result-totals.d.ts.map +1 -0
- package/dist/exporters/matrix/result-totals.js +21 -0
- package/dist/exporters/matrix/result-totals.js.map +1 -0
- package/dist/exporters/matrix/testcase-summary-band.d.ts +30 -0
- package/dist/exporters/matrix/testcase-summary-band.d.ts.map +1 -0
- package/dist/exporters/matrix/testcase-summary-band.js +71 -0
- package/dist/exporters/matrix/testcase-summary-band.js.map +1 -0
- package/dist/exporters/matrix/wording.d.ts.map +1 -1
- package/dist/exporters/matrix/wording.js +13 -0
- package/dist/exporters/matrix/wording.js.map +1 -1
- package/dist/exporters/matrix/xlsx-cells.d.ts +38 -0
- package/dist/exporters/matrix/xlsx-cells.d.ts.map +1 -0
- package/dist/exporters/matrix/xlsx-cells.js +52 -0
- package/dist/exporters/matrix/xlsx-cells.js.map +1 -0
- package/dist/exporters/qa-env-secret-denylist.js +2 -2
- package/dist/exporters/qa-env-secret-denylist.js.map +1 -1
- package/dist/exporters/report-path-sanitizer.d.ts +38 -0
- package/dist/exporters/report-path-sanitizer.d.ts.map +1 -0
- package/dist/exporters/report-path-sanitizer.js +137 -0
- package/dist/exporters/report-path-sanitizer.js.map +1 -0
- package/dist/exporters/result-variants.d.ts.map +1 -1
- package/dist/exporters/result-variants.js +2 -1
- package/dist/exporters/result-variants.js.map +1 -1
- package/dist/exporters/sensitive-key.d.ts +31 -0
- package/dist/exporters/sensitive-key.d.ts.map +1 -0
- package/dist/exporters/sensitive-key.js +50 -0
- package/dist/exporters/sensitive-key.js.map +1 -0
- package/dist/exporters/test-data-resolver.d.ts +4 -1
- package/dist/exporters/test-data-resolver.d.ts.map +1 -1
- package/dist/exporters/test-data-resolver.js +12 -2
- package/dist/exporters/test-data-resolver.js.map +1 -1
- package/dist/exporters/xlsx-report-builder.d.ts +12 -1
- package/dist/exporters/xlsx-report-builder.d.ts.map +1 -1
- package/dist/exporters/xlsx-report-builder.js +10 -2
- package/dist/exporters/xlsx-report-builder.js.map +1 -1
- package/dist/generators/gherkin-parser/index.d.ts +10 -0
- package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
- package/dist/generators/gherkin-parser/index.js +20 -12
- package/dist/generators/gherkin-parser/index.js.map +1 -1
- package/dist/generators/gherkin-parser/parent-scope-clause.d.ts +54 -0
- package/dist/generators/gherkin-parser/parent-scope-clause.d.ts.map +1 -0
- package/dist/generators/gherkin-parser/parent-scope-clause.js +109 -0
- package/dist/generators/gherkin-parser/parent-scope-clause.js.map +1 -0
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts +23 -18
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts +4 -19
- package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/appium/appium-adapter.js.map +1 -1
- package/dist/generators/test-generator/adapters/appium/os-support.d.ts +53 -0
- package/dist/generators/test-generator/adapters/appium/os-support.d.ts.map +1 -0
- package/dist/generators/test-generator/adapters/appium/os-support.js +200 -0
- package/dist/generators/test-generator/adapters/appium/os-support.js.map +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/after-all.hbs +18 -0
- package/dist/generators/test-generator/adapters/appium/templates/after-each.hbs +18 -0
- package/dist/generators/test-generator/adapters/appium/templates/before-all.hbs +18 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/alert-fill-action.hbs +9 -2
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/capture-variable.hbs +24 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-at-position-action.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-element-with-text.hbs +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/double-click-action.hbs +7 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/frame-enter-action.hbs +3 -6
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/hover-action.hbs +6 -2
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/hover-element-with-text.hbs +4 -3
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/table-paginate-action.hbs +3 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/unknown-element-action.hbs +9 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/have-value-assertion.hbs +34 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +14 -6
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/go-back-action.hbs +21 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/long-press-action.hbs +4 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/pinch-zoom-action.hbs +4 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/set-geolocation-action.hbs +16 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +9 -1
- package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +62 -23
- package/dist/generators/test-generator/adapters/index.d.ts +1 -1
- package/dist/generators/test-generator/adapters/index.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +4 -19
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/after-all.hbs +19 -1
- package/dist/generators/test-generator/adapters/playwright/templates/after-each.hbs +18 -0
- package/dist/generators/test-generator/adapters/playwright/templates/before-all.hbs +19 -1
- package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/check-action.hbs +6 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/click-at-position-action.hbs +23 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/expand-action.hbs +8 -8
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/fill-editor-action.hbs +7 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row-nth.hbs +7 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +7 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-paginate-action.hbs +5 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/uncheck-action.hbs +3 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/wait-for-page.hbs +18 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +32 -4
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-element.hbs +5 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +5 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +13 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +2 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +2 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +2 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +21 -3
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +20 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-empty.hbs +4 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-match-data.hbs +4 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-count.hbs +4 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +12 -4
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +14 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +3 -3
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/checked-opts.hbs +8 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +8 -8
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-nth.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/id.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/testid.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +8 -8
- package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +10 -8
- package/dist/generators/test-generator/code-generator.d.ts +37 -0
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +165 -40
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/diagnostics.d.ts +116 -6
- package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
- package/dist/generators/test-generator/diagnostics.js +115 -5
- package/dist/generators/test-generator/diagnostics.js.map +1 -1
- package/dist/generators/test-generator/patterns/index.d.ts +57 -11
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +163 -21
- package/dist/generators/test-generator/patterns/index.js.map +1 -1
- package/dist/generators/test-generator/patterns/types.d.ts +8 -0
- package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/with-parent-scope.d.ts +18 -0
- package/dist/generators/test-generator/patterns/with-parent-scope.d.ts.map +1 -0
- package/dist/generators/test-generator/patterns/with-parent-scope.js +40 -0
- package/dist/generators/test-generator/patterns/with-parent-scope.js.map +1 -0
- package/dist/generators/test-generator/step-mapper.d.ts +103 -10
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +266 -45
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/template-engine.d.ts +4 -19
- package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
- package/dist/generators/test-generator/template-engine.js +17 -12
- package/dist/generators/test-generator/template-engine.js.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.d.ts +7 -0
- package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.js +19 -2
- package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
- package/dist/generators/test-generator/utils/dialog-scope-identity.d.ts +41 -0
- package/dist/generators/test-generator/utils/dialog-scope-identity.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/dialog-scope-identity.js +61 -0
- package/dist/generators/test-generator/utils/dialog-scope-identity.js.map +1 -0
- package/dist/generators/test-generator/utils/escape-js-string.d.ts +17 -0
- package/dist/generators/test-generator/utils/escape-js-string.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/escape-js-string.js +29 -0
- package/dist/generators/test-generator/utils/escape-js-string.js.map +1 -0
- package/dist/generators/test-generator/utils/nearest-match.d.ts +24 -0
- package/dist/generators/test-generator/utils/nearest-match.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/nearest-match.js +56 -0
- package/dist/generators/test-generator/utils/nearest-match.js.map +1 -0
- package/dist/generators/test-generator/utils/selector-entry-fields.d.ts +58 -0
- package/dist/generators/test-generator/utils/selector-entry-fields.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/selector-entry-fields.js +119 -0
- package/dist/generators/test-generator/utils/selector-entry-fields.js.map +1 -0
- package/dist/generators/test-generator/utils/selector-entry-walk.d.ts +25 -0
- package/dist/generators/test-generator/utils/selector-entry-walk.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/selector-entry-walk.js +54 -0
- package/dist/generators/test-generator/utils/selector-entry-walk.js.map +1 -0
- package/dist/generators/test-generator/utils/selector-nth-validation.d.ts +59 -0
- package/dist/generators/test-generator/utils/selector-nth-validation.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/selector-nth-validation.js +91 -0
- package/dist/generators/test-generator/utils/selector-nth-validation.js.map +1 -0
- package/dist/generators/test-generator/utils/selector-resolver.d.ts +53 -2
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +273 -27
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-type-validation.d.ts +51 -0
- package/dist/generators/test-generator/utils/selector-type-validation.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/selector-type-validation.js +77 -0
- package/dist/generators/test-generator/utils/selector-type-validation.js.map +1 -0
- package/dist/harness/audit.d.ts +3 -1
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +51 -28
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/capability-plan.d.ts +4 -0
- package/dist/harness/capability-plan.d.ts.map +1 -1
- package/dist/harness/capability-plan.js +32 -2
- package/dist/harness/capability-plan.js.map +1 -1
- package/dist/harness/catalog/drivers.yaml +1 -1
- package/dist/harness/catalog/universal-viewpoints.yaml +145 -0
- package/dist/harness/data-driven-lint.d.ts.map +1 -1
- package/dist/harness/data-driven-lint.js +217 -33
- package/dist/harness/data-driven-lint.js.map +1 -1
- package/dist/harness/parse.d.ts +6 -0
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +1 -1
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/repair.d.ts.map +1 -1
- package/dist/harness/repair.js +8 -1
- package/dist/harness/repair.js.map +1 -1
- package/dist/harness/script-check.d.ts +2 -2
- package/dist/harness/script-check.d.ts.map +1 -1
- package/dist/harness/script-check.js +186 -25
- package/dist/harness/script-check.js.map +1 -1
- package/dist/harness/sensors.d.ts +48 -0
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +26 -1
- package/dist/harness/sensors.js.map +1 -1
- package/dist/harness/viewpoint-ledger.d.ts +10 -0
- package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
- package/dist/harness/viewpoint-ledger.js +23 -2
- package/dist/harness/viewpoint-ledger.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/orchestrator/project-initializer.d.ts +6 -0
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +16 -0
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/report-sanitize-reporter-install.d.ts +24 -0
- package/dist/orchestrator/report-sanitize-reporter-install.d.ts.map +1 -0
- package/dist/orchestrator/report-sanitize-reporter-install.js +161 -0
- package/dist/orchestrator/report-sanitize-reporter-install.js.map +1 -0
- package/dist/orchestrator/templates/ai-src/commands/delivery.md +4 -3
- package/dist/orchestrator/templates/ai-src/commands/run-test.md +9 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +10 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -5
- package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +62 -3
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +126 -11
- package/dist/orchestrator/templates/ai-src/skills/sungen-mobile-gestures/SKILL.md +16 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +12 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +19 -5
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +88 -2
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +62 -5
- package/dist/orchestrator/templates/ai-src/skills/sungen-test-design-techniques/SKILL.md +13 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +59 -10
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/group-f-mobile-native.md +419 -0
- package/dist/orchestrator/templates/playwright.config.ts +7 -0
- package/dist/orchestrator/templates/readme.md +1 -1
- package/dist/orchestrator/templates/specs-checked-state.d.ts +83 -0
- package/dist/orchestrator/templates/specs-checked-state.d.ts.map +1 -0
- package/dist/orchestrator/templates/specs-checked-state.js +261 -0
- package/dist/orchestrator/templates/specs-checked-state.js.map +1 -0
- package/dist/orchestrator/templates/specs-checked-state.ts +324 -0
- package/dist/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
- package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.d.ts +36 -0
- package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.d.ts.map +1 -0
- package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.js +155 -0
- package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.js.map +1 -0
- package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.ts +142 -0
- package/dist/orchestrator/templates/specs-url-assert.d.ts +50 -25
- package/dist/orchestrator/templates/specs-url-assert.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-url-assert.js +65 -24
- package/dist/orchestrator/templates/specs-url-assert.js.map +1 -1
- package/dist/orchestrator/templates/specs-url-assert.ts +101 -28
- package/dist/orchestrator/templates/wdio.conf.ts +105 -1
- package/dist/utils/selector-types.d.ts +43 -1
- package/dist/utils/selector-types.d.ts.map +1 -1
- package/dist/utils/selector-types.js +51 -7
- package/dist/utils/selector-types.js.map +1 -1
- package/package.json +3 -3
- package/src/capabilities/discover.ts +146 -10
- package/src/capabilities/registry.ts +30 -0
- package/src/cli/commands/audit.ts +9 -0
- package/src/cli/commands/capability.ts +1 -1
- package/src/cli/commands/delivery.ts +2 -1
- package/src/cli/commands/generate.ts +5 -2
- package/src/cli/commands/inspect.ts +1 -1
- package/src/cli/commands/results-merge.ts +9 -2
- package/src/cli/commands/results-sanitize.ts +98 -0
- package/src/cli/commands/script-check.ts +3 -1
- package/src/cli/commands/update.ts +3 -0
- package/src/dashboard/templates/index.html +1 -1
- package/src/exporters/api-testcase-formatter.ts +5 -7
- package/src/exporters/feature-parser.ts +1 -1
- package/src/exporters/matrix/build.ts +17 -3
- package/src/exporters/matrix/item-labels.ts +78 -0
- package/src/exporters/matrix/render-coverage-sheet.ts +170 -0
- package/src/exporters/matrix/render-csv.ts +3 -1
- package/src/exporters/matrix/render-overview-sheet.ts +51 -0
- package/src/exporters/matrix/render-testcase-sheet.ts +178 -0
- package/src/exporters/matrix/render-xlsx.ts +67 -448
- package/src/exporters/matrix/result-totals.ts +37 -0
- package/src/exporters/matrix/testcase-summary-band.ts +74 -0
- package/src/exporters/matrix/wording.ts +13 -0
- package/src/exporters/matrix/xlsx-cells.ts +56 -0
- package/src/exporters/qa-env-secret-denylist.ts +1 -1
- package/src/exporters/report-path-sanitizer.ts +144 -0
- package/src/exporters/result-variants.ts +2 -1
- package/src/exporters/sensitive-key.ts +48 -0
- package/src/exporters/test-data-resolver.ts +12 -2
- package/src/exporters/xlsx-report-builder.ts +16 -2
- package/src/generators/gherkin-parser/index.ts +22 -11
- package/src/generators/gherkin-parser/parent-scope-clause.ts +130 -0
- package/src/generators/test-generator/adapters/adapter-interface.ts +25 -3
- package/src/generators/test-generator/adapters/appium/appium-adapter.ts +4 -4
- package/src/generators/test-generator/adapters/appium/os-support.ts +176 -0
- package/src/generators/test-generator/adapters/appium/templates/after-all.hbs +18 -0
- package/src/generators/test-generator/adapters/appium/templates/after-each.hbs +18 -0
- package/src/generators/test-generator/adapters/appium/templates/before-all.hbs +18 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/alert-fill-action.hbs +9 -2
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/capture-variable.hbs +24 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-at-position-action.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-element-with-text.hbs +1 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/double-click-action.hbs +7 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/frame-enter-action.hbs +3 -6
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/hover-action.hbs +6 -2
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/hover-element-with-text.hbs +4 -3
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/table-paginate-action.hbs +3 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/unknown-element-action.hbs +9 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/have-value-assertion.hbs +34 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +14 -6
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/go-back-action.hbs +21 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/long-press-action.hbs +4 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/pinch-zoom-action.hbs +4 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/set-geolocation-action.hbs +16 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +9 -1
- package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +62 -23
- package/src/generators/test-generator/adapters/index.ts +1 -1
- package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +4 -4
- package/src/generators/test-generator/adapters/playwright/templates/after-all.hbs +19 -1
- package/src/generators/test-generator/adapters/playwright/templates/after-each.hbs +18 -0
- package/src/generators/test-generator/adapters/playwright/templates/before-all.hbs +19 -1
- package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/check-action.hbs +6 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/click-at-position-action.hbs +23 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/expand-action.hbs +8 -8
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/fill-editor-action.hbs +7 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row-nth.hbs +7 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +7 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-paginate-action.hbs +5 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/uncheck-action.hbs +3 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/wait-for-page.hbs +18 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +32 -4
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-element.hbs +5 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +5 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +13 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +2 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +2 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +2 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +21 -3
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +20 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-empty.hbs +4 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-match-data.hbs +4 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-count.hbs +4 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +12 -4
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +14 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +3 -3
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/checked-opts.hbs +8 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +8 -8
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-nth.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/id.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/testid.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +8 -8
- package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +10 -8
- package/src/generators/test-generator/code-generator.ts +181 -46
- package/src/generators/test-generator/diagnostics.ts +117 -6
- package/src/generators/test-generator/patterns/index.ts +175 -24
- package/src/generators/test-generator/patterns/types.ts +8 -0
- package/src/generators/test-generator/patterns/with-parent-scope.ts +41 -0
- package/src/generators/test-generator/step-mapper.ts +277 -43
- package/src/generators/test-generator/template-engine.ts +21 -21
- package/src/generators/test-generator/utils/data-resolver.ts +23 -2
- package/src/generators/test-generator/utils/dialog-scope-identity.ts +71 -0
- package/src/generators/test-generator/utils/escape-js-string.ts +24 -0
- package/src/generators/test-generator/utils/nearest-match.ts +55 -0
- package/src/generators/test-generator/utils/selector-entry-fields.ts +131 -0
- package/src/generators/test-generator/utils/selector-entry-walk.ts +58 -0
- package/src/generators/test-generator/utils/selector-nth-validation.ts +99 -0
- package/src/generators/test-generator/utils/selector-resolver.ts +333 -28
- package/src/generators/test-generator/utils/selector-type-validation.ts +87 -0
- package/src/harness/audit.ts +55 -30
- package/src/harness/capability-plan.ts +33 -1
- package/src/harness/catalog/drivers.yaml +1 -1
- package/src/harness/catalog/universal-viewpoints.yaml +145 -0
- package/src/harness/data-driven-lint.ts +192 -32
- package/src/harness/parse.ts +7 -1
- package/src/harness/repair.ts +8 -1
- package/src/harness/script-check.ts +186 -21
- package/src/harness/sensors.ts +75 -1
- package/src/harness/viewpoint-ledger.ts +26 -5
- package/src/index.ts +7 -0
- package/src/orchestrator/project-initializer.ts +17 -0
- package/src/orchestrator/report-sanitize-reporter-install.ts +109 -0
- package/src/orchestrator/templates/ai-src/commands/delivery.md +4 -3
- package/src/orchestrator/templates/ai-src/commands/run-test.md +9 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +10 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -5
- package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +62 -3
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +126 -11
- package/src/orchestrator/templates/ai-src/skills/sungen-mobile-gestures/SKILL.md +16 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +12 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +19 -5
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +88 -2
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +62 -5
- package/src/orchestrator/templates/ai-src/skills/sungen-test-design-techniques/SKILL.md +13 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +59 -10
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/group-f-mobile-native.md +419 -0
- package/src/orchestrator/templates/playwright.config.ts +7 -0
- package/src/orchestrator/templates/readme.md +1 -1
- package/src/orchestrator/templates/specs-checked-state.ts +324 -0
- package/src/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
- package/src/orchestrator/templates/specs-report-path-sanitize-reporter.ts +142 -0
- package/src/orchestrator/templates/specs-url-assert.ts +101 -28
- package/src/orchestrator/templates/wdio.conf.ts +105 -1
- package/src/utils/selector-types.ts +54 -5
|
@@ -3,8 +3,25 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Advisory (warn, never block) — surfaced after `sungen generate`. Catches the silent
|
|
5
5
|
* footguns: a `{{var}}` in a @cases scenario that is neither a row column nor a top-level
|
|
6
|
-
* key, a name collision that shadows a top-level value, inconsistent dataset rows,
|
|
7
|
-
* @query whose param has no value / whose name doesn't resolve
|
|
6
|
+
* key, a name collision that shadows a top-level value, inconsistent dataset rows, a
|
|
7
|
+
* @query whose param has no value / whose name doesn't resolve, and a top-level test-data
|
|
8
|
+
* key nothing binds (dead data).
|
|
9
|
+
*
|
|
10
|
+
* "Referenced" is what the COMPILER and the RUNTIME actually read, not just `{{key}}` in a step:
|
|
11
|
+
* - `{{key…}}` in any step, or in the comments of a `@manual` scenario (its tester procedure is
|
|
12
|
+
* the only body it has — the delivery exports those refs, so they are not dead);
|
|
13
|
+
* - a `@cases:<key>` dataset;
|
|
14
|
+
* - an override value `…={{key…}}` on an @api/@query annotation;
|
|
15
|
+
* - a @query or @api param bound IMPLICITLY by name — `params: [user_email]` with no override
|
|
16
|
+
* reads `testData.raw("user_email")` at run time (driver-db / driver-api), so the key is live
|
|
17
|
+
* even though no `{{user_email}}` appears anywhere;
|
|
18
|
+
* - `{{key}}` in the unit's selectors (locale-aware names) or mock catalog (mock bodies).
|
|
19
|
+
* Keys that start with `_` are Data Factory metadata (`_generated_by`, `_fingerprint`), never
|
|
20
|
+
* scenario data — the factory itself skips them by the same rule.
|
|
21
|
+
* The FORWARD direction is checked here too: a `{{x}}` in a plain scenario whose head is neither a
|
|
22
|
+
* test-data key nor bound at run time compiles to the literal text `'${x}'` — the step types the
|
|
23
|
+
* placeholder into the field and the scenario runs green having tested nothing. The compiler emits
|
|
24
|
+
* no diagnostic for that fallback, so this lint names it.
|
|
8
25
|
*/
|
|
9
26
|
import * as fs from 'fs';
|
|
10
27
|
import * as path from 'path';
|
|
@@ -20,15 +37,113 @@ export interface DataDrivenWarning {
|
|
|
20
37
|
|
|
21
38
|
const ROW_META = new Set(['case', 'name', 'label', '__label']);
|
|
22
39
|
|
|
23
|
-
/**
|
|
24
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Collect the inner text of every `{{…}}` reference in a scenario's steps — and, for a `@manual`
|
|
42
|
+
* scenario, in its comments: the `# Tester verifies: …` procedure is its only body and the delivery
|
|
43
|
+
* exporter reads those refs. Other scenarios' comments are NOT scanned: a ref mentioned only in
|
|
44
|
+
* prose would otherwise hide a genuinely dead key.
|
|
45
|
+
*/
|
|
46
|
+
function collectRefs(sc: ParsedScenario, manual: boolean = isManualScenario(sc)): string[] {
|
|
25
47
|
const refs = new Set<string>();
|
|
26
|
-
|
|
27
|
-
|
|
48
|
+
const texts = (sc.steps || []).map((st) => st.text || '');
|
|
49
|
+
if (manual) texts.push(...(sc.comments || []));
|
|
50
|
+
for (const text of texts) {
|
|
51
|
+
for (const m of text.matchAll(/\{\{\s*([^}]+?)\s*\}\}/g)) refs.add(m[1]);
|
|
28
52
|
}
|
|
29
53
|
return [...refs];
|
|
30
54
|
}
|
|
31
55
|
|
|
56
|
+
function isManualScenario(sc: ParsedScenario): boolean {
|
|
57
|
+
return (sc.tags || []).some((t) => t === '@manual' || t.startsWith('@manual:'));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Variable heads a scenario binds at RUN time: `@api:<name>`/`@query:<name>` responses and `remember … as {{v}}` captures. */
|
|
61
|
+
function runtimeBoundHeads(sc: ParsedScenario): Set<string> {
|
|
62
|
+
const out = new Set<string>();
|
|
63
|
+
for (const t of sc.tags || []) {
|
|
64
|
+
const m = t.match(/^@(?:api|query):([A-Za-z_]\w*)/);
|
|
65
|
+
if (m) out.add(m[1]);
|
|
66
|
+
}
|
|
67
|
+
for (const st of sc.steps || []) {
|
|
68
|
+
for (const m of (st.text || '').matchAll(/\bas\s+\{\{\s*([^}.[\s]+)/g)) out.add(m[1]);
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The unit id the catalogs are resolved by — the path under `qa/`, with the `screens/` prefix
|
|
75
|
+
* dropped: `qa/screens/login` → `login`, `qa/flows/checkout` → `flows/checkout`, `qa/api/pet` →
|
|
76
|
+
* `api/pet`, `qa/api/flows/signup` → `api/flows/signup`. `generate` hands every unit kind to this
|
|
77
|
+
* lint; a bare basename sent an api unit's `@query` to `qa/screens/<area>/database/` and reported
|
|
78
|
+
* every query as "not found".
|
|
79
|
+
*/
|
|
80
|
+
function unitIdOf(unitDir: string): string {
|
|
81
|
+
const parts = path.resolve(unitDir).split(path.sep);
|
|
82
|
+
const qa = parts.lastIndexOf('qa', parts.length - 2); // a unit itself named `qa` is not the qa/ root
|
|
83
|
+
if (qa < 0) {
|
|
84
|
+
const base = path.basename(unitDir);
|
|
85
|
+
return path.basename(path.dirname(unitDir)) === 'flows' ? `flows/${base}` : base;
|
|
86
|
+
}
|
|
87
|
+
const rel = parts.slice(qa + 1);
|
|
88
|
+
return (rel[0] === 'screens' ? rel.slice(1) : rel).join('/');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* `{{key}}` refs the unit's OTHER authored files carry — a locale-aware selector (`name: '{{lbl_submit}}'`,
|
|
93
|
+
* resolved from test-data by the selector resolver) and a mock body (`body: "{{mock_search_body}}"`,
|
|
94
|
+
* bound by the mock driver at compile time). A key used only there is live, not dead.
|
|
95
|
+
*/
|
|
96
|
+
function refsInUnitFiles(unitDir: string): Set<string> {
|
|
97
|
+
const out = new Set<string>();
|
|
98
|
+
for (const sub of ['selectors', 'mock']) {
|
|
99
|
+
const dir = path.join(unitDir, sub);
|
|
100
|
+
if (!fs.existsSync(dir)) continue;
|
|
101
|
+
for (const f of fs.readdirSync(dir)) {
|
|
102
|
+
if (!/\.ya?ml$/.test(f)) continue;
|
|
103
|
+
const text = fs.readFileSync(path.join(dir, f), 'utf8');
|
|
104
|
+
for (const m of text.matchAll(/\{\{\s*([^}$][^}]*?)\s*\}\}/g)) out.add(m[1].split(/[.[]/)[0]);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Param names of the `@api:<name>` entries a unit can reference — declared `params:`, else the
|
|
112
|
+
* `:name` placeholders in path/body/headers (the same derivation the API driver applies). Read
|
|
113
|
+
* straight from the catalog files: a param not overridden in the annotation is bound at run time by
|
|
114
|
+
* NAME from test-data (`testData.raw("petId")`), so that key is live even with no `{{petId}}` anywhere.
|
|
115
|
+
* Best-effort — a missing or unparseable catalog contributes nothing (the api driver's own sensor
|
|
116
|
+
* reports those).
|
|
117
|
+
*/
|
|
118
|
+
function apiParamsByName(unitId: string, cwd: string): Map<string, string[]> {
|
|
119
|
+
const out = new Map<string, string[]>();
|
|
120
|
+
const unitCatalog = unitId.startsWith('api/') || unitId.startsWith('flows/')
|
|
121
|
+
? path.join(cwd, 'qa', unitId, 'api', 'apis.yaml')
|
|
122
|
+
: path.join(cwd, 'qa', 'screens', unitId, 'api', 'apis.yaml');
|
|
123
|
+
for (const file of [unitCatalog, path.join(cwd, 'qa', 'api', 'apis.yaml')]) {
|
|
124
|
+
if (!fs.existsSync(file)) continue;
|
|
125
|
+
let raw: any;
|
|
126
|
+
try { raw = parseYaml(fs.readFileSync(file, 'utf8')) || {}; } catch { continue; }
|
|
127
|
+
const root = raw && typeof raw === 'object' && raw.apis && typeof raw.apis === 'object' ? raw.apis : raw;
|
|
128
|
+
for (const [name, v] of Object.entries(root as Record<string, any>)) {
|
|
129
|
+
if (!v || typeof v !== 'object' || typeof v.method !== 'string' || typeof v.path !== 'string') continue;
|
|
130
|
+
if (out.has(name)) continue; // unit catalog wins over the shared one
|
|
131
|
+
// Same rule as the driver's apiParamNames: declared `params` when non-empty, else the `:name`
|
|
132
|
+
// placeholders of path/body/headers/files/bodyFile — except an XML string body, whose `:` are
|
|
133
|
+
// namespaces, where only declared params count.
|
|
134
|
+
const declared = Array.isArray(v.params) && v.params.length ? v.params.map(String) : null;
|
|
135
|
+
const refs = new Set<string>();
|
|
136
|
+
const xmlBody = v.encoding === 'xml' && typeof v.body === 'string';
|
|
137
|
+
const texts = [v.path, xmlBody ? '' : JSON.stringify(v.body ?? ''), JSON.stringify(v.headers ?? ''), JSON.stringify(v.files ?? ''), JSON.stringify(v.bodyFile ?? '')];
|
|
138
|
+
for (const text of texts) {
|
|
139
|
+
for (const m of String(text).matchAll(/:([A-Za-z_][A-Za-z0-9_]*)/g)) refs.add(m[1]);
|
|
140
|
+
}
|
|
141
|
+
out.set(name, declared ?? (xmlBody ? [] : [...refs]));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
|
|
32
147
|
/** Keys overridden in a `@query:name(a=…,b=…)` annotation. */
|
|
33
148
|
function overrideKeys(raw?: string): Set<string> {
|
|
34
149
|
const out = new Set<string>();
|
|
@@ -43,29 +158,69 @@ function overrideKeys(raw?: string): Set<string> {
|
|
|
43
158
|
/** Lint the @cases/@query usage of one screen/flow directory. Returns advisory warnings. */
|
|
44
159
|
export function lintDataDriven(screenDir: string, cwd: string = process.cwd()): DataDrivenWarning[] {
|
|
45
160
|
const base = path.basename(screenDir);
|
|
46
|
-
const
|
|
47
|
-
const screenName = isFlow ? `flows/${base}` : base;
|
|
161
|
+
const screenName = unitIdOf(screenDir);
|
|
48
162
|
// Every `.feature` of the unit — `add --feature` sub-features lint too. A file that
|
|
49
163
|
// fails to parse contributes nothing rather than voiding the whole unit's lint.
|
|
50
|
-
const scenarios: ParsedScenario[] =
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
164
|
+
const scenarios: ParsedScenario[] = [];
|
|
165
|
+
const featureTags: string[] = [];
|
|
166
|
+
for (const p of featureFilesFor(screenDir, base).filter((f) => fs.existsSync(f))) {
|
|
167
|
+
try {
|
|
168
|
+
const feature = new GherkinParser().parseFeatureFile(p);
|
|
169
|
+
scenarios.push(...(feature.scenarios || []));
|
|
170
|
+
featureTags.push(...(feature.tags || []));
|
|
171
|
+
} catch {
|
|
172
|
+
/* a file that fails to parse contributes nothing */
|
|
173
|
+
}
|
|
174
|
+
}
|
|
59
175
|
if (scenarios.length === 0) return [];
|
|
60
176
|
|
|
61
177
|
const tdPath = path.join(screenDir, 'test-data', `${base}.yaml`);
|
|
62
178
|
const td: Record<string, any> = tdPath && fs.existsSync(tdPath) ? parseYaml(fs.readFileSync(tdPath, 'utf8')) || {} : {};
|
|
63
179
|
const topKeys = new Set(Object.keys(td));
|
|
64
180
|
const warns: DataDrivenWarning[] = [];
|
|
181
|
+
// Bindings a `@beforeAll` hook's `@api:`/`@query:` preconditions make are readable from EVERY
|
|
182
|
+
// scenario of the feature (the compiler registers them feature-wide). A `remember … as` inside the
|
|
183
|
+
// hook is NOT — it stays hook-local — so only the tag-bound names are exempted here.
|
|
184
|
+
const featureBound = new Set<string>();
|
|
185
|
+
for (const sc of scenarios) {
|
|
186
|
+
if (sc.hookType !== 'beforeAll') continue;
|
|
187
|
+
for (const t of sc.tags || []) { const m = t.match(/^@(?:api|query):([A-Za-z_]\w*)/); if (m) featureBound.add(m[1]); }
|
|
188
|
+
}
|
|
189
|
+
// H3: an `@extend:<base>` scenario runs the base's steps first — the base's captures bind for it.
|
|
190
|
+
const stepsBase = new Map<string, ParsedScenario>();
|
|
191
|
+
for (const sc of scenarios) if (sc.stepsName) stepsBase.set(sc.stepsName, sc);
|
|
192
|
+
// `{{key}}` refs carried by the unit's selectors (i18n) and mock catalog are live too.
|
|
193
|
+
const fileRefs = refsInUnitFiles(screenDir);
|
|
194
|
+
// A feature-level `@manual` makes every scenario manual (the whole file is skipped by the compiler).
|
|
195
|
+
const featureManual = featureTags.some((t) => t === '@manual' || t.startsWith('@manual:'));
|
|
196
|
+
|
|
197
|
+
// What the scenarios bind, collected up front — the @query pass below adds the params it binds by name.
|
|
198
|
+
const usedHeads = new Set<string>();
|
|
199
|
+
const usedDatasets = new Set<string>();
|
|
200
|
+
const apiParams = apiParamsByName(screenName, cwd);
|
|
201
|
+
for (const h of fileRefs) usedHeads.add(h);
|
|
202
|
+
for (const sc of scenarios) {
|
|
203
|
+
for (const r of collectRefs(sc, featureManual || isManualScenario(sc))) usedHeads.add(r.split(/[.[]/)[0]);
|
|
204
|
+
for (const t of sc.tags || []) {
|
|
205
|
+
const cm = t.match(/^@cases:(.+)$/);
|
|
206
|
+
if (cm) usedDatasets.add(cm[1].trim());
|
|
207
|
+
const om = t.match(/^@(?:api|query):[A-Za-z_]\w*\((.*)\)$/);
|
|
208
|
+
if (om) for (const ref of om[1].matchAll(/\{\{\s*([^}]+?)\s*\}\}/g)) usedHeads.add(ref[1].split(/[.[]/)[0].trim());
|
|
209
|
+
// `@api:<name>` params not overridden in the annotation are read from test-data BY NAME at run time.
|
|
210
|
+
const am = t.match(/^@api:([A-Za-z_]\w*)(?:\((.*)\))?$/);
|
|
211
|
+
if (am) {
|
|
212
|
+
const overrides = overrideKeys(am[2]);
|
|
213
|
+
for (const p of apiParams.get(am[1]) ?? []) if (!overrides.has(p)) usedHeads.add(p);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
65
217
|
|
|
66
218
|
for (const sc of scenarios) {
|
|
67
219
|
const tags: string[] = sc.tags || [];
|
|
68
|
-
const
|
|
220
|
+
const manual = featureManual || isManualScenario(sc);
|
|
221
|
+
const refs = collectRefs(sc, manual);
|
|
222
|
+
const bound = runtimeBoundHeads(sc);
|
|
223
|
+
if (sc.extendsName && stepsBase.has(sc.extendsName)) for (const h of runtimeBoundHeads(stepsBase.get(sc.extendsName)!)) bound.add(h);
|
|
69
224
|
|
|
70
225
|
// --- @cases -----------------------------------------------------------
|
|
71
226
|
const casesTag = tags.find((t) => t.startsWith('@cases:'));
|
|
@@ -85,6 +240,9 @@ export function lintDataDriven(screenDir: string, cwd: string = process.cwd()):
|
|
|
85
240
|
}
|
|
86
241
|
for (const r of refs) {
|
|
87
242
|
const head = r.split(/[.[]/)[0];
|
|
243
|
+
// `{{$uuid}}` is resolved at run time; `{{login.status}}` / `{{captured}}` are bound by the
|
|
244
|
+
// scenario's own @api/@query call or `remember … as` — none of them is test-data.
|
|
245
|
+
if (head.startsWith('$') || bound.has(head)) continue;
|
|
88
246
|
if (!allCols.has(head) && !topKeys.has(head)) {
|
|
89
247
|
warns.push({ scenario: sc.name, message: `@cases:${ds} → {{${r}}} is neither a dataset column nor a top-level test-data key.` });
|
|
90
248
|
}
|
|
@@ -92,6 +250,16 @@ export function lintDataDriven(screenDir: string, cwd: string = process.cwd()):
|
|
|
92
250
|
}
|
|
93
251
|
}
|
|
94
252
|
|
|
253
|
+
// --- forward: a ref in a PLAIN scenario that nothing provides ----------------------------
|
|
254
|
+
// (@cases scenarios are covered above with their row columns; hooks bind their own refs)
|
|
255
|
+
if (!casesTag && !manual) { // a @manual scenario is never compiled — nothing to say about literals
|
|
256
|
+
for (const r of refs) {
|
|
257
|
+
const head = r.split(/[.[]/)[0];
|
|
258
|
+
if (head.startsWith('$') || bound.has(head) || featureBound.has(head) || topKeys.has(head)) continue;
|
|
259
|
+
warns.push({ scenario: sc.name, message: `{{${r}}} is not a test-data key and nothing binds it at run time — it compiles to the literal text '\${${r}}' (add the key to test-data, or fix the ref).` });
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
95
263
|
// --- @query -----------------------------------------------------------
|
|
96
264
|
for (const t of tags) {
|
|
97
265
|
const m = t.match(/^@query:([A-Za-z_][A-Za-z0-9_]*)(?:\((.*)\))?$/);
|
|
@@ -106,7 +274,10 @@ export function lintDataDriven(screenDir: string, cwd: string = process.cwd()):
|
|
|
106
274
|
continue;
|
|
107
275
|
}
|
|
108
276
|
for (const p of entry.params || []) {
|
|
109
|
-
if (
|
|
277
|
+
if (overrides.has(p)) continue;
|
|
278
|
+
// Not overridden → the runtime reads `testData.raw("<p>")`: the key IS referenced, by name.
|
|
279
|
+
usedHeads.add(p);
|
|
280
|
+
if (!topKeys.has(p)) {
|
|
110
281
|
warns.push({ scenario: sc.name, message: `@query:${name} → param "${p}" has no value: not in the annotation and not a top-level test-data key.` });
|
|
111
282
|
}
|
|
112
283
|
}
|
|
@@ -114,20 +285,9 @@ export function lintDataDriven(screenDir: string, cwd: string = process.cwd()):
|
|
|
114
285
|
}
|
|
115
286
|
|
|
116
287
|
// --- orphan test-data: a top-level key never referenced (dead data — generated but not
|
|
117
|
-
// materialized into a scenario).
|
|
118
|
-
// dataset, or an override value `…={{key…}}` on an @api/@query annotation.
|
|
119
|
-
const usedHeads = new Set<string>();
|
|
120
|
-
const usedDatasets = new Set<string>();
|
|
121
|
-
for (const sc of scenarios) {
|
|
122
|
-
for (const r of collectRefs(sc)) usedHeads.add(r.split(/[.[]/)[0]);
|
|
123
|
-
for (const t of sc.tags || []) {
|
|
124
|
-
const cm = t.match(/^@cases:(.+)$/);
|
|
125
|
-
if (cm) usedDatasets.add(cm[1].trim());
|
|
126
|
-
const om = t.match(/^@(?:api|query):[A-Za-z_]\w*\((.*)\)$/);
|
|
127
|
-
if (om) for (const ref of om[1].matchAll(/\{\{\s*([^}]+?)\s*\}\}/g)) usedHeads.add(ref[1].split(/[.[]/)[0].trim());
|
|
128
|
-
}
|
|
129
|
-
}
|
|
288
|
+
// materialized into a scenario). See the header for what counts as referenced.
|
|
130
289
|
for (const k of topKeys) {
|
|
290
|
+
if (k.startsWith('_')) continue; // Data Factory metadata, not scenario data
|
|
131
291
|
if (!usedHeads.has(k) && !usedDatasets.has(k)) {
|
|
132
292
|
warns.push({ message: `test-data key "${k}" is defined but never referenced ({{${k}}}, a @cases dataset, or an override) — dead data: bind it into a scenario or remove it.` });
|
|
133
293
|
}
|
package/src/harness/parse.ts
CHANGED
|
@@ -41,6 +41,12 @@ export interface ScenarioInfo {
|
|
|
41
41
|
ownedByFlow?: string; // @owned-by:<flow> — the flow that owns this deferred scenario (H6)
|
|
42
42
|
/** Ordered steps with their resolved bucket (And/But inherit) — flow handoff analysis (#569). */
|
|
43
43
|
steps?: Array<{ bucket: 'given' | 'when' | 'then'; text: string }>;
|
|
44
|
+
/**
|
|
45
|
+
* The `.feature` this scenario was read from. A unit may hold several (`add --feature`), and
|
|
46
|
+
* `scenarioIdReuse` scopes its uniqueness check to ONE file — so the sensor needs to know
|
|
47
|
+
* which. Set by `loadScenarios`; absent on hand-built fixtures, which then count as one file.
|
|
48
|
+
*/
|
|
49
|
+
sourceFile?: string;
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
/** Format-tolerant: is this token an ID (project's scheme), not a prose word?
|
|
@@ -270,7 +276,7 @@ export function loadScenarios(featurePath: string): ScenarioInfo[] {
|
|
|
270
276
|
const feature = parser.parseFeatureFile(featurePath);
|
|
271
277
|
return (feature.scenarios || [])
|
|
272
278
|
.filter((s) => !s.stepsName && !s.hookType) // skip @steps/@hook blocks
|
|
273
|
-
.map((s) => classifyScenario(withBackground(s, feature.background)));
|
|
279
|
+
.map((s) => ({ ...classifyScenario(withBackground(s, feature.background)), sourceFile: featurePath }));
|
|
274
280
|
}
|
|
275
281
|
|
|
276
282
|
/**
|
package/src/harness/repair.ts
CHANGED
|
@@ -10,6 +10,7 @@ import * as path from 'path';
|
|
|
10
10
|
import { capabilityRegistry } from '../capabilities/registry';
|
|
11
11
|
import { discoverAndRegisterCapabilities } from '../capabilities/discover';
|
|
12
12
|
import { scoringCapabilityFor } from './audit';
|
|
13
|
+
import { readCapabilities } from './capability';
|
|
13
14
|
|
|
14
15
|
export interface RepairProposal { source: 'audit' | 'runtime'; signal: string; ruleId: string; fix: string }
|
|
15
16
|
export interface RepairPlan {
|
|
@@ -50,7 +51,13 @@ function failuresFromResult(file: string): string[] {
|
|
|
50
51
|
*/
|
|
51
52
|
export function planRepair(unitId: string, reportName: string, cwd: string, generatedDir?: string): RepairPlan {
|
|
52
53
|
discoverAndRegisterCapabilities();
|
|
53
|
-
|
|
54
|
+
// The ACTIVE PLATFORM is part of capability resolution, and leaving it out silently sent every
|
|
55
|
+
// mobile unit to the web catalog: `scoringCapabilityFor`'s third parameter is what lets `mobile`
|
|
56
|
+
// beat the default `ui`, `runAudit` passes it, and this call did not. A bare screen on a
|
|
57
|
+
// `platform: mobile` project therefore resolved to `ui` — whose repair rules are the WEB ones —
|
|
58
|
+
// so registering a mobile catalog would have changed nothing here. `cwd` is the project root
|
|
59
|
+
// (the CLI passes `process.cwd()`), which is where `qa/capabilities.yaml` lives.
|
|
60
|
+
const capId = scoringCapabilityFor(unitId, capabilityRegistry.defaultCapabilityId(), readCapabilities(cwd).platform);
|
|
54
61
|
const rules = (capId ? capabilityRegistry.get(capId)?.repair?.rules : undefined) ?? [];
|
|
55
62
|
|
|
56
63
|
const signals: { source: 'audit' | 'runtime'; text: string }[] = [];
|
|
@@ -38,7 +38,7 @@ export interface ScriptCheckResult {
|
|
|
38
38
|
findings: string[];
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
/** Split a spec into test() blocks (title + body) via brace counting. */
|
|
41
|
+
/** Split a spec into test() / it() blocks (title + body) via brace counting. */
|
|
42
42
|
export function extractTestBlocks(specSrc: string): { title: string; body: string[] }[] {
|
|
43
43
|
const lines = specSrc.split('\n');
|
|
44
44
|
const blocks: { title: string; body: string[] }[] = [];
|
|
@@ -46,7 +46,11 @@ export function extractTestBlocks(specSrc: string): { title: string; body: strin
|
|
|
46
46
|
// Same escape-aware title match as extractTestTitles — with `[^'"`]+` a
|
|
47
47
|
// quote-bearing title extracted truncated (or not at all), missed the
|
|
48
48
|
// automatedTitles lookup, and silently dropped out of the BYPASS scan.
|
|
49
|
-
|
|
49
|
+
// The `test|it` alternation matches extractTestTitles (#429): mobile/Appium specs
|
|
50
|
+
// are mocha and declare `it(`, so a `test(`-only splitter returned ZERO blocks for
|
|
51
|
+
// them and check C — the whole anti-bypass scan — silently examined nothing on
|
|
52
|
+
// every mobile unit (#707). Only extractTestTitles got the alternation in #429.
|
|
53
|
+
const m = lines[i].match(/\b(?:test|it)(?:\.(?:only|skip|fixme))?\(\s*(['"`])((?:\\.|(?!\1).)+)\1/);
|
|
50
54
|
if (!m) continue;
|
|
51
55
|
let depth = 0, started = false;
|
|
52
56
|
const body: string[] = [];
|
|
@@ -66,38 +70,180 @@ export function extractTestBlocks(specSrc: string): { title: string; body: strin
|
|
|
66
70
|
// reading as hollow: a sync void call after its step comment matched none of the
|
|
67
71
|
// original alternatives (no await/expect/testData, `page` only as a bare argument,
|
|
68
72
|
// no `=`), so @mock:passthrough scenarios were flagged BYPASS (field report) — on
|
|
69
|
-
// sungen's own mock-route golden fixture, no less. Comment lines are
|
|
70
|
-
// (
|
|
73
|
+
// sungen's own mock-route golden fixture, no less. Comment lines are excluded by the
|
|
74
|
+
// caller before this test runs (#673 — a step's region legitimately contains template
|
|
75
|
+
// comments), so `identifier(` cannot false-match a commented-out call.
|
|
71
76
|
const CODE_LINE = /\b(await|expect|testData|page\.)\b|=\s*[^=]|[\w$]\s*\(/;
|
|
72
77
|
|
|
78
|
+
// A line that proves something — the oracle vocabulary of BOTH adapters. Written as one
|
|
79
|
+
// pattern because the alternatives differ only in spelling, not in kind:
|
|
80
|
+
// expect( / expectPage( / expectCheckedState( playwright + its generated helpers
|
|
81
|
+
// __assertVisible( / __assertCheckedState( appium helpers
|
|
82
|
+
// db.assertRow( / assertNoUnmocked( / … data-driver and mock oracles
|
|
83
|
+
// throw new Error(`[sungen] Expected …`) appium value/text assertion, emitted inline
|
|
84
|
+
// Deliberately generous: a missed assertion costs a FALSE BYPASS, and this file's own
|
|
85
|
+
// convention is that a false BYPASS — the finding that makes a QA deliverable look
|
|
86
|
+
// untrustworthy — is the costlier error. See #707.
|
|
87
|
+
const ASSERTION_LINE = /\bexpect\w*\s*\(|\b(?:db\.)?assert\w*\s*\(|\b__assert\w*\s*\(|\[sungen\] Expected /;
|
|
88
|
+
|
|
89
|
+
// The compiler's own step-comment convention: an assertion step is emitted as `// Assert …`.
|
|
90
|
+
// It is the second, adapter-neutral oracle signal, and it is what keeps this check honest
|
|
91
|
+
// without chasing template internals: several Appium assertions compile to a bespoke polling
|
|
92
|
+
// loop closed by a `throw` (`Then User see device log contains …`), matching no call shape at
|
|
93
|
+
// all. Measured across the 55 committed goldens: 103 test blocks carry BOTH signals, 19 carry
|
|
94
|
+
// only a call (the api/db steps word their comments differently), 1 carries only the comment
|
|
95
|
+
// — so neither signal alone is sufficient and the union is what the check needs.
|
|
96
|
+
// A `// Assert` step that emitted nothing is NOT silently excused by this: an empty step
|
|
97
|
+
// region is precisely what the hollow-step scan below reports.
|
|
98
|
+
const ASSERTION_STEP_COMMENT = /^\s*\/\/\s*Assert\b/;
|
|
99
|
+
|
|
100
|
+
// A step the compiler deliberately compiles to NOTHING, and says so in the emitted comment.
|
|
101
|
+
// `appium/templates/steps/navigation/navigation.hbs` is the only comment-only step template
|
|
102
|
+
// on that adapter: an Appium session opens the app from the wdio capabilities, so
|
|
103
|
+
// `Given User is on [Home] page` has no navigation left to perform. It is not a hollow step —
|
|
104
|
+
// there is no code the adapter failed to emit. Without this, un-blinding check C on mobile
|
|
105
|
+
// reported a BYPASS on EVERY mobile unit that opens with a page step, i.e. all of them (#707).
|
|
106
|
+
// Anchored to the start of a comment line: the phrase is emitted by exactly one template today,
|
|
107
|
+
// but as a bare substring it would also exempt a genuinely hollow step whose test-data or step
|
|
108
|
+
// text happened to contain it.
|
|
109
|
+
const NOOP_STEP_MARKER = /^\s*\/\/\s*App launched via wdio capabilities/;
|
|
110
|
+
|
|
111
|
+
/** A spec comment line reduced to its text, for comparison against a compiler step comment. */
|
|
112
|
+
const commentText = (line: string): string => line.trim().replace(/^\/\/\s?/, '').trim();
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Which `//` lines of a test body are STEP markers (#673).
|
|
116
|
+
*
|
|
117
|
+
* Structural (preferred) — the compiler records the step comments it emitted per scenario
|
|
118
|
+
* (`CodeGenerator.stepTrace`), so a comment line is a step marker only when it IS one, matched
|
|
119
|
+
* in order. Template bodies, `// [from @steps:…]` section headers and a step's own trailing
|
|
120
|
+
* explanation lines are then region content rather than phantom steps.
|
|
121
|
+
*
|
|
122
|
+
* Heuristic fallback (no trace — a hand-rolled spec, or regeneration failed) — a RUN of
|
|
123
|
+
* consecutive comment lines counts as ONE marker, anchored at its first line. Attributing every
|
|
124
|
+
* comment line separately gave each one an empty region, so a step followed by so much as one
|
|
125
|
+
* explanation comment reported "emitted no code": 30+ false BYPASS findings on shipped templates.
|
|
126
|
+
*/
|
|
127
|
+
function stepMarkers(body: string[], trace?: string[]): { markers: number[]; structural: boolean } {
|
|
128
|
+
const commentIdx: number[] = [];
|
|
129
|
+
for (let k = 1; k < body.length; k++) if (body[k].trim().startsWith('//')) commentIdx.push(k);
|
|
130
|
+
// First line of each run of adjacent comments — the step comment precedes its template body.
|
|
131
|
+
const runs = { markers: commentIdx.filter((k, i) => i === 0 || commentIdx[i - 1] !== k - 1), structural: false };
|
|
132
|
+
if (!trace?.length) return runs;
|
|
133
|
+
const markers: number[] = [];
|
|
134
|
+
let from = 0;
|
|
135
|
+
for (const want of trace) {
|
|
136
|
+
const hit = commentIdx.findIndex((k, i) => i >= from && commentText(body[k]) === want.trim());
|
|
137
|
+
if (hit < 0) return runs; // the trace does not describe THIS block (stale/edited spec) → heuristic
|
|
138
|
+
markers.push(commentIdx[hit]);
|
|
139
|
+
from = hit + 1;
|
|
140
|
+
}
|
|
141
|
+
return { markers, structural: true };
|
|
142
|
+
}
|
|
143
|
+
|
|
73
144
|
/**
|
|
74
145
|
* Faithfulness / anti-bypass: a non-@manual test that performs actions but proves
|
|
75
146
|
* nothing (0 assertions), or a step-comment that emitted no executable code (hollow
|
|
76
147
|
* — the testcase step is not run). Structural 1:1 can't see these.
|
|
77
148
|
*/
|
|
78
|
-
export function analyzeFaithfulness(
|
|
149
|
+
export function analyzeFaithfulness(
|
|
150
|
+
specSrc: string,
|
|
151
|
+
automatedTitles: Set<string>,
|
|
152
|
+
stepTrace?: Map<string, string[]>,
|
|
153
|
+
) {
|
|
79
154
|
const assertionlessTests: string[] = [];
|
|
80
155
|
const hollowSteps: { test: string; step: string }[] = [];
|
|
81
156
|
for (const blk of extractTestBlocks(specSrc)) {
|
|
82
|
-
|
|
157
|
+
// Compare on the canonical scenario name, exactly as check A does: a mobile title carries
|
|
158
|
+
// its tags (`… [@high]`) and a @cases title its row-label suffix, so a raw comparison
|
|
159
|
+
// matched NOTHING on those units and check C skipped every one of them (#707).
|
|
160
|
+
const title = canonSpecTitle(blk.title);
|
|
161
|
+
if (!automatedTitles.has(title)) continue; // only non-@manual scenarios
|
|
83
162
|
// TQ-11 — a capability-pending @requires scenario compiles to a `test.skip(true, …)` stub:
|
|
84
163
|
// it intentionally proves nothing here (it runs once the driver is added), so it is not a bypass.
|
|
85
164
|
if (blk.body.some((l) => /\btest\.skip\(\s*true\b/.test(l))) continue;
|
|
165
|
+
// Same category on the appium adapter (#707): a web-only step compiles to a loud refusal
|
|
166
|
+
// (`throw new Error('[sungen] step … is web-only — the appium adapter cannot express it')`).
|
|
167
|
+
// It carries no assertion by design and can never pass silently — it always throws — so it
|
|
168
|
+
// is a refusal, not a bypass. Flagging it would report the adapter doing its job as a defect.
|
|
169
|
+
if (blk.body.some((l) => /web-only — the appium adapter cannot express it/.test(l))) continue;
|
|
86
170
|
const body = blk.body;
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
|
|
171
|
+
// Step boundaries come first — the assertionless check below needs them to tell an
|
|
172
|
+
// `// Assert …` step that emitted code from one that emitted nothing.
|
|
173
|
+
// stepTrace is keyed by the RAW scenario name (code-generator.ts), so it too must be
|
|
174
|
+
// looked up by the canonical title — on mobile the tagged spec title never hit it and
|
|
175
|
+
// check C silently fell back to its heuristic attribution.
|
|
176
|
+
const { markers, structural } = stepMarkers(body, stepTrace?.get(title));
|
|
177
|
+
const isComment = (l: string): boolean => l.trim().startsWith('//');
|
|
178
|
+
/** The lines a step owns: from its marker to the next marker (or the end of the block). */
|
|
179
|
+
const regionOf = (s: number): string[] =>
|
|
180
|
+
body.slice(markers[s] + 1, s + 1 < markers.length ? markers[s + 1] : body.length);
|
|
181
|
+
// What counts as an assertion, across both adapters:
|
|
182
|
+
// - Playwright `expect(...)`, and its generated helpers `expectPage(...)` /
|
|
183
|
+
// `expectCheckedState(...)` — `expect\(` alone missed these, since the literal
|
|
184
|
+
// `(` never follows `expect` in a helper name;
|
|
185
|
+
// - the appium helpers `__assertVisible(...)` / `__assertCheckedState(...)`, which is
|
|
186
|
+
// how the mobile adapter expresses EVERY visibility and checked-state assertion;
|
|
187
|
+
// - a Data Driver DB assertion (`db.assertRow/assertNoRow/assertCount/...`) or a
|
|
188
|
+
// mock assertion (`assertNoUnmocked(...)`) — a DB check is a real oracle, so a
|
|
189
|
+
// DB-only scenario (no UI expect) is NOT a bypass;
|
|
190
|
+
// - the appium value/text assertions, which compile to an inline comparison closed by
|
|
191
|
+
// `throw new Error('[sungen] Expected …')` rather than a helper call.
|
|
192
|
+
// Under the Playwright-literal detector that preceded this, un-blinding the mobile splitter
|
|
193
|
+
// reported 13 of 18 mobile-parity tests as assertionless — every one of them a false BYPASS.
|
|
194
|
+
//
|
|
195
|
+
// Both signals are read from CODE, never from a comment:
|
|
196
|
+
// - a comment is not an oracle. `alert-text-assertion.hbs` ships a commented-out
|
|
197
|
+
// `// expect(await dialogPromise1).toContain(…)` placeholder for the author to
|
|
198
|
+
// uncomment; scanning raw lines let that dead literal satisfy the check and silence
|
|
199
|
+
// a genuinely assertionless dialog test.
|
|
200
|
+
// - an `// Assert …` step counts only when the step actually EMITTED something. An
|
|
201
|
+
// assertion step that compiled to nothing is not an assertion, and it must stay
|
|
202
|
+
// visible here: the hollow-step scan below excuses an ambiguous LAST step on the
|
|
203
|
+
// heuristic path, so leaning on it as the sole backstop let a hollow trailing
|
|
204
|
+
// `// Assert …` escape both signals whenever the regenerate failed.
|
|
205
|
+
//
|
|
206
|
+
// On the heuristic path the `// Assert …` line is not always the marker: stepMarkers
|
|
207
|
+
// collapses a run of adjacent comments to its FIRST line, so a step whose comment sits
|
|
208
|
+
// under a header — an `@extend` `// [from @steps:…]` section header, a @cases row label —
|
|
209
|
+
// has its real step comment one line INTO the region. Reading only the marker there
|
|
210
|
+
// reported a genuinely-asserting polling test as assertionless. Scan the rest of the
|
|
211
|
+
// collapsed run, and only there: with structural attribution the markers ARE the compiler's
|
|
212
|
+
// step comments, nothing was collapsed, and a region's later comments are template body
|
|
213
|
+
// text that must not be mistaken for the step's own label.
|
|
214
|
+
const assertLabeled = (s: number): boolean => {
|
|
215
|
+
if (ASSERTION_STEP_COMMENT.test(body[markers[s]])) return true;
|
|
216
|
+
if (structural) return false;
|
|
217
|
+
for (const l of regionOf(s)) {
|
|
218
|
+
if (!isComment(l)) return false; // past the collapsed run
|
|
219
|
+
if (ASSERTION_STEP_COMMENT.test(l)) return true;
|
|
220
|
+
}
|
|
221
|
+
return false;
|
|
222
|
+
};
|
|
223
|
+
const hasOracle =
|
|
224
|
+
body.some((l) => !isComment(l) && ASSERTION_LINE.test(l)) ||
|
|
225
|
+
markers.some((_m, s) => assertLabeled(s)
|
|
226
|
+
&& regionOf(s).some((l) => !isComment(l) && CODE_LINE.test(l)));
|
|
227
|
+
if (!hasOracle) assertionlessTests.push(title);
|
|
91
228
|
// hollow step: a `// step` whose region (until the NEXT step-comment / block end)
|
|
92
229
|
// contains no executable code. The region — not just the next line — is checked,
|
|
93
230
|
// so block-style steps (`// Assert all … { … expect … }`) are correctly counted.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
231
|
+
// Heuristic path only: the block's LAST comment run is ambiguous — a genuinely hollow final
|
|
232
|
+
// step and a step's own trailing explanation (`alert-text-assertion.hbs` emits two such lines)
|
|
233
|
+
// have an identical shape, and only the compiler knows which it is. A false BYPASS is the
|
|
234
|
+
// costlier error — it is the finding that makes a QA deliverable look untrustworthy — so stay
|
|
235
|
+
// silent there and let the 0-assertion check carry a truly empty test.
|
|
236
|
+
const tailAmbiguous = !structural;
|
|
237
|
+
for (let s = 0; s < markers.length; s++) {
|
|
238
|
+
// Comment-only lines are never the step's code: with structural attribution a step's region
|
|
239
|
+
// legitimately holds its template's comments (`user-login-todo.hbs` emits six).
|
|
240
|
+
const region = regionOf(s);
|
|
241
|
+
const hasCode = region.some((l) => !isComment(l) && CODE_LINE.test(l));
|
|
242
|
+
// A documented no-op emits its explanation INTO the step's region, so read the region
|
|
243
|
+
// (not just the marker line) for it — see NOOP_STEP_MARKER.
|
|
244
|
+
if (hasCode || region.some((l) => NOOP_STEP_MARKER.test(l))) continue;
|
|
245
|
+
if (tailAmbiguous && s === markers.length - 1) continue;
|
|
246
|
+
hollowSteps.push({ test: title, step: body[markers[s]].trim().slice(0, 70) });
|
|
101
247
|
}
|
|
102
248
|
}
|
|
103
249
|
return { assertionlessTests, hollowSteps };
|
|
@@ -119,6 +265,22 @@ function extractTestTitles(specSrc: string): string[] {
|
|
|
119
265
|
return titles;
|
|
120
266
|
}
|
|
121
267
|
|
|
268
|
+
/**
|
|
269
|
+
* Reduce a title as it appears IN THE SPEC to the bare scenario name from the .feature,
|
|
270
|
+
* so both compare on the same footing. Two codegen shapes add a suffix:
|
|
271
|
+
* - mobile/mocha appends the pass-through tags (`… [@high @spec:FR-01]`) because mocha
|
|
272
|
+
* has no per-test tag option;
|
|
273
|
+
* - a @cases scenario emits `'<name> — ' + __row.__label` (or the legacy
|
|
274
|
+
* `<name> — ${__row.__label}`), extracted as the literal `<name> —`.
|
|
275
|
+
* Shared by check A (structural 1:1) and check C (faithfulness) — check C used to compare
|
|
276
|
+
* raw and so skipped every mobile and every @cases block it was handed (#707).
|
|
277
|
+
*/
|
|
278
|
+
function canonSpecTitle(t: string): string {
|
|
279
|
+
return t
|
|
280
|
+
.replace(/\s*\[@[^\]]*\]$/, '')
|
|
281
|
+
.replace(/\s*—(\s*\$\{__row\.__label\})?$/, '');
|
|
282
|
+
}
|
|
283
|
+
|
|
122
284
|
/** Undo the compiler's escapeQuotes escaping (\' \\ \n \r \t) so extracted titles
|
|
123
285
|
* compare against the raw scenario names from the .feature. */
|
|
124
286
|
function unescapeJsLiteral(s: string): string {
|
|
@@ -200,9 +362,7 @@ export async function runScriptCheck(screenDir: string, screenName: string, kind
|
|
|
200
362
|
// (and plain titles) compare on the same footing. Mobile/mocha specs append the
|
|
201
363
|
// tags to the title (`it('<name> [@high @spec:FR-01]')` — mocha has no tag option),
|
|
202
364
|
// so strip that suffix too or every mobile scenario reads MISSING + EXTRA (#429).
|
|
203
|
-
|
|
204
|
-
.replace(/\s*\[@[^\]]*\]$/, '')
|
|
205
|
-
.replace(/\s*—(\s*\$\{__row\.__label\})?$/, '');
|
|
365
|
+
// canonSpecTitle now lives at module scope — check C needs the identical reduction.
|
|
206
366
|
const specTitleSet = new Set(specTitles.map(canonSpecTitle));
|
|
207
367
|
const expectedSet = new Set(automated.map((s) => s.name));
|
|
208
368
|
const missingInSpec = automated.filter((s) => !specTitleSet.has(s.name)).map((s) => s.name);
|
|
@@ -217,6 +377,10 @@ export async function runScriptCheck(screenDir: string, screenName: string, kind
|
|
|
217
377
|
// B. Drift — regenerate to temp and diff
|
|
218
378
|
let drift: ScriptCheckResult['drift'] = committedSpec ? 'in-sync' : 'no-spec';
|
|
219
379
|
const driftHunks: string[] = [];
|
|
380
|
+
// #673 — the regenerate below also yields the compiler's step-boundary trace, which check C
|
|
381
|
+
// needs to tell a step comment from a template body comment. Hoisted out of the try so a
|
|
382
|
+
// failed regenerate degrades check C to its heuristic instead of crashing it.
|
|
383
|
+
let stepTrace: Map<string, string[]> | undefined;
|
|
220
384
|
if (committedSpec) {
|
|
221
385
|
try {
|
|
222
386
|
const { CodeGenerator } = require('../generators/test-generator/code-generator');
|
|
@@ -232,6 +396,7 @@ export async function runScriptCheck(screenDir: string, screenName: string, kind
|
|
|
232
396
|
? new CodeGenerator({ framework, runtimeData: true })
|
|
233
397
|
: new CodeGenerator({ framework, screenName, runtimeData: true, flowMode: kind === 'flow' });
|
|
234
398
|
await gen.generateAllTests(qaSourceDir, tmp, featureFiles);
|
|
399
|
+
stepTrace = gen.stepTrace;
|
|
235
400
|
// Pair each committed spec with its regenerated twin by filename — one `.feature`
|
|
236
401
|
// compiles to one `.spec.ts`, so a multi-feature unit has several of each.
|
|
237
402
|
const freshByName = new Map(findSpecs(tmp, screenName, kind).map((p) => [path.basename(p), p]));
|
|
@@ -265,7 +430,7 @@ export async function runScriptCheck(screenDir: string, screenName: string, kind
|
|
|
265
430
|
|
|
266
431
|
// C. Anti-bypass / faithfulness
|
|
267
432
|
const { assertionlessTests, hollowSteps } = committedSpec
|
|
268
|
-
? analyzeFaithfulness(specSrc, expectedSet)
|
|
433
|
+
? analyzeFaithfulness(specSrc, expectedSet, stepTrace)
|
|
269
434
|
: { assertionlessTests: [], hollowSteps: [] };
|
|
270
435
|
for (const t of assertionlessTests) {
|
|
271
436
|
findings.push(`BYPASS: test "${t}" has 0 assertions (action-only — proves nothing). The testcase is not really automated.`);
|