@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
|
@@ -8,6 +8,9 @@ import { SelectorType } from '../../../utils/selector-types';
|
|
|
8
8
|
import { DataResolver } from './data-resolver';
|
|
9
9
|
import { DiagnosticsCollector } from '../diagnostics';
|
|
10
10
|
import { DEFAULT_CONTENT_NAMED_ROLES } from '../app-profile';
|
|
11
|
+
import { reportUnknownEntryFields } from './selector-entry-fields';
|
|
12
|
+
import { reportUnknownSelectorTypes } from './selector-type-validation';
|
|
13
|
+
import { normalizeNth, reportInvalidNth } from './selector-nth-validation';
|
|
11
14
|
|
|
12
15
|
// Structured selector format v2
|
|
13
16
|
interface SelectorEntry {
|
|
@@ -15,7 +18,8 @@ interface SelectorEntry {
|
|
|
15
18
|
type?: SelectorType; // Locator strategy
|
|
16
19
|
value?: string; // Natural language value for the locator (e.g., 'button', 'link' for role)
|
|
17
20
|
name?: string; // Accessible name for role-based selectors (e.g., 'Login', 'Submit')
|
|
18
|
-
nth?: number; // Element index
|
|
21
|
+
nth?: number; // Element index: undefined = no positional filter, 0+ = that index,
|
|
22
|
+
// -1 = the last match. Anything else is dropped + SG-W018 (#720)
|
|
19
23
|
exact?: boolean; // Whether to use exact matching (default: false)
|
|
20
24
|
inputMethod?: string; // 'pressSequentially' for contenteditable/rich-text elements
|
|
21
25
|
|
|
@@ -46,6 +50,28 @@ interface SelectorEntry {
|
|
|
46
50
|
// === v2: Table ===
|
|
47
51
|
columns?: Record<string, { index: number; header: string; elements?: Record<string, SelectorEntry> }>;
|
|
48
52
|
pagination?: Record<string, string>;
|
|
53
|
+
// What EVERY row of this table matches, relative to the table locator (issue #688). The table
|
|
54
|
+
// templates assume an ARIA table — `getByRole('row')`, `tbody` — and on a list built from plain
|
|
55
|
+
// <div>s that universe is 0 elements, so `is hidden` / `is empty` pass without checking anything.
|
|
56
|
+
// `rows: 'div.job-row'` replaces the whole row chain. Distinct from a ROW LABEL's own `type: locator`
|
|
57
|
+
// entry, which positions ONE row (`tbody tr:first-child`) — a shape is a property of the table.
|
|
58
|
+
rows?: string;
|
|
59
|
+
|
|
60
|
+
// === Checked/selected state signal (issue #730) ===
|
|
61
|
+
// How a control publishes its on/off state when it publishes NO ARIA. A Tailwind checkbox or
|
|
62
|
+
// card picker — a <div> with a class swap and no <input>, no role, no aria-checked/aria-pressed —
|
|
63
|
+
// has no state for `check`/`uncheck` to drive or for `is checked`/`is selected` to read, so every
|
|
64
|
+
// such step failed with "it has no on/off state to set" no matter how right the selector was.
|
|
65
|
+
// `checked: { class: 'is-checked' }` names the class that marks the ON state.
|
|
66
|
+
//
|
|
67
|
+
// `selected:` is an ALIAS for the same signal, not a second one: a card picker declaring
|
|
68
|
+
// `checked:` reads wrong, and `is selected` on a non-ARIA element already falls through to the
|
|
69
|
+
// checked family (selected-assertion.hbs). Declaring both is a conflict — see resolveFromEntry.
|
|
70
|
+
// `checked.attribute` is the same signal published as a boolean attribute ('true'/'false')
|
|
71
|
+
// instead of a class, for a control that swaps `data-checked` rather than a class token.
|
|
72
|
+
// Declare ONE of the two per entry: class and attribute are two ways to read one state.
|
|
73
|
+
checked?: { class?: string; attribute?: string };
|
|
74
|
+
selected?: { class?: string; attribute?: string };
|
|
49
75
|
|
|
50
76
|
// === v2: Assertion Helpers ===
|
|
51
77
|
attribute?: string; // Attribute to check (for "has" assertions)
|
|
@@ -117,7 +143,7 @@ export interface ResolvedSelector {
|
|
|
117
143
|
role?: string; // For role-based selectors (e.g., 'button', 'link')
|
|
118
144
|
name?: string; // For accessible name in role selectors
|
|
119
145
|
locator?: string; // CSS locator for fallback
|
|
120
|
-
nth?: number; // Index for .nth() if multiple matches exist
|
|
146
|
+
nth?: number; // Index for .nth() if multiple matches exist (0+, or -1 = last match)
|
|
121
147
|
exact?: boolean; // Whether to use exact matching (default: false)
|
|
122
148
|
inputMethod?: string; // 'pressSequentially' for contenteditable/rich-text elements
|
|
123
149
|
|
|
@@ -130,9 +156,15 @@ export interface ResolvedSelector {
|
|
|
130
156
|
contenteditable?: boolean; // Rich text editor
|
|
131
157
|
columns?: Record<string, { index: number; header: string; elements?: Record<string, any> }>;
|
|
132
158
|
pagination?: Record<string, string>;
|
|
159
|
+
rows?: string; // Row shape, relative to the table (issue #688) — see SelectorEntry.rows
|
|
133
160
|
attribute?: string; // Attribute to check
|
|
134
161
|
pattern?: string; // Regex for attribute value
|
|
135
162
|
expanded?: { class?: string; attribute?: string; state?: 'none' }; // Expand/collapse state signal
|
|
163
|
+
// Class / boolean attribute marking the ON state, normalised from `checked:`/`selected:`
|
|
164
|
+
// (issue #730). Flat rather than an object because ONE resolved signal is what every consumer
|
|
165
|
+
// needs — the two YAML spellings are an authoring convenience, not two different things.
|
|
166
|
+
checkedClass?: string;
|
|
167
|
+
checkedAttribute?: string;
|
|
136
168
|
anchor?: ResolvedSelector; // page entries only — the rendered-content proof behind `see [X] page` (#662)
|
|
137
169
|
}
|
|
138
170
|
|
|
@@ -150,13 +182,38 @@ export class SelectorResolver {
|
|
|
150
182
|
private diagnostics?: DiagnosticsCollector;
|
|
151
183
|
private contentNamedRoles: string[] = DEFAULT_CONTENT_NAMED_ROLES;
|
|
152
184
|
private warnedContentNamed = new Set<string>();
|
|
185
|
+
/** The adapter's target platform — gates the list-member auto-infer (#665). Defaults to web. */
|
|
186
|
+
private platform: 'web' | 'mobile' = 'web';
|
|
153
187
|
|
|
154
|
-
constructor(selectorsDir?: string, screenName?: string) {
|
|
188
|
+
constructor(selectorsDir?: string, screenName?: string, platform: 'web' | 'mobile' = 'web') {
|
|
155
189
|
this.selectorsDir = selectorsDir || path.join(process.cwd(), 'qa', 'selectors', 'screens');
|
|
156
190
|
this.screenName = screenName;
|
|
191
|
+
this.platform = platform;
|
|
157
192
|
}
|
|
158
193
|
|
|
159
|
-
/**
|
|
194
|
+
/**
|
|
195
|
+
* A role that resolved with NO accessible name, on the mobile adapter — a dead selector (#665).
|
|
196
|
+
*
|
|
197
|
+
* `appium-selector.hbs` renders a role as `~{{name || value}}`, so with no name it emits the role
|
|
198
|
+
* TOKEN itself: `~listitem`, `~status`. A native a11y tree has no role concept and contains no
|
|
199
|
+
* such string, so the locator can never match — yet it compiled clean and only failed at run time
|
|
200
|
+
* with `element with selector "~listitem" wasn't found`.
|
|
201
|
+
*
|
|
202
|
+
* The invariant is "role with no name", NOT "role in LIST_MEMBER_ROLES". Both are reachable and
|
|
203
|
+
* both are dead:
|
|
204
|
+
* · auto-infer — only the three LIST_MEMBER_ROLES are nameless (the other 22 default the label)
|
|
205
|
+
* · an entry — a list-member role withholds the name by design, and `name: null` is the
|
|
206
|
+
* documented opt-out for a content-named role, so `type: role` reaches it too
|
|
207
|
+
* One predicate covers every door; the CALLERS choose the remedy, because it differs: with no
|
|
208
|
+
* entry the fix is "add one", with an entry the fix is "change its type".
|
|
209
|
+
*
|
|
210
|
+
* Web is unaffected — there the role IS the locator (see LIST_MEMBER_ROLES).
|
|
211
|
+
*/
|
|
212
|
+
private isUnusableMobileRole(r: ResolvedSelector | null | undefined): boolean {
|
|
213
|
+
return this.platform === 'mobile' && !!r && r.strategy === 'role' && !r.name;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Attach the compile-diagnostics collector (SG-W003, SG-W016, SG-W030) */
|
|
160
217
|
setDiagnostics(collector: DiagnosticsCollector): void {
|
|
161
218
|
this.diagnostics = collector;
|
|
162
219
|
}
|
|
@@ -452,11 +509,28 @@ export class SelectorResolver {
|
|
|
452
509
|
// Auto-infer locator from elementType when no exact YAML entry exists
|
|
453
510
|
if (elementType) {
|
|
454
511
|
const inferred = SelectorResolver.inferFromElementType(elementType, displayLabel);
|
|
455
|
-
|
|
512
|
+
|
|
513
|
+
// #665 — auto-infer is an ARIA concept. It survives on mobile only because a label-named
|
|
514
|
+
// role hands the appium template a `name` to render as an accessibility-id (`[Submit]
|
|
515
|
+
// button` → `~Submit`, a plausible guess). A nameless role has nothing to hand it, and the
|
|
516
|
+
// template's `~{{name || value}}` fell through to the role TOKEN — `[Job List] item` →
|
|
517
|
+
// `~listitem`, a string no Android/iOS a11y tree contains. Clean compile, guaranteed miss.
|
|
518
|
+
//
|
|
519
|
+
// Declining rather than warning is what repairs the workflow. `sungen-selector-fix` Phase 0
|
|
520
|
+
// decides whether to scan the live page by asking whether the ref "can't be auto-inferred";
|
|
521
|
+
// while auto-infer answered yes, Phase 0 skipped the scan, so no real selector was ever
|
|
522
|
+
// written and a warning would have reported the breakage with the repair path still switched
|
|
523
|
+
// off. Declining makes Phase 0 scan, and if it was skipped the not-found throw below names
|
|
524
|
+
// the exact key to add — the remedy here is "add an entry", which is what that error says.
|
|
525
|
+
//
|
|
526
|
+
// Web is untouched: `getByRole('listitem')` with no name is a real, working locator there.
|
|
527
|
+
if (inferred && !this.isUnusableMobileRole(inferred)) {
|
|
456
528
|
// Same SG-W003 class as the explicit-entry path: an auto-inferred name on a
|
|
457
529
|
// name-from-content role ([Loading] spinner → getByRole('status', { name: 'Loading' }))
|
|
458
530
|
// can never match. Add a YAML entry with name: null to opt out.
|
|
459
|
-
|
|
531
|
+
// WEB ONLY — see the note at the entry-path emission (#665).
|
|
532
|
+
if (this.platform !== 'mobile'
|
|
533
|
+
&& inferred.strategy === 'role' && inferred.name && inferred.role && this.contentNamedRoles.includes(inferred.role)) {
|
|
460
534
|
const warnKey = `${inferred.role}:${displayLabel}`;
|
|
461
535
|
if (!this.warnedContentNamed.has(warnKey)) {
|
|
462
536
|
this.warnedContentNamed.add(warnKey);
|
|
@@ -546,9 +620,137 @@ export class SelectorResolver {
|
|
|
546
620
|
});
|
|
547
621
|
}
|
|
548
622
|
|
|
623
|
+
/** Encode `{{var}}` references inside a table's `columns: { <col>: { header } }` map. */
|
|
624
|
+
private static resolveColumnHeaders(
|
|
625
|
+
columns: NonNullable<SelectorEntry['columns']>,
|
|
626
|
+
): NonNullable<SelectorEntry['columns']> {
|
|
627
|
+
const out: NonNullable<SelectorEntry['columns']> = {};
|
|
628
|
+
for (const [key, col] of Object.entries(columns)) {
|
|
629
|
+
out[key] = col && typeof col.header === 'string'
|
|
630
|
+
? { ...col, header: SelectorResolver.resolveTemplateVars(col.header) }
|
|
631
|
+
: col;
|
|
632
|
+
}
|
|
633
|
+
return out;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Normalise `checked:` / `selected:` into the one signal consumers read (issue #730). The two
|
|
638
|
+
* spellings are an authoring convenience for the same thing — `checked:` on a toggle, `selected:`
|
|
639
|
+
* on a card/tab picker — so they collapse here rather than at every call site. Each spelling can
|
|
640
|
+
* publish the state as a class token or as a boolean attribute; one entry declares one of them.
|
|
641
|
+
*
|
|
642
|
+
* Validated at CODEGEN because every failure mode here is silent at run time: the class token
|
|
643
|
+
* lands inside `[class~="..."]`, where a quote breaks the selector and whitespace can never match
|
|
644
|
+
* (the `~=` operator matches ONE whitespace-separated token, so 'a b' matches nothing at all and
|
|
645
|
+
* the step would just time out claiming the control never changed state); an attribute name has
|
|
646
|
+
* the same exposure inside `[name="true"]`. Refused rather than warned-and-dropped, unlike
|
|
647
|
+
* `expanded:`: dropping this signal leaves the step with nothing to read, so it would fail again
|
|
648
|
+
* with the original "no on/off state" error and hide the real cause.
|
|
649
|
+
*/
|
|
650
|
+
private resolveCheckedSignal(
|
|
651
|
+
entry: SelectorEntry,
|
|
652
|
+
label: string,
|
|
653
|
+
): { class?: string; attribute?: string } | undefined {
|
|
654
|
+
const signal: { class?: string; attribute?: string } = {};
|
|
655
|
+
// Recorded BEFORE it is thrown, every time. A pattern that resolves its selector inside a
|
|
656
|
+
// `catch {}` — several do, to fall back when a `[Ref]` simply is not in the file — would
|
|
657
|
+
// otherwise swallow the refusal whole and emit a step against an unresolved locator, with
|
|
658
|
+
// nothing anywhere naming the entry. The throw is what stops the file; the diagnostic is what
|
|
659
|
+
// survives a caller that decided not to be stopped, and what `--strict` counts.
|
|
660
|
+
const refuse = (message: string): never => {
|
|
661
|
+
this.diagnostics?.add('SG-E031', message, { feature: this.featureName });
|
|
662
|
+
throw new Error(message);
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
for (const key of ['class', 'attribute'] as const) {
|
|
666
|
+
const fromChecked = entry.checked?.[key]?.trim();
|
|
667
|
+
const fromSelected = entry.selected?.[key]?.trim();
|
|
668
|
+
|
|
669
|
+
if (fromChecked && fromSelected && fromChecked !== fromSelected) {
|
|
670
|
+
refuse(
|
|
671
|
+
`Selector "[${label}]" declares both \`checked.${key}\` ("${fromChecked}") and ` +
|
|
672
|
+
`\`selected.${key}\` ("${fromSelected}") with different values. They are two spellings of ONE ` +
|
|
673
|
+
`signal — how the control marks its ON state — so only one can be right. Keep \`checked:\` for ` +
|
|
674
|
+
`a toggle/checkbox or \`selected:\` for a card/tab picker, and delete the other.`
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const value = fromChecked || fromSelected;
|
|
679
|
+
if (!value) continue;
|
|
680
|
+
|
|
681
|
+
if (/\s/.test(value)) {
|
|
682
|
+
refuse(
|
|
683
|
+
`Selector "[${label}]" declares checked/selected ${key} "${value}", which contains whitespace. ` +
|
|
684
|
+
`This must be ONE ${key === 'class' ? 'class token, not a class list' : 'attribute name'}: the ` +
|
|
685
|
+
`generated matcher tests a single token, so a multi-token value can never match and the step ` +
|
|
686
|
+
`would time out rather than fail clearly. Pick the one ${key} that marks the ON state ` +
|
|
687
|
+
`(e.g. '${key === 'class' ? 'border-link' : 'data-checked'}', not ` +
|
|
688
|
+
`'${key === 'class' ? 'card border-link' : 'data-checked aria-checked'}').`
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
if (value.includes('"') || value.includes('\\')) {
|
|
692
|
+
refuse(
|
|
693
|
+
`Selector "[${label}]" declares checked/selected ${key} "${value}", which contains a quote or ` +
|
|
694
|
+
`backslash. The value is embedded in a [${key === 'class' ? 'class~="..."' : 'name="true"'}] CSS ` +
|
|
695
|
+
`selector, which those characters would break. A ${key} carrying them is almost certainly a typo.`
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
signal[key] = value;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (signal.class && signal.attribute) {
|
|
702
|
+
refuse(
|
|
703
|
+
`Selector "[${label}]" declares both a checked/selected \`class\` ("${signal.class}") and ` +
|
|
704
|
+
`\`attribute\` ("${signal.attribute}"). One control publishes its ON state one way — declaring ` +
|
|
705
|
+
`two lets an action drive one signal while the assertion after it reads the other. Keep the one ` +
|
|
706
|
+
`the component actually swaps.`
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
return signal.class || signal.attribute ? signal : undefined;
|
|
711
|
+
}
|
|
712
|
+
|
|
549
713
|
/**
|
|
550
714
|
* Resolve from new structured entry format
|
|
551
715
|
*/
|
|
716
|
+
/**
|
|
717
|
+
* An `expanded:` signal's `class` / `attribute` value is pasted into the generated spec — into a
|
|
718
|
+
* string literal, a regex, a `classList.contains()` argument. selectors.yaml is often generated
|
|
719
|
+
* from the page under test, so a value carrying a quote, a `${`, whitespace or any other character
|
|
720
|
+
* a class or attribute name cannot legitimately contain is refused (diagnostic + the field is
|
|
721
|
+
* dropped) rather than compiled into code. Dropping it leaves a working step — codegen falls back
|
|
722
|
+
* to driving/asserting `aria-expanded` — which is why this warns where the checked/selected signal,
|
|
723
|
+
* whose fallback is the failure it exists to fix, refuses outright.
|
|
724
|
+
*/
|
|
725
|
+
private validatedStateSignal(
|
|
726
|
+
signal: { class?: string; attribute?: string },
|
|
727
|
+
field: string,
|
|
728
|
+
label: string,
|
|
729
|
+
): { class?: string; attribute?: string } | undefined {
|
|
730
|
+
// Letters, digits and the punctuation real class/attribute names use — including Tailwind
|
|
731
|
+
// arbitrary values (`bg-[#eff6ff]`, `!mt-0`, `@md:flex`, `w-[calc(100%-2rem)]`). Excluded on
|
|
732
|
+
// purpose: whitespace, quotes, backticks, backslash, `$`/`{`/`}` — the characters that could
|
|
733
|
+
// close a literal or open an interpolation in the generated code.
|
|
734
|
+
const SAFE = /^[A-Za-z0-9_\-:./[\]%#!@(),&+=*~|?]+$/;
|
|
735
|
+
const out: { class?: string; attribute?: string } = {};
|
|
736
|
+
for (const key of ['class', 'attribute'] as const) {
|
|
737
|
+
const value = signal[key];
|
|
738
|
+
if (value === undefined || value === null) continue;
|
|
739
|
+
const text = String(value);
|
|
740
|
+
if (SAFE.test(text)) { out[key] = text; continue; }
|
|
741
|
+
const warnKey = `w030:${label}:${field}.${key}`;
|
|
742
|
+
if (!this.warnedContentNamed.has(warnKey)) { // once per entry+field, not once per step that uses it
|
|
743
|
+
this.warnedContentNamed.add(warnKey);
|
|
744
|
+
this.diagnostics?.add(
|
|
745
|
+
'SG-W030',
|
|
746
|
+
`selector "${label}": ${field}.${key} ${JSON.stringify(text)} is not accepted as a ${key} name (no whitespace, quotes, backslash, \`$\`, \`{\` or \`}\`) — the value is ignored and the step compiles to its default form`,
|
|
747
|
+
{ feature: this.featureName },
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return out.class || out.attribute ? out : undefined;
|
|
752
|
+
}
|
|
753
|
+
|
|
552
754
|
private resolveFromEntry(entry: SelectorEntry, originalLabel: string): ResolvedSelector {
|
|
553
755
|
// Default values
|
|
554
756
|
const locator = entry.locator || '';
|
|
@@ -570,9 +772,22 @@ export class SelectorResolver {
|
|
|
570
772
|
: (entry.name ?? '');
|
|
571
773
|
const value = SelectorResolver.resolveTemplateVars(rawValue);
|
|
572
774
|
const name = SelectorResolver.resolveTemplateVars(rawName);
|
|
573
|
-
|
|
775
|
+
// `nth:` is the one field whose VALUE nothing used to check (#720): anything non-null was
|
|
776
|
+
// handed to the adapters, which disagreed about it — web dropped `-1` silently, mobile emitted
|
|
777
|
+
// `$$(...)[-1]`. Normalised to a real index (integer >= -1) or `undefined` HERE, so no template
|
|
778
|
+
// ever renders a value it cannot express; the out-of-contract write is reported as SG-W018 at
|
|
779
|
+
// the file load point below.
|
|
780
|
+
const nth = normalizeNth(entry.nth);
|
|
574
781
|
const exact = entry.exact === true || entry.match === 'exact';
|
|
575
|
-
|
|
782
|
+
// `contenteditable: true` is the MANUAL override for a rich-text editor (#712). It was a
|
|
783
|
+
// documented, plumbed field that nothing read: the rich-text decision lives in driver-ui's
|
|
784
|
+
// fill pattern, which gates on `inputMethod` to pick `fill-editor-action`, so an author who
|
|
785
|
+
// followed the selector reference and wrote `contenteditable: true` got a plain `.fill()` and
|
|
786
|
+
// no diagnostic. Forcing the input method HERE rather than adding a second condition in the
|
|
787
|
+
// pattern keeps ONE rich-text signal on ResolvedSelector, so every consumer — current and
|
|
788
|
+
// future — reads the intent off a single field. An explicit `inputMethod` is more specific and
|
|
789
|
+
// still wins, so the two may be mixed without surprise.
|
|
790
|
+
const inputMethod = entry.inputMethod ?? (entry.contenteditable ? 'pressSequentially' : undefined);
|
|
576
791
|
|
|
577
792
|
// v2 fields
|
|
578
793
|
const v2Fields: Partial<ResolvedSelector> = {};
|
|
@@ -582,11 +797,21 @@ export class SelectorResolver {
|
|
|
582
797
|
if (entry.trigger) v2Fields.trigger = entry.trigger;
|
|
583
798
|
if (entry.frame) v2Fields.frame = entry.frame;
|
|
584
799
|
if (entry.contenteditable) v2Fields.contenteditable = true;
|
|
585
|
-
|
|
800
|
+
// A column's `header` is DOM text like any other selector field, so a `{{var}}` in it is a
|
|
801
|
+
// runtime reference — the same i18n escape hatch `name:`/`value:` have. Encoded here rather
|
|
802
|
+
// than at the read site so every consumer of the map gets it (issue #674).
|
|
803
|
+
if (entry.columns) v2Fields.columns = SelectorResolver.resolveColumnHeaders(entry.columns);
|
|
586
804
|
if (entry.pagination) v2Fields.pagination = entry.pagination;
|
|
805
|
+
if (typeof entry.rows === 'string' && entry.rows.trim()) v2Fields.rows = entry.rows.trim();
|
|
587
806
|
if (entry.attribute) v2Fields.attribute = entry.attribute;
|
|
588
807
|
if (entry.pattern) v2Fields.pattern = entry.pattern;
|
|
589
|
-
if (entry.expanded)
|
|
808
|
+
if (entry.expanded) {
|
|
809
|
+
const expanded = this.validatedStateSignal(entry.expanded, 'expanded', originalLabel);
|
|
810
|
+
if (expanded || entry.expanded.state) v2Fields.expanded = { ...expanded, ...(entry.expanded.state ? { state: entry.expanded.state } : {}) };
|
|
811
|
+
}
|
|
812
|
+
const checked = this.resolveCheckedSignal(entry, originalLabel);
|
|
813
|
+
if (checked?.class) v2Fields.checkedClass = checked.class;
|
|
814
|
+
if (checked?.attribute) v2Fields.checkedAttribute = checked.attribute;
|
|
590
815
|
// The anchor is a selector in its own right; it must not inherit the PAGE's label as its
|
|
591
816
|
// accessible name, so it resolves against its own name (or nothing).
|
|
592
817
|
if (entry.anchor) v2Fields.anchor = this.resolveFromEntry(entry.anchor, entry.anchor.name ?? '');
|
|
@@ -622,7 +847,26 @@ export class SelectorResolver {
|
|
|
622
847
|
// If type is 'locator', use locator field (or value as fallback) as the CSS locator directly.
|
|
623
848
|
// `exact` travels along: a locator LIST filtered by a test-data value (hasText) matches by
|
|
624
849
|
// substring by default, and `exact: true` in the YAML opts into the anchored full-text match.
|
|
625
|
-
|
|
850
|
+
//
|
|
851
|
+
// `upload` rides the SAME branch rather than getting a case of its own. A file input is
|
|
852
|
+
// addressed by CSS in practice (`input[type=file]`, an id, a testid wrapper), and the selector
|
|
853
|
+
// reference has always documented `upload` as `page.locator(value).setInputFiles()` — so the
|
|
854
|
+
// two spellings must resolve identically or teams migrating off the workaround get a silent
|
|
855
|
+
// behaviour change. It had no case at all before, so it fell through to `default:` and
|
|
856
|
+
// compiled to `getByPlaceholder('<the CSS string>')` — a locator hunting an input whose
|
|
857
|
+
// placeholder ATTRIBUTE is the literal text `input[type=file]`, which no page has. It failed
|
|
858
|
+
// at run time with no diagnostic, and the only way out was to work out for oneself that
|
|
859
|
+
// `type: locator` had to be used instead, which no page of the reference says (issue #690).
|
|
860
|
+
//
|
|
861
|
+
// Handled HERE, above the generic `locator:`-field branch below, because that branch returns
|
|
862
|
+
// strategy 'css' and drops `exact` — putting `upload` in the switch left the two spellings
|
|
863
|
+
// diverging for any entry that sets `locator:`. `withExtras` still stamps the declared
|
|
864
|
+
// `selectorType`, so a consumer that needs to tell an upload from a plain locator still can.
|
|
865
|
+
//
|
|
866
|
+
// `exact` is inert for the upload action itself (`setInputFiles` takes no text filter), but it
|
|
867
|
+
// is NOT dead: the same key reused by a filtered step (`… with {{v}}`) reaches has-text-value,
|
|
868
|
+
// which reads `exact` regardless of strategy.
|
|
869
|
+
if (type === 'locator' || type === 'upload') {
|
|
626
870
|
const locatorValue = locator && locator.trim() ? locator : value;
|
|
627
871
|
return withExtras({
|
|
628
872
|
strategy: 'locator',
|
|
@@ -658,7 +902,16 @@ export class SelectorResolver {
|
|
|
658
902
|
// reference text. For name-from-content roles (status/alert/…) that filter can
|
|
659
903
|
// never match the live element (its accessible name is computed from its content,
|
|
660
904
|
// e.g. a toast's message text) — issue #467. Opt out with `name: null`.
|
|
661
|
-
|
|
905
|
+
//
|
|
906
|
+
// WEB ONLY. "The accessible name is computed from content" is an ARIA rule with no mobile
|
|
907
|
+
// counterpart: the appium template turns a role into an accessibility-id lookup, so
|
|
908
|
+
// `[Loading] spinner` → `~Loading` is exactly as legitimate a guess as `[Submit] button`
|
|
909
|
+
// → `~Submit`, which this compiler emits happily. On mobile the warning was therefore a
|
|
910
|
+
// false positive — and worse, its remedy made things strictly worse: `name: null` leaves
|
|
911
|
+
// the role nameless, which now renders as the role TOKEN and is refused outright (#665).
|
|
912
|
+
// A wrong selector fails loudly at run time; advice that manufactures a dead one does not.
|
|
913
|
+
if (this.platform !== 'mobile'
|
|
914
|
+
&& entry.name === undefined && name && this.contentNamedRoles.includes(value)) {
|
|
662
915
|
const warnKey = `${value}:${originalLabel}`;
|
|
663
916
|
if (!this.warnedContentNamed.has(warnKey)) {
|
|
664
917
|
this.warnedContentNamed.add(warnKey);
|
|
@@ -669,14 +922,31 @@ export class SelectorResolver {
|
|
|
669
922
|
);
|
|
670
923
|
}
|
|
671
924
|
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
925
|
+
{
|
|
926
|
+
const resolvedRole = withExtras({
|
|
927
|
+
strategy: 'role',
|
|
928
|
+
role: value,
|
|
929
|
+
name: name, // Use the name field for accessible name
|
|
930
|
+
value,
|
|
931
|
+
nth,
|
|
932
|
+
exact,
|
|
933
|
+
});
|
|
934
|
+
// #665 — the same dead selector as the auto-infer path, reached through an ENTRY: a
|
|
935
|
+
// list-member role withholds its name by design, and `name: null` is the documented
|
|
936
|
+
// opt-out for a content-named role. Either way a mobile role with no name renders as
|
|
937
|
+
// the role token. Different remedy from the auto-infer case, so a different message:
|
|
938
|
+
// the entry exists, it is the TYPE that cannot work here.
|
|
939
|
+
if (this.isUnusableMobileRole(resolvedRole)) {
|
|
940
|
+
throw new Error(
|
|
941
|
+
`Selector "${originalLabel}" is \`type: role, value: ${value}\`${name === null ? ' with `name: null`' : ''}, ` +
|
|
942
|
+
`which resolves to a role with no accessible name. A native app has no role concept, so this compiles to ` +
|
|
943
|
+
`the accessibility-id "~${value}" — a string no Android/iOS a11y tree contains, and the step can never match. ` +
|
|
944
|
+
`Give this entry a locator the device actually exposes: \`type: accessibility-id\` (a content-desc / ` +
|
|
945
|
+
`accessibilityIdentifier), \`type: xpath\`, or \`type: id\`.`
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
return resolvedRole;
|
|
949
|
+
}
|
|
680
950
|
|
|
681
951
|
case 'testid':
|
|
682
952
|
return withExtras({
|
|
@@ -722,7 +992,14 @@ export class SelectorResolver {
|
|
|
722
992
|
});
|
|
723
993
|
|
|
724
994
|
default:
|
|
725
|
-
//
|
|
995
|
+
// Reached by the NON_STRATEGY_SELECTOR_TYPES (`page`/`table`/`column`/`frame`, whose
|
|
996
|
+
// consumers read the entry or `selectorType` rather than `strategy`) and by a type this
|
|
997
|
+
// compiler does not know at all — YAML is untyped at run time, so a misspelling lands
|
|
998
|
+
// here too. The placeholder result is inert for the first group and wrong for the second,
|
|
999
|
+
// but it MUST NOT throw: `page` alone reaches this branch on every page assertion.
|
|
1000
|
+
// What must never arrive here is a STRATEGY type — that is the #690 defect, and
|
|
1001
|
+
// tests/codegen/selector-type-dispatch.run.ts fails the build if one does (it checks the
|
|
1002
|
+
// resolved strategy, so an early return above satisfies it as well as a `case` here).
|
|
726
1003
|
return withExtras({
|
|
727
1004
|
strategy: 'placeholder',
|
|
728
1005
|
value,
|
|
@@ -737,13 +1014,18 @@ export class SelectorResolver {
|
|
|
737
1014
|
* Returns metadata only - no framework-specific code
|
|
738
1015
|
*/
|
|
739
1016
|
private resolveByPriority(element: LegacySelectorElement): ResolvedSelector {
|
|
1017
|
+
// The legacy format never passes through the SG-W018 file check (that walks new-format
|
|
1018
|
+
// entries), so it is normalised here instead — a template must never see an index it cannot
|
|
1019
|
+
// express, whichever door the entry came through.
|
|
1020
|
+
const nth = normalizeNth(element.nth);
|
|
1021
|
+
|
|
740
1022
|
// Extract data-testid from locator if present
|
|
741
1023
|
const testIdMatch = element.locator.match(/\[data-testid=["']([^"']+)["']\]/);
|
|
742
1024
|
if (testIdMatch) {
|
|
743
1025
|
return {
|
|
744
1026
|
strategy: 'testid',
|
|
745
1027
|
value: testIdMatch[1],
|
|
746
|
-
nth
|
|
1028
|
+
nth,
|
|
747
1029
|
};
|
|
748
1030
|
}
|
|
749
1031
|
|
|
@@ -754,7 +1036,7 @@ export class SelectorResolver {
|
|
|
754
1036
|
role: 'button',
|
|
755
1037
|
name: element.value,
|
|
756
1038
|
value: element.value,
|
|
757
|
-
nth
|
|
1039
|
+
nth,
|
|
758
1040
|
};
|
|
759
1041
|
}
|
|
760
1042
|
|
|
@@ -762,7 +1044,7 @@ export class SelectorResolver {
|
|
|
762
1044
|
return {
|
|
763
1045
|
strategy: 'placeholder',
|
|
764
1046
|
value: element.value,
|
|
765
|
-
nth
|
|
1047
|
+
nth,
|
|
766
1048
|
};
|
|
767
1049
|
}
|
|
768
1050
|
|
|
@@ -770,7 +1052,7 @@ export class SelectorResolver {
|
|
|
770
1052
|
return {
|
|
771
1053
|
strategy: 'label',
|
|
772
1054
|
value: element.value,
|
|
773
|
-
nth
|
|
1055
|
+
nth,
|
|
774
1056
|
};
|
|
775
1057
|
}
|
|
776
1058
|
|
|
@@ -778,7 +1060,7 @@ export class SelectorResolver {
|
|
|
778
1060
|
return {
|
|
779
1061
|
strategy: 'text',
|
|
780
1062
|
value: element.value,
|
|
781
|
-
nth
|
|
1063
|
+
nth,
|
|
782
1064
|
};
|
|
783
1065
|
}
|
|
784
1066
|
|
|
@@ -789,7 +1071,7 @@ export class SelectorResolver {
|
|
|
789
1071
|
strategy: 'id',
|
|
790
1072
|
value: idMatch[1],
|
|
791
1073
|
locator: `#${idMatch[1]}`,
|
|
792
|
-
nth
|
|
1074
|
+
nth,
|
|
793
1075
|
};
|
|
794
1076
|
}
|
|
795
1077
|
|
|
@@ -798,7 +1080,7 @@ export class SelectorResolver {
|
|
|
798
1080
|
strategy: 'css',
|
|
799
1081
|
value: element.locator,
|
|
800
1082
|
locator: element.locator,
|
|
801
|
-
nth
|
|
1083
|
+
nth,
|
|
802
1084
|
};
|
|
803
1085
|
}
|
|
804
1086
|
|
|
@@ -848,6 +1130,29 @@ export class SelectorResolver {
|
|
|
848
1130
|
}
|
|
849
1131
|
}
|
|
850
1132
|
|
|
1133
|
+
// SG-W004 — a field nothing reads (#712). Reported HERE, at the one cached load point, so a
|
|
1134
|
+
// table named by twenty row steps warns once rather than twenty times.
|
|
1135
|
+
reportUnknownEntryFields(selectors as Record<string, unknown>, {
|
|
1136
|
+
feature: featureName,
|
|
1137
|
+
diagnostics: this.diagnostics,
|
|
1138
|
+
});
|
|
1139
|
+
|
|
1140
|
+
// SG-E027 — a `type:` nothing dispatches (#711). Same load point and same once-per-file
|
|
1141
|
+
// reason as SG-W004 above: the two checks are the same question one level apart (an unknown
|
|
1142
|
+
// KEY vs an unknown VALUE for a known key), and both want the file in hand to name the entry.
|
|
1143
|
+
reportUnknownSelectorTypes(selectors as Record<string, unknown>, {
|
|
1144
|
+
feature: featureName,
|
|
1145
|
+
diagnostics: this.diagnostics,
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
// SG-W018 — an `nth:` value outside the contract (#720). Third of the same family at the same
|
|
1149
|
+
// load point: SG-W004 asks whether anything READS the key, SG-E027 whether the `type:` VALUE
|
|
1150
|
+
// dispatches, this one whether the `nth:` VALUE is an index the adapters can express.
|
|
1151
|
+
reportInvalidNth(selectors as Record<string, unknown>, {
|
|
1152
|
+
feature: featureName,
|
|
1153
|
+
diagnostics: this.diagnostics,
|
|
1154
|
+
});
|
|
1155
|
+
|
|
851
1156
|
this.selectorCache.set(cacheKey, selectors);
|
|
852
1157
|
return selectors;
|
|
853
1158
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SG-E027 — an entry `type:` the compiler does not know (issue #711).
|
|
3
|
+
*
|
|
4
|
+
* `selectors.yaml` is YAML, so `type:` is an arbitrary string at run time. `VALID_SELECTOR_TYPES`
|
|
5
|
+
* constrained it only through the `SelectorType` TypeScript union, and nothing imported that list
|
|
6
|
+
* for a runtime check — so any value the resolver did not recognise fell to the strategy switch's
|
|
7
|
+
* `default:` branch, which returns `{ strategy: 'placeholder' }`, and the entry's `value:` was
|
|
8
|
+
* emitted as literal placeholder text:
|
|
9
|
+
*
|
|
10
|
+
* save:
|
|
11
|
+
* type: 'testId' # note the capital I
|
|
12
|
+
* value: 'btn-save'
|
|
13
|
+
* → await page.getByPlaceholder('btn-save').click();
|
|
14
|
+
*
|
|
15
|
+
* Same family as #690 (a valid type with no dispatch) and #712 (a documented field with no
|
|
16
|
+
* consumer): the DECLARED surface was wider than the IMPLEMENTED one and nothing compared the two.
|
|
17
|
+
* Here the gap was the widest of the three, because an unknown type is not a fixed list of
|
|
18
|
+
* oversights — it is every possible misspelling.
|
|
19
|
+
*
|
|
20
|
+
* Checked HERE, at the selector-file load, rather than in the strategy switch. The switch CANNOT
|
|
21
|
+
* throw: `type: page` reaches its `default:` on every page assertion (its `value` is a URL, not a
|
|
22
|
+
* locator) and `StepMapper.checkPageStepFallback` resolves inside a try/catch that would read
|
|
23
|
+
* a throw as "not a page" and emit a false SG-W013. Validating at the load point also buys a
|
|
24
|
+
* better message — the file, the entry key and the nearest valid spelling — and reports once per
|
|
25
|
+
* entry rather than once per step that names it.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import type { DiagnosticsCollector } from '../diagnostics';
|
|
29
|
+
import { VALID_SELECTOR_TYPES } from '../../../utils/selector-types';
|
|
30
|
+
import { nearestMatch } from './nearest-match';
|
|
31
|
+
import { walkSelectorEntries } from './selector-entry-walk';
|
|
32
|
+
|
|
33
|
+
export interface UnknownSelectorType {
|
|
34
|
+
/** Dotted path to the offending entry, e.g. `save` or `greeting.ios`. */
|
|
35
|
+
path: string;
|
|
36
|
+
/** The unrecognised `type:` value exactly as written. */
|
|
37
|
+
type: string;
|
|
38
|
+
/** A near-miss valid type, when one is close enough to be worth naming. */
|
|
39
|
+
suggestion?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Every entry in `file` whose `type:` is not a member of `VALID_SELECTOR_TYPES`. */
|
|
43
|
+
export function collectUnknownSelectorTypes(file: Record<string, unknown>): UnknownSelectorType[] {
|
|
44
|
+
const out: UnknownSelectorType[] = [];
|
|
45
|
+
walkSelectorEntries(file, {
|
|
46
|
+
entry(entry, path) {
|
|
47
|
+
const type = entry.type;
|
|
48
|
+
// Only a STRING type is judged here. A non-string `type:` (a YAML nesting slip) is a
|
|
49
|
+
// different defect with a different fix, and SG-W004's field walk is not the owner of it
|
|
50
|
+
// either — naming it as an unknown type would print `[object Object]` at the author.
|
|
51
|
+
if (typeof type !== 'string') return;
|
|
52
|
+
if ((VALID_SELECTOR_TYPES as readonly string[]).includes(type)) return;
|
|
53
|
+
out.push({ path, type, suggestion: nearestMatch(type, VALID_SELECTOR_TYPES) });
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** True when this entry kind is one the compiler knows how to resolve. */
|
|
60
|
+
export function isKnownSelectorType(type: unknown): boolean {
|
|
61
|
+
return typeof type === 'string' && (VALID_SELECTOR_TYPES as readonly string[]).includes(type);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* SG-E027 — report every entry whose `type:` nothing dispatches.
|
|
66
|
+
*
|
|
67
|
+
* Runs once per selector FILE (the resolver's load is cached), so an entry named by twenty steps
|
|
68
|
+
* is reported once. Silent when there is no diagnostics channel attached — `generate` must not
|
|
69
|
+
* depend on diagnostics being wired.
|
|
70
|
+
*/
|
|
71
|
+
export function reportUnknownSelectorTypes(
|
|
72
|
+
file: Record<string, unknown>,
|
|
73
|
+
opts: { feature?: string; diagnostics?: DiagnosticsCollector },
|
|
74
|
+
): void {
|
|
75
|
+
const sink = opts.diagnostics;
|
|
76
|
+
if (!sink) return;
|
|
77
|
+
for (const { path, type, suggestion } of collectUnknownSelectorTypes(file)) {
|
|
78
|
+
const hint = suggestion
|
|
79
|
+
? ` Did you mean "${suggestion}"?`
|
|
80
|
+
: ` Valid types: ${VALID_SELECTOR_TYPES.join(', ')}.`;
|
|
81
|
+
sink.add('SG-E027',
|
|
82
|
+
`selectors.yaml entry "${path}" declares type: "${type}", which is not a selector type — `
|
|
83
|
+
+ `the entry resolves to getByPlaceholder("<its value>"), a locator that cannot match, so `
|
|
84
|
+
+ `every step naming it now throws instead of failing later as a mystery timeout.${hint}`,
|
|
85
|
+
{ feature: opts.feature });
|
|
86
|
+
}
|
|
87
|
+
}
|