@sun-asterisk/sungen 3.2.29 → 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/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/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/matrix/build.d.ts.map +1 -1
- package/dist/exporters/matrix/build.js +4 -2
- package/dist/exporters/matrix/build.js.map +1 -1
- package/dist/exporters/matrix/render-coverage-sheet.d.ts.map +1 -1
- package/dist/exporters/matrix/render-coverage-sheet.js +3 -1
- package/dist/exporters/matrix/render-coverage-sheet.js.map +1 -1
- 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.map +1 -1
- package/dist/exporters/matrix/render-overview-sheet.js +5 -9
- package/dist/exporters/matrix/render-overview-sheet.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.map +1 -1
- package/dist/exporters/matrix/testcase-summary-band.js +2 -6
- package/dist/exporters/matrix/testcase-summary-band.js.map +1 -1
- 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/generators/gherkin-parser/index.d.ts +3 -0
- package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
- package/dist/generators/gherkin-parser/index.js +17 -9
- 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/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/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/table-paginate-action.hbs +3 -0
- 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/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/partials/locator.hbs +9 -1
- package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +54 -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 +3 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +3 -1
- 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 +14 -3
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +13 -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-nth.hbs +1 -1
- 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 +160 -39
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/diagnostics.d.ts +79 -5
- package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
- package/dist/generators/test-generator/diagnostics.js +78 -4
- package/dist/generators/test-generator/diagnostics.js.map +1 -1
- package/dist/generators/test-generator/patterns/index.d.ts +24 -5
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +92 -14
- package/dist/generators/test-generator/patterns/index.js.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 +63 -9
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +166 -28
- 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 +30 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +195 -15
- 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/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/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/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/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-error-mapping/SKILL.md +14 -2
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +108 -8
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +1 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +9 -2
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +4 -4
- package/dist/orchestrator/templates/playwright.config.ts +7 -0
- 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-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/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/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/exporters/api-testcase-formatter.ts +5 -7
- package/src/exporters/matrix/build.ts +4 -2
- package/src/exporters/matrix/render-coverage-sheet.ts +3 -1
- package/src/exporters/matrix/render-csv.ts +3 -1
- package/src/exporters/matrix/render-overview-sheet.ts +5 -10
- package/src/exporters/matrix/result-totals.ts +37 -0
- package/src/exporters/matrix/testcase-summary-band.ts +2 -7
- 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/generators/gherkin-parser/index.ts +21 -10
- 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/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/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/table-paginate-action.hbs +3 -0
- 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/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/partials/locator.hbs +9 -1
- package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +54 -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 +3 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +3 -1
- 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 +14 -3
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +13 -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-nth.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +10 -8
- package/src/generators/test-generator/code-generator.ts +176 -45
- package/src/generators/test-generator/diagnostics.ts +79 -5
- package/src/generators/test-generator/patterns/index.ts +97 -16
- package/src/generators/test-generator/patterns/with-parent-scope.ts +41 -0
- package/src/generators/test-generator/step-mapper.ts +170 -26
- 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 +245 -16
- package/src/generators/test-generator/utils/selector-type-validation.ts +87 -0
- package/src/harness/data-driven-lint.ts +192 -32
- package/src/harness/script-check.ts +186 -21
- 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/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-error-mapping/SKILL.md +14 -2
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +108 -8
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +1 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +9 -2
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +4 -4
- package/src/orchestrator/templates/playwright.config.ts +7 -0
- package/src/orchestrator/templates/specs-checked-state.ts +324 -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/utils/selector-types.ts +54 -5
|
@@ -1,3 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
{{!-- Rooted at `page`, like the two lines below it. This line used to render `{{> locator}}` from
|
|
2
|
+
step data that carries no strategy/value at all, so it emitted `page.locator('')` — an empty
|
|
3
|
+
CSS selector, which Playwright refuses to parse. The step could not run.
|
|
4
|
+
|
|
5
|
+
Block-scoped: the column-index const is named after the [Reference], so two steps naming the
|
|
6
|
+
same column — or two non-ASCII references, which sanitize down to the same identifier — would
|
|
7
|
+
redeclare it in one scenario and the spec would not compile. The braces make each step's
|
|
8
|
+
binding its own.
|
|
9
|
+
|
|
10
|
+
Not dialog/frame scoped: this step form carries no selector entry, so there is nothing for
|
|
11
|
+
`pageRoot` to read. A column assertion inside a modal needs the row-scoped form. --~}}{ await expect(page.getByRole('columnheader', { name: '{{columnName}}', includeHidden: true })).toBeVisible();
|
|
2
12
|
const {{columnIndexVar}} = (await page.getByRole('columnheader', { includeHidden: true }).filter({ visible: true }).allTextContents()).findIndex(h => h.includes('{{columnName}}'));
|
|
3
|
-
|
|
13
|
+
expect({{columnIndexVar}}, 'Column "{{escapeQuotes columnName}}" is not among the visible headers').toBeGreaterThanOrEqual(0);
|
|
14
|
+
await expect(page.getByRole('row', { includeHidden: true }).filter({ visible: true }).nth({{rowNth}}).getByRole('cell', { includeHidden: true }).filter({ visible: true }).nth({{columnIndexVar}})).toHaveText('{{dataValue}}'); }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
await expect({{> locator}}).toContainText('{{expectedText}}');
|
|
1
|
+
await expect({{> locator}}).toContainText('{{escapeQuotes expectedText}}');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{{#if name}}
|
|
2
|
-
await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{
|
|
2
|
+
await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeDisabled();
|
|
3
3
|
{{else}}
|
|
4
|
-
await expect(page.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{
|
|
4
|
+
await expect(page.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeDisabled();
|
|
5
5
|
{{/if}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
await expect(page.getByText('{{escapeQuotes selectorRef}}').filter({ hasText: {{#if (eq selectorValue "")}}/.*{{escapeRegex dataValue}}$/{{else}}'{{escapeQuotes dataValue}}'{{/if}} }){{
|
|
1
|
+
await expect(page.getByText('{{escapeQuotes selectorRef}}').filter({ hasText: {{#if (eq selectorValue "")}}/.*{{escapeRegex dataValue}}$/{{else}}'{{escapeQuotes dataValue}}'{{/if}} }){{> locator-nth}}).toBeDisabled();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
await expect({{> locator}}).toHaveText('{{expectedText}}');
|
|
1
|
+
await expect({{> locator}}).toHaveText('{{escapeQuotes expectedText}}');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{{#if name}}
|
|
2
|
-
await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{
|
|
2
|
+
await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeHidden();
|
|
3
3
|
{{else}}
|
|
4
|
-
await expect(page.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{
|
|
4
|
+
await expect(page.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeHidden();
|
|
5
5
|
{{/if}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
await expect(page.getByText('{{escapeQuotes selectorRef}}').filter({ hasText: {{#if (eq selectorValue "")}}/.*{{escapeRegex dataValue}}$/{{else}}'{{escapeQuotes dataValue}}'{{/if}} }){{
|
|
1
|
+
await expect(page.getByText('{{escapeQuotes selectorRef}}').filter({ hasText: {{#if (eq selectorValue "")}}/.*{{escapeRegex dataValue}}$/{{else}}'{{escapeQuotes dataValue}}'{{/if}} }){{> locator-nth}}).toBeHidden();
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
{{!-- Inverse of checked-assertion.hbs — see there (#675). --}}
|
|
2
|
+
await expectCheckedState({{> locator}}, false, '{{escapeQuotes selectorRef}}'{{> checked-opts cls=checkedClass attr=checkedAttribute}});
|
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
{{#if (or
|
|
1
|
+
{{#if (or checkedClass checkedAttribute)}}
|
|
2
|
+
{{!-- A DECLARED signal (issue #730) short-circuits the whole ARIA probe below: the author
|
|
3
|
+
reached for `selected:`/`checked:` precisely because this component publishes no aria-selected,
|
|
4
|
+
no aria-current and no checked state — a Tailwind card/tab picker. Probing for them first would
|
|
5
|
+
just spend the budget confirming they are absent. --}}
|
|
6
|
+
await expectCheckedState({{> locator}}, true, '{{escapeQuotes selectorRef}}'{{> checked-opts cls=checkedClass attr=checkedAttribute}});
|
|
7
|
+
{{else if (or (eq role 'checkbox') (eq role 'radio') (eq role 'switch'))}}
|
|
2
8
|
{{!-- Form control: "selected" is the checked state, never aria-selected. A native checkbox/radio
|
|
3
9
|
exposes no aria-selected attribute — its state lives in the `checked` property. --}}
|
|
4
|
-
|
|
10
|
+
{{!-- "selected" on a form control IS the checked state — routed through expectCheckedState so an
|
|
11
|
+
aria-pressed toggle does not throw "Not a checkbox or radio button" (#675), and so the selector
|
|
12
|
+
that declares role: switch while the element publishes aria-pressed still reads correctly. --}}
|
|
13
|
+
await expectCheckedState({{> locator}}, true, '{{escapeQuotes selectorRef}}');
|
|
5
14
|
{{else}}
|
|
6
15
|
{{!-- Other/unknown role (tab, option, or a text/testid/css selector): probe at runtime. An ARIA
|
|
7
16
|
single-select widget exposes aria-selected → assert it; anything without it falls back to the
|
|
@@ -17,7 +26,9 @@ await expect(el).toHaveAttribute('aria-selected', 'true');
|
|
|
17
26
|
role=tab owns the stronger signal. --}}
|
|
18
27
|
await expect(el).toHaveAttribute('aria-current', /^(?!false$)./);
|
|
19
28
|
} else {
|
|
20
|
-
|
|
29
|
+
{{!-- Last resort, same as the -with-filter twin: neither aria-selected nor aria-current, so read the
|
|
30
|
+
checked/pressed state through the #675-safe path rather than toBeChecked(). --}}
|
|
31
|
+
await expectCheckedState(el, true, '{{escapeQuotes selectorRef}}');
|
|
21
32
|
}
|
|
22
33
|
}
|
|
23
34
|
{{/if}}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
{{!-- Filter contract shared with the visible/hidden/contains siblings (has-text-value):
|
|
2
2
|
substring by default, `exact: true` in the selector YAML anchors. --}}
|
|
3
3
|
{{#if selectedProbe}}
|
|
4
|
+
{{#if (or checkedClass checkedAttribute)}}
|
|
5
|
+
{{!-- Declared class signal (issue #730) — short-circuits the ARIA probe, exactly as the unfiltered
|
|
6
|
+
twin selected-assertion.hbs does. Without this branch the filtered form stayed broken while the
|
|
7
|
+
unfiltered one worked, which is the inconsistency #730's second field report ran into. --}}
|
|
8
|
+
await expectCheckedState({{> locator-base}}.filter({ hasText: {{> has-text-value dataValue=dataValue exact=exact}} }){{> locator-nth}}, true, '{{escapeQuotes selectorRef}}'{{> checked-opts cls=checkedClass attr=checkedAttribute}});
|
|
9
|
+
{{else}}
|
|
4
10
|
{ const el = {{> locator-base}}.filter({ hasText: {{> has-text-value dataValue=dataValue exact=exact}} }){{> locator-nth}};
|
|
5
11
|
if ((await el.getAttribute('aria-selected')) !== null) {
|
|
6
12
|
await expect(el).toHaveAttribute('aria-selected', 'true');
|
|
@@ -12,9 +18,14 @@ await expect(el).toHaveAttribute('aria-selected', 'true');
|
|
|
12
18
|
role=tab owns the stronger signal. --}}
|
|
13
19
|
await expect(el).toHaveAttribute('aria-current', /^(?!false$)./);
|
|
14
20
|
} else {
|
|
15
|
-
|
|
21
|
+
{{!-- Last resort: the element publishes neither aria-selected nor aria-current. toBeChecked() used
|
|
22
|
+
to sit here and threw "Not a checkbox or radio button" on an aria-pressed toggle (#675). --}}
|
|
23
|
+
await expectCheckedState(el, true, '{{escapeQuotes selectorRef}}');
|
|
16
24
|
}
|
|
17
25
|
}
|
|
26
|
+
{{/if}}
|
|
27
|
+
{{else}}{{#if isCheckedAssertion}}
|
|
28
|
+
await expectCheckedState({{> locator-base}}.filter({ hasText: {{> has-text-value dataValue=dataValue exact=exact}} }){{> locator-nth}}, {{checkedWant}}, '{{escapeQuotes selectorRef}}'{{> checked-opts cls=checkedClass attr=checkedAttribute}});
|
|
18
29
|
{{else}}
|
|
19
30
|
await expect({{> locator-base}}.filter({ hasText: {{> has-text-value dataValue=dataValue exact=exact}} }){{> locator-nth}}).{{{stateAssertion}}};
|
|
20
|
-
{{/if}}
|
|
31
|
+
{{/if}}{{/if}}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{{#if name}}
|
|
2
2
|
{{#if dataValue}}
|
|
3
|
-
await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{
|
|
3
|
+
await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeVisible();
|
|
4
4
|
{{else}}
|
|
5
|
-
await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }){{
|
|
5
|
+
await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }){{> locator-nth}}).toBeVisible();
|
|
6
6
|
{{/if}}
|
|
7
7
|
{{else}}
|
|
8
|
-
await expect(page.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{
|
|
8
|
+
await expect(page.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeVisible();
|
|
9
9
|
{{/if}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
await expect(page.getByText('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{
|
|
1
|
+
await expect(page.getByText('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{> locator-nth}}).toBeVisible();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
await expect(page.getByText('{{escapeQuotes selectorValue}}').filter({ hasText: /.*{{escapeRegex dataValue}}$/ }){{
|
|
1
|
+
await expect(page.getByText('{{escapeQuotes selectorValue}}').filter({ hasText: /.*{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeVisible();
|
package/src/generators/test-generator/adapters/playwright/templates/steps/partials/checked-opts.hbs
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{{!-- The `checked:`/`selected:` signal from selectors.yaml, appended as the options argument of
|
|
2
|
+
setCheckedState/expectCheckedState (issue #730). One partial so all nine call sites carry it
|
|
3
|
+
identically, and so a site that declares nothing stays BYTE-IDENTICAL to before — the whole
|
|
4
|
+
argument disappears rather than becoming an empty object. Takes `cls`/`attr` so a caller can
|
|
5
|
+
pass a signal resolved from a DIFFERENT entry than the one it spread (table-action-in-row
|
|
6
|
+
spreads the TABLE but acts on the row's own control). A selector declares one or the other;
|
|
7
|
+
the resolver refuses both, so the two branches never collide here. --~}}
|
|
8
|
+
{{~#if cls}}, { checkedClass: '{{escapeQuotes cls}}' }{{else if attr}}, { checkedAttribute: '{{escapeQuotes attr}}' }{{/if~}}
|
package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-nth.hbs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{{#if (gte nth 0)}}.nth({{nth}}){{/if}}
|
|
1
|
+
{{#if (gte nth 0)}}.nth({{nth}}){{else if (eq nth -1)}}.last(){{/if}}
|
|
@@ -98,6 +98,16 @@ test.describe.serial('{{featureName}}', () => {
|
|
|
98
98
|
});
|
|
99
99
|
{{/if}}
|
|
100
100
|
|
|
101
|
+
{{#if beforeAll}}
|
|
102
|
+
{{beforeAll}}
|
|
103
|
+
|
|
104
|
+
{{/if}}
|
|
105
|
+
{{#if afterAll}}
|
|
106
|
+
// User @afterAll runs BEFORE the built-in close below (Playwright runs afterAll hooks in registration
|
|
107
|
+
// order), so the shared page is still open for its cleanup steps.
|
|
108
|
+
{{afterAll}}
|
|
109
|
+
|
|
110
|
+
{{/if}}
|
|
101
111
|
test.afterAll(async () => {
|
|
102
112
|
await page.close();
|
|
103
113
|
await context.close();
|
|
@@ -112,18 +122,10 @@ test.describe.serial('{{featureName}}', () => {
|
|
|
112
122
|
{{#if screenshotOnFailure}}
|
|
113
123
|
test.use({ screenshotOnFailure: true });
|
|
114
124
|
|
|
115
|
-
{{/if}}
|
|
116
|
-
{{#if beforeAll}}
|
|
117
|
-
{{beforeAll}}
|
|
118
|
-
|
|
119
125
|
{{/if}}
|
|
120
126
|
{{#if afterEach}}
|
|
121
127
|
{{afterEach}}
|
|
122
128
|
|
|
123
|
-
{{/if}}
|
|
124
|
-
{{#if afterAll}}
|
|
125
|
-
{{afterAll}}
|
|
126
|
-
|
|
127
129
|
{{/if}}
|
|
128
130
|
{{#if authGroups}}
|
|
129
131
|
{{#each authGroups}}
|
|
@@ -2,7 +2,7 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { ParsedFeature, ParsedScenario, ParsedStep } from '../gherkin-parser';
|
|
4
4
|
import { StepMapper } from './step-mapper';
|
|
5
|
-
import { TestGeneratorAdapter, adapterRegistry } from './adapters';
|
|
5
|
+
import { TestGeneratorAdapter, adapterRegistry, HookStep } from './adapters';
|
|
6
6
|
import { transformToRuntimeData } from './utils/runtime-data-transformer';
|
|
7
7
|
import { DiagnosticsCollector } from './diagnostics';
|
|
8
8
|
import { parseResetTier, DEFAULT_RESET_TIER } from './reset-tier';
|
|
@@ -12,6 +12,9 @@ import { readCapabilities } from '../../harness/capability';
|
|
|
12
12
|
import { parseManualComments } from '../../exporters/scenario-merger';
|
|
13
13
|
import { findSyntaxError } from './utils/spec-syntax-check';
|
|
14
14
|
|
|
15
|
+
/** One capability-injected precondition statement (`@api:`/`@query:` → `testData.bind(name, …)`). */
|
|
16
|
+
type CapabilityPrecondition = { comment?: string; code: string; boundVars?: string[] };
|
|
17
|
+
|
|
15
18
|
/**
|
|
16
19
|
* Filter base scenario steps for @extend: only keep Given→When steps.
|
|
17
20
|
* Skips Then and any And/But that follow Then (since And/But inherit
|
|
@@ -346,7 +349,7 @@ export class CodeGenerator {
|
|
|
346
349
|
// helper into every UI spec — including the many that touch no URL at all. Hence the body is
|
|
347
350
|
// rendered BEFORE the imports. Importing exactly the names used keeps the import honest; the
|
|
348
351
|
// helper file itself is synced only when something needs it (an api-only unit carries none).
|
|
349
|
-
const urlAssertImports = ['urlMatches', 'joinPath', 'expectPage'].filter((n) => testCode.includes(`${n}(`)).join(', ');
|
|
352
|
+
const urlAssertImports = ['urlMatches', 'joinPath', 'expectPage', 'waitForPage'].filter((n) => testCode.includes(`${n}(`)).join(', ');
|
|
350
353
|
if (urlAssertImports) this.syncGeneratedHelper(outputDir, 'url-assert.ts', 'specs-url-assert.ts');
|
|
351
354
|
|
|
352
355
|
// Same body-first rule for the runtime regex-escape helper: only a spec whose
|
|
@@ -354,7 +357,16 @@ export class CodeGenerator {
|
|
|
354
357
|
const regexEscapeImport = /\bescapeRegExp\(/.test(testCode);
|
|
355
358
|
if (regexEscapeImport) this.syncGeneratedHelper(outputDir, 'regex-escape.ts', 'specs-regex-escape.ts');
|
|
356
359
|
|
|
357
|
-
|
|
360
|
+
// Same body-first rule for the checked-state helper (#675): only a spec that actually compiled a
|
|
361
|
+
// `check`/`uncheck` step imports (and syncs) it, so the many specs that touch no toggle carry
|
|
362
|
+
// neither the import nor the file.
|
|
363
|
+
// Import exactly the names the body used, like urlAssertImports above — the action half and the
|
|
364
|
+
// assertion half of #675 are independent, and most specs need only one of them.
|
|
365
|
+
const checkedStateImports = ['setCheckedState', 'expectCheckedState']
|
|
366
|
+
.filter((n) => testCode.includes(`${n}(`)).join(', ');
|
|
367
|
+
if (checkedStateImports) this.syncGeneratedHelper(outputDir, 'checked-state.ts', 'specs-checked-state.ts');
|
|
368
|
+
|
|
369
|
+
const imports = this.adapter.renderImports({ runtimeData: this.options.runtimeData, basePath, needsCleanupImport, needsDb, needsApi, needsMock, urlAssertImports, regexEscapeImport, checkedStateImports });
|
|
358
370
|
|
|
359
371
|
// Combine and collapse any runs of 3+ newlines down to 2 (one blank line max)
|
|
360
372
|
const raw = `${imports}\n\n${testCode}`;
|
|
@@ -482,10 +494,10 @@ export class CodeGenerator {
|
|
|
482
494
|
* Each capability owns its annotation codegen via the SPI (`preconditionCodegen`); the compiler
|
|
483
495
|
* just composes + indents the returned statements (R4).
|
|
484
496
|
*/
|
|
485
|
-
private capabilityPreconditions(scenario: ParsedScenario):
|
|
497
|
+
private capabilityPreconditions(scenario: ParsedScenario): CapabilityPrecondition[] {
|
|
486
498
|
discoverAndRegisterCapabilities();
|
|
487
499
|
const tags = scenario.tags || [];
|
|
488
|
-
const out:
|
|
500
|
+
const out: CapabilityPrecondition[] = [];
|
|
489
501
|
for (const cap of capabilityRegistry.all()) {
|
|
490
502
|
if (!cap.preconditionCodegen) continue;
|
|
491
503
|
out.push(...cap.preconditionCodegen({ tags, screenName: this.queryScreenName, cwd: process.cwd() }));
|
|
@@ -527,6 +539,11 @@ export class CodeGenerator {
|
|
|
527
539
|
if (this.options.runtimeData) {
|
|
528
540
|
this.syncGeneratedHelper(outputDir, 'test-data.ts', 'specs-test-data.ts');
|
|
529
541
|
}
|
|
542
|
+
// The Playwright reporter that keeps machine paths out of the committed JSON result — refreshed
|
|
543
|
+
// like base.ts so an existing project picks it up on its next generate (mobile has no Playwright).
|
|
544
|
+
if (this.adapter.frameworkName !== 'appium') {
|
|
545
|
+
this.syncGeneratedHelper(outputDir, 'report-path-sanitize-reporter.ts', 'specs-report-path-sanitize-reporter.ts');
|
|
546
|
+
}
|
|
530
547
|
}
|
|
531
548
|
|
|
532
549
|
private async generateTestCode(feature: ParsedFeature): Promise<string> {
|
|
@@ -600,6 +617,26 @@ export class CodeGenerator {
|
|
|
600
617
|
// never skip-cascades). `@serial` opts into the shared-page serial suite for a stateful sequence.
|
|
601
618
|
const isParallel = !(feature.tags || []).includes('@serial');
|
|
602
619
|
|
|
620
|
+
// A `@beforeAll` hook runs before every test of the describe, so the `{{name}}` its `@api:`/`@query:`
|
|
621
|
+
// preconditions bind are readable from EVERY scenario of the feature. Register the bound heads
|
|
622
|
+
// before the scenarios map: the per-scenario captured set is cleared at each scenario, and without
|
|
623
|
+
// this a scenario's `{{login.body.token}}` compiles to a dead literal. `@afterEach`/`@afterAll` run
|
|
624
|
+
// after the tests — their bindings stay hook-local. The set is per feature: clear it FIRST, before
|
|
625
|
+
// the Background maps, or the previous feature's bindings would leak into this Background.
|
|
626
|
+
// Whether the Background itself may read them depends on where it runs: per-scenario (parallel)
|
|
627
|
+
// Background is a beforeEach and runs after the user beforeAll → register before it; @serial
|
|
628
|
+
// Background is inlined into the built-in beforeAll that runs BEFORE the user hook → register
|
|
629
|
+
// only after it has mapped, so a serial Background ref stays an unresolved literal, as it should.
|
|
630
|
+
this.stepMapper.clearFeatureCaptured();
|
|
631
|
+
const registerBeforeAllBindings = (): void => {
|
|
632
|
+
const beforeAllHook = hookScenarios.get('beforeAll');
|
|
633
|
+
if (!beforeAllHook) return;
|
|
634
|
+
for (const p of this.capabilityPreconditions(beforeAllHook)) {
|
|
635
|
+
for (const head of p.boundVars || []) this.stepMapper.registerFeatureCaptured(head);
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
if (isParallel) registerBeforeAllBindings();
|
|
639
|
+
|
|
603
640
|
// Generate background if exists
|
|
604
641
|
let background: string | undefined;
|
|
605
642
|
let backgroundSteps: Array<{ comment?: string; code: string }> | undefined;
|
|
@@ -642,6 +679,8 @@ export class CodeGenerator {
|
|
|
642
679
|
}
|
|
643
680
|
}
|
|
644
681
|
|
|
682
|
+
if (!isParallel) registerBeforeAllBindings();
|
|
683
|
+
|
|
645
684
|
// Generate hook blocks
|
|
646
685
|
const beforeAll = hookScenarios.has('beforeAll')
|
|
647
686
|
? await this.generateHook(hookScenarios.get('beforeAll')!, 'beforeAll')
|
|
@@ -858,6 +897,21 @@ export class CodeGenerator {
|
|
|
858
897
|
}
|
|
859
898
|
if (iosPart) iosBundleId = iosPart;
|
|
860
899
|
}
|
|
900
|
+
// SG-W029 — the appium adapter reached codegen with NO usable app package. Both ways in are
|
|
901
|
+
// silent: a feature with no `Path:` line at all, and a web-style route `Path: /applied-jobs`
|
|
902
|
+
// that the guard above deliberately rejects. Before this code that combination quietly emitted
|
|
903
|
+
// Mocha's `before()` — once per FILE — so Background ran once and every scenario shared one
|
|
904
|
+
// continuous app session, leaking tab/scroll/login state forward; the `@reset:` tier ladder was
|
|
905
|
+
// dropped along with it, making the tag inert. The generated spec now falls back to the session
|
|
906
|
+
// capability instead, but the feature file is still missing something it should declare, and
|
|
907
|
+
// the run throws if wdio.conf cannot supply it either — so it is reported, not assumed.
|
|
908
|
+
// A warning rather than an error on SG-W004's reasoning: the run usually still works (wdio.conf
|
|
909
|
+
// carries the id), and `--strict` already turns the channel non-zero for anyone who wants a gate.
|
|
910
|
+
if (this.adapter.frameworkName === 'appium' && !appPackage && !iosBundleId) {
|
|
911
|
+
this.diagnostics.add('SG-W029',
|
|
912
|
+
`this mobile feature declares no app-package \`Path:\`${feature.path ? ` (\`${feature.path}\` is a web route, not a package)` : ''}, so the compiler cannot name the app to reset between scenarios. The spec now resolves the app id from the session capability (APP_PACKAGE / IOS_BUNDLE_ID in wdio.conf) and THROWS at run time if that is absent too. Declare it on the feature instead: \`Path: com.example.app/.MainActivity\` (or \`<androidPkg>/<activity> | <iosBundleId>\` for a dual-id feature).`,
|
|
913
|
+
{ feature: feature.name });
|
|
914
|
+
}
|
|
861
915
|
|
|
862
916
|
// Use adapter to render the complete test file structure
|
|
863
917
|
return this.adapter.renderTestFile({
|
|
@@ -920,23 +974,66 @@ export class CodeGenerator {
|
|
|
920
974
|
});
|
|
921
975
|
}
|
|
922
976
|
|
|
977
|
+
/**
|
|
978
|
+
* Hook block (`@beforeAll` / `@afterEach` / `@afterAll`). Shares prepare/finalize with ordinary
|
|
979
|
+
* scenarios, so an `@api:` / `@query:` precondition on the hook runs INSIDE it (bind {{name}}) and
|
|
980
|
+
* the hook's own `{{name.*}}` refs resolve at runtime — a hook used to map only its steps, so
|
|
981
|
+
* `@beforeAll @api:login` compiled to a beforeAll with no api.call and a testData.get() of nothing.
|
|
982
|
+
* Not applied to hooks: `@cases` (a hook has no dataset rows) and `@extend` (no base scenario) —
|
|
983
|
+
* warned and ignored; `@auth:` grouping and the mock epilogue are scenario-only. `@concurrent:N`
|
|
984
|
+
* IS valid: the api capability reads it off the same tags.
|
|
985
|
+
*
|
|
986
|
+
* Failure collection: a failing hook skips every test in its describe, so one run must report
|
|
987
|
+
* EVERY failed check, not just the first. The hook template wraps each mapped step in try/catch:
|
|
988
|
+
* an assertion step (effective keyword Then) records its failure and lets the next step run; an
|
|
989
|
+
* action step (Given/When) records it and fails the hook right there, since the steps after it
|
|
990
|
+
* would run on state the failed action never produced. The block ends with a guard that fails once
|
|
991
|
+
* with the full list. `expect.soft` is not an option here — a soft failure marks the owning test,
|
|
992
|
+
* and a hook has none.
|
|
993
|
+
*/
|
|
923
994
|
private async generateHook(
|
|
924
995
|
scenario: ParsedScenario,
|
|
925
996
|
hookType: 'beforeAll' | 'afterEach' | 'afterAll'
|
|
926
997
|
): Promise<string> {
|
|
927
|
-
const
|
|
928
|
-
for (const
|
|
998
|
+
const unsupported = scenario.tags.filter((t) => t.startsWith('@cases:') || t.startsWith('@extend:'));
|
|
999
|
+
for (const tag of unsupported) {
|
|
1000
|
+
console.warn(`⚠ @${hookType} hook "${scenario.name}": ${tag} is not supported on hooks — ignored`);
|
|
1001
|
+
}
|
|
1002
|
+
const hookScenario: ParsedScenario = {
|
|
1003
|
+
...scenario,
|
|
1004
|
+
tags: scenario.tags.filter((t) => !unsupported.includes(t)),
|
|
1005
|
+
extendsName: undefined,
|
|
1006
|
+
};
|
|
1007
|
+
const { preconditions } = this.prepareScenarioBody(hookScenario, hookScenario.steps);
|
|
1008
|
+
|
|
1009
|
+
const steps: HookStep[] = [];
|
|
1010
|
+
let effectiveKeyword = 'Given'; // And/But inherit the last Given/When/Then
|
|
1011
|
+
for (const step of hookScenario.steps) {
|
|
1012
|
+
const keyword = step.keyword.trim();
|
|
1013
|
+
if (keyword === 'Given' || keyword === 'When' || keyword === 'Then') effectiveKeyword = keyword;
|
|
929
1014
|
const mapped = await Promise.resolve(this.stepMapper.mapStep(step));
|
|
1015
|
+
// A step whose code declares a top-level binding (`const frame = …`, `let tableRow = …`) is read
|
|
1016
|
+
// by the steps AFTER it — a try/catch would scope the binding away from them. Such a step runs
|
|
1017
|
+
// unwrapped and throws on failure exactly as before.
|
|
1018
|
+
if (/^(const|let|var)\s/m.test(mapped.code)) {
|
|
1019
|
+
steps.push({ comment: mapped.comment, code: this.indentCode(mapped.code, 4) });
|
|
1020
|
+
continue;
|
|
1021
|
+
}
|
|
930
1022
|
steps.push({
|
|
931
1023
|
comment: mapped.comment,
|
|
932
|
-
code: this.indentCode(mapped.code,
|
|
1024
|
+
code: this.indentCode(mapped.code, 6),
|
|
1025
|
+
wrap: true,
|
|
1026
|
+
stopOnFailure: effectiveKeyword !== 'Then',
|
|
1027
|
+
label: JSON.stringify(mapped.comment || step.text || 'hook step'),
|
|
933
1028
|
});
|
|
934
1029
|
}
|
|
1030
|
+
this.finalizeScenarioBody(steps, preconditions);
|
|
1031
|
+
const collect = steps.some((s) => s.wrap);
|
|
935
1032
|
|
|
936
1033
|
const renderMap = {
|
|
937
|
-
beforeAll: () => this.adapter.renderBeforeAll({ steps }),
|
|
938
|
-
afterEach: () => this.adapter.renderAfterEach({ steps }),
|
|
939
|
-
afterAll: () => this.adapter.renderAfterAll({ steps }),
|
|
1034
|
+
beforeAll: () => this.adapter.renderBeforeAll({ steps, collect }),
|
|
1035
|
+
afterEach: () => this.adapter.renderAfterEach({ steps, collect }),
|
|
1036
|
+
afterAll: () => this.adapter.renderAfterAll({ steps, collect }),
|
|
940
1037
|
};
|
|
941
1038
|
return renderMap[hookType]();
|
|
942
1039
|
}
|
|
@@ -956,6 +1053,63 @@ export class CodeGenerator {
|
|
|
956
1053
|
].join('\n');
|
|
957
1054
|
}
|
|
958
1055
|
|
|
1056
|
+
/**
|
|
1057
|
+
* Scenario-body preparation shared by ordinary scenarios and hook blocks — runs BEFORE the steps
|
|
1058
|
+
* are mapped (the step mapper reads what it sets): scenario context, `@cases` row-column capture,
|
|
1059
|
+
* and the capability preconditions (`@api:<name>` / `@query:<name>` → bind {{name}}) whose bound
|
|
1060
|
+
* `{{name.*}}` refs exist only at runtime, so they are registered as captured and compile to a
|
|
1061
|
+
* runtime get() instead of a compile-time YAML lookup that would fail. Pair with
|
|
1062
|
+
* finalizeScenarioBody(), which prepends the preconditions once the steps are mapped.
|
|
1063
|
+
*/
|
|
1064
|
+
private prepareScenarioBody(
|
|
1065
|
+
scenario: ParsedScenario,
|
|
1066
|
+
stepsToMap: ParsedStep[],
|
|
1067
|
+
): { preconditions: CapabilityPrecondition[]; casesDataset?: string } {
|
|
1068
|
+
this.stepMapper.setScenarioContext(stepsToMap);
|
|
1069
|
+
|
|
1070
|
+
// Data-driven (@cases): the scenario's {{col}} refs are dataset *row columns* that
|
|
1071
|
+
// exist only at runtime — register them as captured so they resolve to a runtime
|
|
1072
|
+
// get() (→ rowData.get) instead of compile-time YAML lookup.
|
|
1073
|
+
const casesTag = scenario.tags.find((t) => t.startsWith('@cases:'));
|
|
1074
|
+
const casesDataset = casesTag ? casesTag.slice('@cases:'.length).trim() : undefined;
|
|
1075
|
+
if (casesDataset) {
|
|
1076
|
+
const refs = new Set<string>();
|
|
1077
|
+
for (const st of stepsToMap) {
|
|
1078
|
+
for (const m of (st.text || '').matchAll(/\{\{\s*([\w.]+)\s*\}\}/g)) refs.add(m[1]);
|
|
1079
|
+
}
|
|
1080
|
+
for (const r of refs) this.stepMapper.registerCaptured(r);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
const preconditions = this.capabilityPreconditions(scenario);
|
|
1084
|
+
const boundVars = preconditions.flatMap((p) => p.boundVars || []);
|
|
1085
|
+
if (boundVars.length) {
|
|
1086
|
+
for (const st of stepsToMap) {
|
|
1087
|
+
for (const mt of (st.text || '').matchAll(/\{\{\s*([^}]+?)\s*\}\}/g)) {
|
|
1088
|
+
const head = mt[1].split(/[.[]/)[0];
|
|
1089
|
+
if (boundVars.includes(head)) this.stepMapper.registerCaptured(mt[1]);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
return { preconditions, casesDataset };
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/**
|
|
1097
|
+
* Capability preconditions run BEFORE the body's own steps — prepend them (indented) once the
|
|
1098
|
+
* steps are mapped. Second half of the prepareScenarioBody() pair.
|
|
1099
|
+
*/
|
|
1100
|
+
private finalizeScenarioBody(steps: Array<{ comment?: string; code: string }>, preconditions: CapabilityPrecondition[]): void {
|
|
1101
|
+
if (preconditions.length) {
|
|
1102
|
+
steps.unshift(...preconditions.map((p) => ({ comment: p.comment, code: this.indentCode(p.code, 4) })));
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
/**
|
|
1107
|
+
* Step-boundary trace (#673): scenario name → the ordered step-comment texts emitted for it.
|
|
1108
|
+
* Read by script-check to attribute emitted code to steps structurally; see the populating
|
|
1109
|
+
* site in generateScenario for why comment adjacency cannot do the job.
|
|
1110
|
+
*/
|
|
1111
|
+
readonly stepTrace = new Map<string, string[]>();
|
|
1112
|
+
|
|
959
1113
|
private async generateScenario(
|
|
960
1114
|
scenario: ParsedScenario,
|
|
961
1115
|
hasBackground: boolean,
|
|
@@ -987,35 +1141,7 @@ export class CodeGenerator {
|
|
|
987
1141
|
warnMultipleAuthTags(scenario.tags, `scenario '${scenario.name}'`);
|
|
988
1142
|
}
|
|
989
1143
|
|
|
990
|
-
|
|
991
|
-
this.stepMapper.setScenarioContext(stepsToMap);
|
|
992
|
-
|
|
993
|
-
// Data-driven (@cases): the scenario's {{col}} refs are dataset *row columns* that
|
|
994
|
-
// exist only at runtime — register them as captured so they resolve to a runtime
|
|
995
|
-
// get() (→ rowData.get) instead of compile-time YAML lookup.
|
|
996
|
-
const casesTag = scenario.tags.find((t) => t.startsWith('@cases:'));
|
|
997
|
-
const casesDataset = casesTag ? casesTag.slice('@cases:'.length).trim() : undefined;
|
|
998
|
-
if (casesDataset) {
|
|
999
|
-
const refs = new Set<string>();
|
|
1000
|
-
for (const st of stepsToMap) {
|
|
1001
|
-
for (const m of (st.text || '').matchAll(/\{\{\s*([\w.]+)\s*\}\}/g)) refs.add(m[1]);
|
|
1002
|
-
}
|
|
1003
|
-
for (const r of refs) this.stepMapper.registerCaptured(r);
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
// Capability preconditions (db `@query:<name>` → bind {{name}}) are owned by the capability via
|
|
1007
|
-
// the SPI. Their bound `{{name.*}}` vars exist only at runtime → register them as captured so
|
|
1008
|
-
// they resolve to a runtime get() instead of a compile-time YAML lookup that would fail.
|
|
1009
|
-
const preconditions = this.capabilityPreconditions(scenario);
|
|
1010
|
-
const boundVars = preconditions.flatMap((p) => p.boundVars || []);
|
|
1011
|
-
if (boundVars.length) {
|
|
1012
|
-
for (const st of stepsToMap) {
|
|
1013
|
-
for (const mt of (st.text || '').matchAll(/\{\{\s*([^}]+?)\s*\}\}/g)) {
|
|
1014
|
-
const head = mt[1].split(/[.[]/)[0];
|
|
1015
|
-
if (boundVars.includes(head)) this.stepMapper.registerCaptured(mt[1]);
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1144
|
+
const { preconditions, casesDataset } = this.prepareScenarioBody(scenario, stepsToMap);
|
|
1019
1145
|
|
|
1020
1146
|
const steps: Array<{ comment?: string; code: string }> = [];
|
|
1021
1147
|
|
|
@@ -1046,11 +1172,7 @@ export class CodeGenerator {
|
|
|
1046
1172
|
}
|
|
1047
1173
|
}
|
|
1048
1174
|
|
|
1049
|
-
|
|
1050
|
-
// above) run BEFORE the scenario's own steps — prepend them, indenting the capability statements.
|
|
1051
|
-
if (preconditions.length) {
|
|
1052
|
-
steps.unshift(...preconditions.map((p) => ({ comment: p.comment, code: this.indentCode(p.code, 4) })));
|
|
1053
|
-
}
|
|
1175
|
+
this.finalizeScenarioBody(steps, preconditions);
|
|
1054
1176
|
|
|
1055
1177
|
// Mock scenario epilogue. (a) EVERY path: the fail-on-unmocked verdict — the guard
|
|
1056
1178
|
// records+aborts unmatched requests (it must not throw inside a route handler:
|
|
@@ -1080,6 +1202,15 @@ export class CodeGenerator {
|
|
|
1080
1202
|
const tags = formatPassThroughTags(extractPassThroughTags(scenario.tags, featureTags), this.adapter.frameworkName);
|
|
1081
1203
|
|
|
1082
1204
|
// Use adapter to render scenario
|
|
1205
|
+
// #673 — record which `//` lines of this scenario are STEP markers, in order. A generated
|
|
1206
|
+
// spec has no textual way to tell a step comment from a template body comment (both land at
|
|
1207
|
+
// the same indent), so script-check attributed code to steps by comment ADJACENCY: any two
|
|
1208
|
+
// adjacent comments gave the first one an empty region → a false hollow BYPASS. Three shipped
|
|
1209
|
+
// templates emit consecutive `//` lines (user-login-todo 6, clear-database 4,
|
|
1210
|
+
// alert-text-assertion 2), as do `// [from @steps:…]` headers, so the check fired on sungen's
|
|
1211
|
+
// own output. The trace makes the attribution structural instead of positional.
|
|
1212
|
+
this.stepTrace.set(scenario.name, steps.map((s) => s.comment).filter((c): c is string => !!c));
|
|
1213
|
+
|
|
1083
1214
|
const rendered = this.adapter.renderScenario({
|
|
1084
1215
|
scenarioName: scenario.name,
|
|
1085
1216
|
steps,
|