@sun-asterisk/sungen 3.2.24-beta.6 → 3.2.25
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/context-router.d.ts.map +1 -1
- package/dist/capabilities/context-router.js +15 -7
- package/dist/capabilities/context-router.js.map +1 -1
- package/dist/cli/commands/capability.d.ts.map +1 -1
- package/dist/cli/commands/capability.js +6 -48
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/changed.d.ts +14 -0
- package/dist/cli/commands/changed.d.ts.map +1 -0
- package/dist/cli/commands/changed.js +128 -0
- package/dist/cli/commands/changed.js.map +1 -0
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +25 -1
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/commands/generate.js +1 -1
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/results-merge.d.ts +19 -0
- package/dist/cli/commands/results-merge.d.ts.map +1 -0
- package/dist/cli/commands/results-merge.js +191 -0
- package/dist/cli/commands/results-merge.js.map +1 -0
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +100 -12
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/index.js +4 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/exporters/api-catalog-loader.d.ts.map +1 -1
- package/dist/exporters/api-catalog-loader.js +10 -3
- package/dist/exporters/api-catalog-loader.js.map +1 -1
- package/dist/exporters/api-testcase-formatter.d.ts +9 -0
- package/dist/exporters/api-testcase-formatter.d.ts.map +1 -1
- package/dist/exporters/api-testcase-formatter.js +55 -3
- package/dist/exporters/api-testcase-formatter.js.map +1 -1
- package/dist/exporters/csv-exporter.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.js +10 -1
- package/dist/exporters/csv-exporter.js.map +1 -1
- package/dist/exporters/json-exporter.d.ts.map +1 -1
- package/dist/exporters/json-exporter.js +6 -1
- package/dist/exporters/json-exporter.js.map +1 -1
- package/dist/exporters/matrix/build.d.ts.map +1 -1
- package/dist/exporters/matrix/build.js +10 -0
- package/dist/exporters/matrix/build.js.map +1 -1
- package/dist/exporters/matrix/export.js +1 -1
- package/dist/exporters/matrix/export.js.map +1 -1
- package/dist/exporters/matrix/render-csv.d.ts.map +1 -1
- package/dist/exporters/matrix/render-csv.js +5 -1
- package/dist/exporters/matrix/render-csv.js.map +1 -1
- package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
- package/dist/exporters/matrix/render-xlsx.js +4 -1
- package/dist/exporters/matrix/render-xlsx.js.map +1 -1
- package/dist/exporters/matrix/types.d.ts +7 -0
- package/dist/exporters/matrix/types.d.ts.map +1 -1
- package/dist/exporters/matrix/types.js.map +1 -1
- package/dist/exporters/playwright-report-parser.d.ts +17 -2
- package/dist/exporters/playwright-report-parser.d.ts.map +1 -1
- package/dist/exporters/playwright-report-parser.js +68 -8
- package/dist/exporters/playwright-report-parser.js.map +1 -1
- package/dist/exporters/results-merge.d.ts +46 -0
- package/dist/exporters/results-merge.d.ts.map +1 -0
- package/dist/exporters/results-merge.js +148 -0
- package/dist/exporters/results-merge.js.map +1 -0
- package/dist/exporters/selector-key-resolver.d.ts +4 -0
- package/dist/exporters/selector-key-resolver.d.ts.map +1 -1
- package/dist/exporters/selector-key-resolver.js +101 -1
- package/dist/exporters/selector-key-resolver.js.map +1 -1
- package/dist/exporters/types.d.ts +8 -1
- package/dist/exporters/types.d.ts.map +1 -1
- package/dist/exporters/xlsx-report-builder.d.ts.map +1 -1
- package/dist/exporters/xlsx-report-builder.js +5 -4
- package/dist/exporters/xlsx-report-builder.js.map +1 -1
- package/dist/generators/gherkin-parser/index.d.ts +2 -0
- package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
- package/dist/generators/gherkin-parser/index.js +52 -1
- package/dist/generators/gherkin-parser/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts +20 -10
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts +2 -11
- 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/imports.hbs +3 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/biometric-action.hbs +9 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/check-action.hbs +11 -6
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/date-picker-unsupported.hbs +4 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/deep-link-action.hbs +8 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/lifecycle-missing-app-id.hbs +5 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/location-services-action.hbs +11 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/reinstall-app-action.hbs +21 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/restart-app-action.hbs +16 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/set-connectivity-action.hbs +11 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/uncheck-action.hbs +9 -5
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/checked-assertion.hbs +10 -4
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/device-log-assertion.hbs +16 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/disappears-within-assertion.hbs +5 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/matches-format-assertion.hbs +15 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/not-checked-assertion.hbs +6 -4
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/state-with-filter-assertion.hbs +4 -3
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/still-visible-after-assertion.hbs +4 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +5 -2
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/pull-to-refresh-action.hbs +3 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/swipe-action.hbs +3 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/partials/appium-selector.hbs +7 -1
- package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +302 -29
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +2 -11
- 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/imports.hbs +3 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs +17 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/attribute-assertion.hbs +6 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/have-value-assertion.hbs +18 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-filter-assertion.hbs +5 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/matches-format-assertion.hbs +5 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +4 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-for-element-with-text.hbs +4 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-expect-text.hbs +1 -1
- package/dist/generators/test-generator/code-generator.d.ts +6 -0
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +80 -7
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/diagnostics.d.ts +60 -6
- package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
- package/dist/generators/test-generator/diagnostics.js +59 -5
- package/dist/generators/test-generator/diagnostics.js.map +1 -1
- package/dist/generators/test-generator/patterns/expect-patterns.d.ts +7 -0
- package/dist/generators/test-generator/patterns/expect-patterns.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/expect-patterns.js +36 -2
- package/dist/generators/test-generator/patterns/expect-patterns.js.map +1 -1
- package/dist/generators/test-generator/patterns/index.d.ts +19 -12
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +58 -38
- package/dist/generators/test-generator/patterns/index.js.map +1 -1
- package/dist/generators/test-generator/patterns/types.d.ts +23 -0
- package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/unsupported-step-code.d.ts +18 -0
- package/dist/generators/test-generator/patterns/unsupported-step-code.d.ts.map +1 -0
- package/dist/generators/test-generator/patterns/unsupported-step-code.js +23 -0
- package/dist/generators/test-generator/patterns/unsupported-step-code.js.map +1 -0
- package/dist/generators/test-generator/recipe-expander.js +9 -1
- package/dist/generators/test-generator/recipe-expander.js.map +1 -1
- package/dist/generators/test-generator/reset-tier.d.ts +68 -0
- package/dist/generators/test-generator/reset-tier.d.ts.map +1 -0
- package/dist/generators/test-generator/reset-tier.js +94 -0
- package/dist/generators/test-generator/reset-tier.js.map +1 -0
- package/dist/generators/test-generator/step-mapper.d.ts +39 -0
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +144 -5
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/template-engine.d.ts +2 -10
- package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
- package/dist/generators/test-generator/template-engine.js +19 -2
- package/dist/generators/test-generator/template-engine.js.map +1 -1
- package/dist/generators/test-generator/utils/format-pattern.d.ts +35 -0
- package/dist/generators/test-generator/utils/format-pattern.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/format-pattern.js +82 -0
- package/dist/generators/test-generator/utils/format-pattern.js.map +1 -0
- package/dist/generators/test-generator/utils/js-literal-scanner.d.ts +42 -0
- package/dist/generators/test-generator/utils/js-literal-scanner.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/js-literal-scanner.js +142 -0
- package/dist/generators/test-generator/utils/js-literal-scanner.js.map +1 -0
- package/dist/generators/test-generator/utils/mask-refs-for-matching.d.ts +18 -0
- package/dist/generators/test-generator/utils/mask-refs-for-matching.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/mask-refs-for-matching.js +25 -0
- package/dist/generators/test-generator/utils/mask-refs-for-matching.js.map +1 -0
- package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts +9 -1
- package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/runtime-data-transformer.js +124 -63
- package/dist/generators/test-generator/utils/runtime-data-transformer.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +24 -1
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/generators/test-generator/utils/spec-syntax-check.d.ts +14 -0
- package/dist/generators/test-generator/utils/spec-syntax-check.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/spec-syntax-check.js +26 -0
- package/dist/generators/test-generator/utils/spec-syntax-check.js.map +1 -0
- package/dist/harness/annotation-overrides.d.ts +0 -12
- package/dist/harness/annotation-overrides.d.ts.map +1 -1
- package/dist/harness/annotation-overrides.js +9 -5
- package/dist/harness/annotation-overrides.js.map +1 -1
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +7 -5
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/capability-plan.d.ts +2 -1
- package/dist/harness/capability-plan.d.ts.map +1 -1
- package/dist/harness/capability-plan.js +14 -4
- package/dist/harness/capability-plan.js.map +1 -1
- package/dist/harness/capability.d.ts +11 -0
- package/dist/harness/capability.d.ts.map +1 -1
- package/dist/harness/capability.js +8 -0
- package/dist/harness/capability.js.map +1 -1
- package/dist/harness/catalog/drivers.yaml +5 -0
- package/dist/harness/parse.d.ts +1 -0
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +6 -1
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/run-manifest.d.ts +58 -0
- package/dist/harness/run-manifest.d.ts.map +1 -0
- package/dist/harness/run-manifest.js +264 -0
- package/dist/harness/run-manifest.js.map +1 -0
- package/dist/harness/script-check.d.ts.map +1 -1
- package/dist/harness/script-check.js +6 -2
- package/dist/harness/script-check.js.map +1 -1
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +5 -1
- package/dist/harness/sensors.js.map +1 -1
- package/dist/harness/viewpoint-ledger.d.ts +1 -1
- package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
- package/dist/harness/viewpoint-ledger.js +25 -4
- package/dist/harness/viewpoint-ledger.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/orchestrator/lockstep-version.d.ts +22 -0
- package/dist/orchestrator/lockstep-version.d.ts.map +1 -0
- package/dist/orchestrator/lockstep-version.js +58 -0
- package/dist/orchestrator/lockstep-version.js.map +1 -0
- package/dist/orchestrator/mobile-runtime-scaffolder.d.ts.map +1 -1
- package/dist/orchestrator/mobile-runtime-scaffolder.js +5 -1
- package/dist/orchestrator/mobile-runtime-scaffolder.js.map +1 -1
- package/dist/orchestrator/project-deps-sync.d.ts +39 -0
- package/dist/orchestrator/project-deps-sync.d.ts.map +1 -0
- package/dist/orchestrator/project-deps-sync.js +175 -0
- package/dist/orchestrator/project-deps-sync.js.map +1 -0
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +5 -2
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/run-test.md +109 -0
- package/dist/orchestrator/templates/ai-src/config/claude.md +1 -1
- package/dist/orchestrator/templates/ai-src/config/copilot.md +1 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-api-design/SKILL.md +49 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +14 -3
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +120 -10
- package/dist/orchestrator/templates/ai-src/skills/sungen-locale/SKILL.md +1 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-mobile-gestures/SKILL.md +31 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +1 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +9 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +15 -0
- package/dist/orchestrator/templates/env.appium.example +21 -0
- package/dist/orchestrator/templates/specs-api.d.ts +4 -2
- package/dist/orchestrator/templates/specs-api.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-api.js +233 -10
- package/dist/orchestrator/templates/specs-api.js.map +1 -1
- package/dist/orchestrator/templates/specs-api.ts +214 -11
- package/dist/orchestrator/templates/specs-db.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-db.js +79 -6
- package/dist/orchestrator/templates/specs-db.js.map +1 -1
- package/dist/orchestrator/templates/specs-db.ts +70 -7
- package/dist/orchestrator/templates/specs-regex-escape.d.ts +13 -0
- package/dist/orchestrator/templates/specs-regex-escape.d.ts.map +1 -0
- package/dist/orchestrator/templates/specs-regex-escape.js +19 -0
- package/dist/orchestrator/templates/specs-regex-escape.js.map +1 -0
- package/dist/orchestrator/templates/specs-regex-escape.ts +16 -0
- package/dist/orchestrator/templates/specs-test-data.ts +53 -6
- package/dist/orchestrator/templates/wdio.conf.ts +126 -10
- package/dist/utils/selector-types.d.ts +1 -1
- package/dist/utils/selector-types.d.ts.map +1 -1
- package/dist/utils/selector-types.js +1 -0
- package/dist/utils/selector-types.js.map +1 -1
- package/package.json +8 -6
- package/src/capabilities/context-router.ts +13 -5
- package/src/cli/commands/capability.ts +4 -38
- package/src/cli/commands/changed.ts +104 -0
- package/src/cli/commands/delivery.ts +27 -2
- package/src/cli/commands/generate.ts +1 -1
- package/src/cli/commands/results-merge.ts +153 -0
- package/src/cli/commands/update.ts +72 -13
- package/src/cli/index.ts +4 -0
- package/src/exporters/api-catalog-loader.ts +10 -2
- package/src/exporters/api-testcase-formatter.ts +59 -3
- package/src/exporters/csv-exporter.ts +11 -2
- package/src/exporters/json-exporter.ts +7 -2
- package/src/exporters/matrix/build.ts +9 -0
- package/src/exporters/matrix/export.ts +1 -1
- package/src/exporters/matrix/render-csv.ts +5 -1
- package/src/exporters/matrix/render-xlsx.ts +4 -1
- package/src/exporters/matrix/types.ts +7 -0
- package/src/exporters/playwright-report-parser.ts +68 -9
- package/src/exporters/results-merge.ts +150 -0
- package/src/exporters/selector-key-resolver.ts +81 -1
- package/src/exporters/types.ts +8 -1
- package/src/exporters/xlsx-report-builder.ts +5 -4
- package/src/generators/gherkin-parser/index.ts +55 -1
- package/src/generators/test-generator/adapters/adapter-interface.ts +23 -1
- package/src/generators/test-generator/adapters/appium/appium-adapter.ts +2 -2
- package/src/generators/test-generator/adapters/appium/templates/imports.hbs +3 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/biometric-action.hbs +9 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/check-action.hbs +11 -6
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/date-picker-unsupported.hbs +4 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/deep-link-action.hbs +8 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/lifecycle-missing-app-id.hbs +5 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/location-services-action.hbs +11 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/reinstall-app-action.hbs +21 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/restart-app-action.hbs +16 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/set-connectivity-action.hbs +11 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/uncheck-action.hbs +9 -5
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/checked-assertion.hbs +10 -4
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/device-log-assertion.hbs +16 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/disappears-within-assertion.hbs +5 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/matches-format-assertion.hbs +15 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/not-checked-assertion.hbs +6 -4
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/state-with-filter-assertion.hbs +4 -3
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/still-visible-after-assertion.hbs +4 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +5 -2
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/pull-to-refresh-action.hbs +3 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/swipe-action.hbs +3 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/partials/appium-selector.hbs +7 -1
- package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +302 -29
- package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +2 -2
- package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs +17 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/attribute-assertion.hbs +6 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/have-value-assertion.hbs +18 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-filter-assertion.hbs +5 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/matches-format-assertion.hbs +5 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +4 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-for-element-with-text.hbs +4 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-expect-text.hbs +1 -1
- package/src/generators/test-generator/code-generator.ts +86 -7
- package/src/generators/test-generator/diagnostics.ts +60 -6
- package/src/generators/test-generator/patterns/expect-patterns.ts +37 -2
- package/src/generators/test-generator/patterns/index.ts +58 -33
- package/src/generators/test-generator/patterns/types.ts +36 -0
- package/src/generators/test-generator/patterns/unsupported-step-code.ts +19 -0
- package/src/generators/test-generator/recipe-expander.ts +9 -1
- package/src/generators/test-generator/reset-tier.ts +93 -0
- package/src/generators/test-generator/step-mapper.ts +149 -6
- package/src/generators/test-generator/template-engine.ts +22 -3
- package/src/generators/test-generator/utils/format-pattern.ts +79 -0
- package/src/generators/test-generator/utils/js-literal-scanner.ts +132 -0
- package/src/generators/test-generator/utils/mask-refs-for-matching.ts +21 -0
- package/src/generators/test-generator/utils/runtime-data-transformer.ts +126 -76
- package/src/generators/test-generator/utils/selector-resolver.ts +26 -1
- package/src/generators/test-generator/utils/spec-syntax-check.ts +22 -0
- package/src/harness/annotation-overrides.ts +10 -5
- package/src/harness/audit.ts +7 -5
- package/src/harness/capability-plan.ts +13 -6
- package/src/harness/capability.ts +16 -0
- package/src/harness/catalog/drivers.yaml +5 -0
- package/src/harness/parse.ts +7 -1
- package/src/harness/run-manifest.ts +254 -0
- package/src/harness/script-check.ts +6 -2
- package/src/harness/sensors.ts +5 -1
- package/src/harness/viewpoint-ledger.ts +26 -5
- package/src/index.ts +1 -0
- package/src/orchestrator/lockstep-version.ts +46 -0
- package/src/orchestrator/mobile-runtime-scaffolder.ts +5 -1
- package/src/orchestrator/project-deps-sync.ts +175 -0
- package/src/orchestrator/project-initializer.ts +5 -2
- package/src/orchestrator/templates/ai-src/commands/run-test.md +109 -0
- package/src/orchestrator/templates/ai-src/config/claude.md +1 -1
- package/src/orchestrator/templates/ai-src/config/copilot.md +1 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-api-design/SKILL.md +49 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +14 -3
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +120 -10
- package/src/orchestrator/templates/ai-src/skills/sungen-locale/SKILL.md +1 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-mobile-gestures/SKILL.md +31 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +1 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +9 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +15 -0
- package/src/orchestrator/templates/env.appium.example +21 -0
- package/src/orchestrator/templates/specs-api.ts +214 -11
- package/src/orchestrator/templates/specs-db.ts +70 -7
- package/src/orchestrator/templates/specs-regex-escape.ts +16 -0
- package/src/orchestrator/templates/specs-test-data.ts +53 -6
- package/src/orchestrator/templates/wdio.conf.ts +126 -10
- package/src/utils/selector-types.ts +1 -0
|
@@ -72,20 +72,38 @@ async function __ensureVisible(selector: string): Promise<WebdriverIO.Element> {
|
|
|
72
72
|
}
|
|
73
73
|
const safeBottom = Math.round(winH * 0.82); // above the floating bottom-nav band
|
|
74
74
|
let lastY: number | null = null;
|
|
75
|
+
let lastEl: WebdriverIO.Element | null = null;
|
|
75
76
|
for (let __i = 0; __i < 12; __i++) {
|
|
76
77
|
let y: number | null = null;
|
|
78
|
+
let existsUndisplayed = false;
|
|
77
79
|
try {
|
|
78
80
|
const el = await $(selector);
|
|
79
81
|
if (await el.isDisplayed()) {
|
|
80
82
|
const r = await driver.getElementRect(el.elementId);
|
|
81
|
-
|
|
83
|
+
// Done when the element sits fully above the floating bottom-nav band — OR is simply too
|
|
84
|
+
// tall to ever fit there (a scroll container / full-screen list): demanding "fully above"
|
|
85
|
+
// from such an element makes the loop scroll a page that was already correct, dragging
|
|
86
|
+
// list-hosted siblings (e.g. the top bar as first list item) out of the viewport.
|
|
87
|
+
if (r.y + r.height <= safeBottom || r.height >= safeBottom) return el;
|
|
82
88
|
y = r.y;
|
|
89
|
+
lastEl = el;
|
|
90
|
+
} else if (el.elementId) {
|
|
91
|
+
existsUndisplayed = true; // in the tree, just not visible right now
|
|
83
92
|
}
|
|
84
93
|
} catch { /* off-screen / not in tree yet → keep scrolling */ }
|
|
94
|
+
// Overlay grace (Android mirror of the iOS render grace above): the element is IN the tree but
|
|
95
|
+
// reports not-displayed — typically a launch popup/overlay covering it that auto-dismisses
|
|
96
|
+
// within seconds. Scrolling now would carry a list-hosted element (a Compose top bar is often
|
|
97
|
+
// the first list item) clean out of the viewport, where a down-only loop can never recover it —
|
|
98
|
+
// so wait the overlay out before the first scroll.
|
|
99
|
+
if (existsUndisplayed && __i < 4) { await driver.pause(1200); continue; }
|
|
85
100
|
// No-progress break: the element is on screen but a prior scroll didn't move it — it's a FIXED
|
|
86
101
|
// element (e.g. a bottom-nav tab, intentionally in the nav band) or we've hit the end of the
|
|
87
102
|
// scrollable. Stop rather than burn the whole budget; the caller acts on it where it is.
|
|
88
|
-
|
|
103
|
+
// Return the RESOLVED element, never a fresh $() stub: on an animation-churned tree (a looping
|
|
104
|
+
// banner is enough) a from-scratch re-find takes seconds and outlasts the caller's assertion
|
|
105
|
+
// budget — the element we just measured is the one the caller should act on.
|
|
106
|
+
if (y !== null && lastY !== null && Math.abs(y - lastY) < 2) return lastEl ?? $(selector);
|
|
89
107
|
lastY = y;
|
|
90
108
|
await driver.execute('mobile: scrollGesture', { left: 60, top: 420, width: 960, height: 1320, direction: 'down', percent: 0.5 });
|
|
91
109
|
await driver.pause(300);
|
|
@@ -116,6 +134,74 @@ async function __assertVisible(selector: string): Promise<void> {
|
|
|
116
134
|
}
|
|
117
135
|
throw new Error(`Expect ${JSON.stringify(selector)} to be displayed — iOS geometry oracle: rect=${JSON.stringify(r)} is not on screen (0x0 = not rendered / scrolled out)`);
|
|
118
136
|
}
|
|
137
|
+
|
|
138
|
+
// Checkbox / toggle / radio state oracle. There is NO single authoritative source for it on
|
|
139
|
+
// mobile, and the standard one lies in a way that cannot be detected from the read itself:
|
|
140
|
+
// UiAutomator2 puts `checked` on EVERY Android node and defaults it to "false" when the widget
|
|
141
|
+
// publishes no checked semantics. So a custom-rendered control — a Flutter widget without
|
|
142
|
+
// `Semantics(checked: …)`, an RN `Pressable` without `accessibilityState={ checked: … }` — is not
|
|
143
|
+
// ABSENT from the tree, it reports a confident false while sitting visibly ON, and a strict
|
|
144
|
+
// `toHaveAttribute('checked','true')` fails with no way to see why.
|
|
145
|
+
// Hence: probe every signal both runtimes expose, and let the DIRECTION of the assertion decide
|
|
146
|
+
// how to combine them (__assertCheckedState below).
|
|
147
|
+
// Android: `checked`, `selected`, isSelected() iOS: `value` ("1"/"0"), `selected`, isSelected()
|
|
148
|
+
// Descendants are probed too, bounded: on a composite row (label + control merged into one a11y
|
|
149
|
+
// node) the state routinely sits on an inner node while the queried outer node reports the default.
|
|
150
|
+
// A child xpath must start with `.//` — a leading `//` is ABSOLUTE in Appium even when chained.
|
|
151
|
+
async function __probeCheckedSignals(el: WebdriverIO.Element): Promise<{ source: string; value: boolean }[]> {
|
|
152
|
+
const out: { source: string; value: boolean }[] = [];
|
|
153
|
+
const read = async (node: WebdriverIO.Element, prefix: string): Promise<void> => {
|
|
154
|
+
for (const attr of driver.isIOS ? ['value', 'selected'] : ['checked', 'selected']) {
|
|
155
|
+
let raw: string | null = null;
|
|
156
|
+
try { raw = await node.getAttribute(attr); } catch { continue; } // attribute unsupported on this node
|
|
157
|
+
const v = String(raw ?? '').trim().toLowerCase();
|
|
158
|
+
if (v === 'true' || v === '1') out.push({ source: `${prefix}${attr}="${raw}"`, value: true });
|
|
159
|
+
else if (v === 'false' || v === '0') out.push({ source: `${prefix}${attr}="${raw}"`, value: false });
|
|
160
|
+
// anything else (empty, a real iOS `value` string like "Off") carries no parseable state → skip
|
|
161
|
+
}
|
|
162
|
+
try { out.push({ source: `${prefix}isSelected()`, value: await node.isSelected() }); } catch { /* not supported here */ }
|
|
163
|
+
};
|
|
164
|
+
await read(el, '');
|
|
165
|
+
// Only descend when the node itself yielded nothing positive — otherwise we already have the
|
|
166
|
+
// answer and a subtree walk would turn one assertion into dozens of round-trips for nothing.
|
|
167
|
+
if (!out.some(s => s.value)) {
|
|
168
|
+
let kids: WebdriverIO.Element[] = [];
|
|
169
|
+
try { kids = await el.$$('.//*'); } catch { /* no child query on this node */ }
|
|
170
|
+
for (const k of kids.slice(0, 8)) await read(k, 'descendant.');
|
|
171
|
+
}
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Assert checked/unchecked from the probed signals. The two directions are deliberately NOT
|
|
176
|
+
// symmetric, because the evidence isn't:
|
|
177
|
+
// checked → ANY credible positive signal passes. One runtime exposing the state is enough;
|
|
178
|
+
// demanding the standard attribute is what breaks custom-rendered controls.
|
|
179
|
+
// unchecked → NO signal anywhere may be positive. Strictly stronger than reading one attribute,
|
|
180
|
+
// so a visibly-ON custom toggle can't slip through as a false PASS.
|
|
181
|
+
// Polls like WDIO's own matchers do — a tap-then-assert reads the tree mid-animation.
|
|
182
|
+
// KNOWN LIMIT, stated rather than hidden: on Android a UiAutomator2-defaulted `checked="false"` is
|
|
183
|
+
// indistinguishable from a genuine false — that information is not in the tree. So `unchecked`
|
|
184
|
+
// passes on "no positive signal anywhere", which is the strongest available evidence, not proof.
|
|
185
|
+
async function __assertCheckedState(el: WebdriverIO.Element, expected: boolean, label: string): Promise<void> {
|
|
186
|
+
// Fail loud with the REAL cause when the element never resolved (mirrors check-action, #430).
|
|
187
|
+
if (!el.elementId) throw new Error((el as any).error?.message || `Element not found: ${el.selector}`);
|
|
188
|
+
let signals: { source: string; value: boolean }[] = [];
|
|
189
|
+
for (let __i = 0; __i < 10; __i++) {
|
|
190
|
+
signals = await __probeCheckedSignals(el);
|
|
191
|
+
if (signals.some(s => s.value) === expected) return;
|
|
192
|
+
await driver.pause(500);
|
|
193
|
+
}
|
|
194
|
+
const want = expected ? 'checked' : 'unchecked';
|
|
195
|
+
if (signals.length === 0) {
|
|
196
|
+
throw new Error(
|
|
197
|
+
`Expect ${label} to be ${want} — the control publishes NO checked/selected state, on itself or any child. ` +
|
|
198
|
+
`A custom-rendered control has to publish it for any tool to read it: Flutter → wrap it in Semantics(checked: …); ` +
|
|
199
|
+
`React Native → accessibilityState={ checked: … }. Until then assert a visible consequence instead ` +
|
|
200
|
+
`(a label/text change), or tag the scenario @manual.`,
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
throw new Error(`Expect ${label} to be ${want} — read [${signals.map(s => `${s.source} → ${s.value}`).join(', ')}]`);
|
|
204
|
+
}
|
|
119
205
|
{{#if singleAuthRole}}
|
|
120
206
|
import * as fs from 'node:fs';
|
|
121
207
|
import * as path from 'node:path';
|
|
@@ -133,7 +219,9 @@ function __authSelector(s: { type: string; value: string }): string {
|
|
|
133
219
|
case 'accessibility-id': case 'testid': case 'label': case 'placeholder': case 'text': return '~' + s.value;
|
|
134
220
|
case 'android-uiautomator': return 'android=' + s.value;
|
|
135
221
|
case 'ios-predicate': return '-ios predicate string:' + s.value;
|
|
136
|
-
|
|
222
|
+
// Appium's id strategy prepends `<appPackage>:id/` to colon-less values, which breaks bare
|
|
223
|
+
// Compose testTagsAsResourceId ids — match those exactly via UiSelector on Android instead.
|
|
224
|
+
case 'id': return (!driver.isIOS && !s.value.includes(':')) ? 'android=new UiSelector().resourceId("' + s.value + '")' : 'id=' + s.value;
|
|
137
225
|
case 'xpath': case 'locator': default: return s.value;
|
|
138
226
|
}
|
|
139
227
|
}
|
|
@@ -205,10 +293,29 @@ async function __ensureAuth(role: string): Promise<void> {
|
|
|
205
293
|
}
|
|
206
294
|
|
|
207
295
|
// ── Mode B: legacy authedMarker (present on the landing screen ⟺ logged in) ──
|
|
208
|
-
|
|
209
|
-
|
|
296
|
+
// markerTimeoutMs: contract override for slow-booting apps — a cold start that needs 10-15s+ to
|
|
297
|
+
// render the landing screen false-negatives the 6s default into "logged out" and then blocks the
|
|
298
|
+
// spec when the login form never appears.
|
|
299
|
+
const __budget = cfg.markerTimeoutMs || 6000;
|
|
300
|
+
// When the contract also names a loggedOutMarker (an element unique to the login screen), the
|
|
301
|
+
// state is decided by WHICHEVER marker renders first (alternating short probes): a slow-booting
|
|
302
|
+
// landing screen costs only its real boot time instead of gambling on one fixed timeout, and a
|
|
303
|
+
// genuine logged-out state is detected without waiting out the full authed budget first.
|
|
304
|
+
if (cfg.loggedOutMarker) {
|
|
305
|
+
let loggedOut = false;
|
|
306
|
+
const __t0 = Date.now();
|
|
307
|
+
while (Date.now() - __t0 < __budget) {
|
|
308
|
+
if (await present(cfg.authedMarker, 3000)) return; // logged in — nothing to do
|
|
309
|
+
if (await present(cfg.loggedOutMarker, 3000)) { loggedOut = true; break; }
|
|
310
|
+
}
|
|
311
|
+
if (!loggedOut) {
|
|
312
|
+
throw new Error(`@auth:${role} could not decide the login state within ${__budget}ms — neither authedMarker nor loggedOutMarker rendered. Check the markers in ${rel}.`);
|
|
313
|
+
}
|
|
314
|
+
} else if (await present(cfg.authedMarker, __budget)) {
|
|
315
|
+
return; // idempotent — already authenticated (session kept by noReset)
|
|
316
|
+
}
|
|
210
317
|
await runSteps(cfg.loginSteps);
|
|
211
|
-
if (!(await
|
|
318
|
+
if (!(await present(cfg.authedMarker, __budget))) {
|
|
212
319
|
throw new Error(`@auth:${role} login failed — verify credentials / loginSteps / authedMarker in ${rel}.`);
|
|
213
320
|
}
|
|
214
321
|
}
|
|
@@ -259,37 +366,203 @@ describe('{{featureName}}', () => {
|
|
|
259
366
|
}
|
|
260
367
|
return __APP_PKG_DEFAULT__;
|
|
261
368
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
|
|
369
|
+
// ── Reset tier ────────────────────────────────────────────────────────────────────────────────
|
|
370
|
+
// How much state each scenario starts from (`@reset:` on the Feature; default `soft`):
|
|
371
|
+
// none — nothing reset; state carries over BY DESIGN (a cart accumulating across scenarios)
|
|
372
|
+
// soft — terminate + activate: in-memory state and the nav stack
|
|
373
|
+
// data — soft + wipe the app's data layer (local DB, cache, prefs, keychain)
|
|
374
|
+
// full — remove + reinstall: also permissions and install-time state
|
|
375
|
+
// Unlike web, no cheap "reload the URL" exists here, and a cold start is 5–30s — hence a ladder
|
|
376
|
+
// rather than one all-or-nothing switch.
|
|
377
|
+
const __DECLARED_TIER = '{{resetTier}}';
|
|
378
|
+
const __TIER_ORDER = ['none', 'soft', 'data', 'full'];
|
|
379
|
+
// SUNGEN_RESET_TIER RAISES the floor for a whole run — CI at `data`, local dev at `soft` — without
|
|
380
|
+
// regenerating a single spec (same runtime-resolution model as the test-data overlays). It never
|
|
381
|
+
// LOWERS a declared tier: a feature that asked for `full` needs it. Resolved once, so an
|
|
382
|
+
// unrecognized value warns once rather than per scenario — a silently ignored typo here would
|
|
383
|
+
// reintroduce exactly the false-confidence this tier system exists to remove.
|
|
384
|
+
const __RUN_TIER: string = (() => {
|
|
385
|
+
// Defensive only — the compiler validates the tag — but fall back to `soft`, NOT to index 0
|
|
386
|
+
// (`none`). A safety net that silently drops to "reset nothing" points the wrong way.
|
|
387
|
+
const declaredIdx = __TIER_ORDER.indexOf(__DECLARED_TIER);
|
|
388
|
+
const declared = declaredIdx < 0 ? __TIER_ORDER.indexOf('soft') : declaredIdx;
|
|
389
|
+
const raw = (process.env.SUNGEN_RESET_TIER || '').trim().toLowerCase();
|
|
390
|
+
if (!raw) return __TIER_ORDER[declared];
|
|
391
|
+
const floor = __TIER_ORDER.indexOf(raw);
|
|
392
|
+
if (floor < 0) {
|
|
393
|
+
console.warn(` ! SUNGEN_RESET_TIER='${raw}' is not a tier (${__TIER_ORDER.join(', ')}) — ignored`);
|
|
394
|
+
return __TIER_ORDER[declared];
|
|
395
|
+
}
|
|
396
|
+
return __TIER_ORDER[Math.max(declared, floor)];
|
|
397
|
+
})();
|
|
398
|
+
|
|
399
|
+
// A scenario that FAILED left unknown state behind — a half-written local DB, an open modal — that a
|
|
400
|
+
// plain process relaunch does not undo. So the NEXT scenario resets one rung harder than it asked
|
|
401
|
+
// for: this is the "A fails mid-flow, B fails for A's reasons" case that no amount of relaunching
|
|
402
|
+
// fixes. One rung, one scenario, never sticky — otherwise a single flake would silently promote the
|
|
403
|
+
// whole run to `full` and put 30s on every remaining scenario.
|
|
404
|
+
// `none` is a FIXED POINT by design: it is an explicit state-carryover contract (a cart built up
|
|
405
|
+
// across scenarios), and escalating would silently break the very thing it was asked for. The chain
|
|
406
|
+
// is dead once one link fails anyway, so a relaunch would change only how the rest fail.
|
|
407
|
+
// Mirrors escalateResetTier() in the compiler; a test asserts the two ladders agree.
|
|
408
|
+
const __ESCALATE: Record<string, string> = { none: 'none', soft: 'data', data: 'full', full: 'full' };
|
|
409
|
+
// Holds the TITLE of the scenario that failed, not a boolean — because escalation must reach the next
|
|
410
|
+
// DIFFERENT scenario and must NOT reach a RETRY of the failed one.
|
|
411
|
+
//
|
|
412
|
+
// `mochaOpts.retries` re-runs a failed scenario, and the retry is also "the next run". Escalating it
|
|
413
|
+
// would make attempt 2 reset harder than attempt 1, which breaks the guarantee this config is built
|
|
414
|
+
// on: race victims recover on attempt 2 while REAL failures fail both attempts. A dirty-state failure
|
|
415
|
+
// is a real failure — if the retry silently got a wiped data layer it would pass, the reporter takes
|
|
416
|
+
// the last attempt, and a scenario that only works after a wipe would be delivered as a clean
|
|
417
|
+
// `Passed` under a feature file still reading its weaker declared tier. That false pass is worse than
|
|
418
|
+
// the flakiness escalation was added to fix, so the retry keeps attempt 1's tier and fails honestly.
|
|
419
|
+
//
|
|
420
|
+
// Comparing titles rather than reading mocha's retry counter keeps this independent of the runner's
|
|
421
|
+
// API, and it fails in the SAFE direction: if a title is unavailable both sides compare equal, so no
|
|
422
|
+
// escalation happens and the invariant above still holds. (Two scenarios sharing one title in a file
|
|
423
|
+
// lose the escalation between them — SG-W024 already reports that shape when tiers are involved.)
|
|
424
|
+
let __escalateAfter: string | null = null;
|
|
425
|
+
|
|
426
|
+
// Registered BEFORE any user @afterEach block so a throwing user hook cannot skip the bookkeeping.
|
|
427
|
+
afterEach(async function () {
|
|
428
|
+
__escalateAfter = this.currentTest?.state === 'failed' ? (this.currentTest?.title ?? '') : null;
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
{{#if scenarioResetTiers}}
|
|
432
|
+
// Per-scenario `@reset:` overrides, keyed on the scenario's BASE name. Not on the rendered title:
|
|
433
|
+
// a @cases scenario builds its title at runtime (`name + ' — ' + row.__label`) and appium appends
|
|
434
|
+
// pass-through tags as a `[…]` suffix, so no compile-time key could match what mocha reports.
|
|
435
|
+
// __tierFor therefore strips those two suffixes back off — and MEMBERSHIP-TESTS at every step, so a
|
|
436
|
+
// name it cannot resolve returns 'none' (inherit the feature tier) instead of guessing a wrong one.
|
|
437
|
+
// Silently applying the wrong tier is the failure this whole feature exists to remove.
|
|
438
|
+
const __SCENARIO_TIERS: Record<string, string> = {
|
|
439
|
+
{{#each scenarioResetTiers}}
|
|
440
|
+
'{{escapeQuotes name}}': '{{tier}}',
|
|
441
|
+
{{/each}}
|
|
442
|
+
};
|
|
443
|
+
// Only a @cases scenario builds its it() title at RUNTIME (`name + ' \u2014 ' + row.__label`), so only
|
|
444
|
+
// these names may shed a trailing ` \u2014 <label>` during lookup. Stripping that from EVERY title let a
|
|
445
|
+
// scenario merely NAMED `<something> \u2014 <something>` resolve to a different scenario's tier.
|
|
446
|
+
const __CASES_NAMES = new Set<string>([{{#each scenarioResetTiers}}{{#if cases}}'{{escapeQuotes name}}', {{/if}}{{/each}}]);
|
|
447
|
+
function __tierFor(title: string): string {
|
|
448
|
+
if (Object.prototype.hasOwnProperty.call(__SCENARIO_TIERS, title)) return __SCENARIO_TIERS[title];
|
|
449
|
+
// `\[@` — must be a TAG group, not a trailing selector ref. Without the `@`, a scenario named
|
|
450
|
+
// `VP-001 tap [Save]` would reduce to `VP-001 tap` and could pick up a DIFFERENT scenario's tier.
|
|
451
|
+
const noTags = title.replace(/ \[@[^\]]*\]$/, '');
|
|
452
|
+
if (Object.prototype.hasOwnProperty.call(__SCENARIO_TIERS, noTags)) return __SCENARIO_TIERS[noTags];
|
|
453
|
+
// Shed row-label segments from the RIGHT, one at a time, accepting only a @cases name. Right-to-left
|
|
454
|
+
// and iterative so an em dash inside the scenario NAME or inside the row LABEL still resolves; every
|
|
455
|
+
// step is membership-tested against __CASES_NAMES, so this narrows toward a declared name and never
|
|
456
|
+
// guesses at one.
|
|
457
|
+
let base = noTags;
|
|
458
|
+
for (let i = base.lastIndexOf(' \u2014 '); i >= 0; i = base.lastIndexOf(' \u2014 ')) {
|
|
459
|
+
base = base.slice(0, i);
|
|
460
|
+
if (__CASES_NAMES.has(base)) return __SCENARIO_TIERS[base];
|
|
461
|
+
}
|
|
462
|
+
// Unresolvable ⟹ inherit the feature tier. Never a guess: a wrong tier applied silently is the
|
|
463
|
+
// failure this whole lookup exists to avoid.
|
|
464
|
+
return 'none';
|
|
465
|
+
}
|
|
466
|
+
{{/if}}
|
|
467
|
+
|
|
468
|
+
// Settle after a cold (re)launch: a Flutter app first renders a BLANK accessibility skeleton
|
|
469
|
+
// (nested FrameLayout/View, no content-desc) for several seconds before the real tree appears.
|
|
470
|
+
// So require rendered content — at least one non-empty content-desc/text — on two consecutive
|
|
471
|
+
// reads before proceeding. A generous timeout covers slow cold renders on a stressed emulator.
|
|
472
|
+
// Best-effort: a timeout never fails the scenario, it just falls through to the step's own
|
|
473
|
+
// auto-wait.
|
|
474
|
+
// "Settled" = rendered content present on TWO consecutive reads. Deliberately NOT full source
|
|
475
|
+
// equality: a UI with any looping animation (a rotating promo carousel is enough) never yields
|
|
476
|
+
// two identical snapshots, so an equality oracle starves and burns the whole timeout on every
|
|
477
|
+
// scenario. Two content-bearing reads still reject the blank first-frame skeleton (no
|
|
478
|
+
// content-desc/text at all) that a single read could false-positive on.
|
|
479
|
+
async function __settleUi(timeout: number): Promise<void> {
|
|
480
|
+
let __prevHadContent = false;
|
|
281
481
|
await driver
|
|
282
482
|
.waitUntil(
|
|
283
483
|
async () => {
|
|
284
484
|
const src = await driver.getPageSource();
|
|
285
485
|
const hasContent = /content-desc="[^"]+"|\btext="[^"]+"/.test(src);
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
return
|
|
486
|
+
const settled = hasContent && __prevHadContent;
|
|
487
|
+
__prevHadContent = hasContent;
|
|
488
|
+
return settled;
|
|
289
489
|
},
|
|
290
|
-
{ timeout
|
|
490
|
+
{ timeout, interval: 400, timeoutMsg: 'UI did not settle (no rendered content)' },
|
|
291
491
|
)
|
|
292
492
|
.catch(() => { /* best-effort — fall through to per-step auto-wait */ });
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Wipe the app's data layer. `mobile: clearApp` is a UiAutomator2 (Android) command — it maps to
|
|
496
|
+
// `pm clear`, which drops the local DB, cache, SharedPreferences and keychain entries in one call
|
|
497
|
+
// for ~2–5s. XCUITest has NO equivalent, so iOS degrades to a reinstall: slower, but the only way
|
|
498
|
+
// to reach the same layers. That needs a build to install from, hence the named throw.
|
|
499
|
+
async function __clearAppData(pkg: string): Promise<void> {
|
|
500
|
+
if (driver.isIOS) {
|
|
501
|
+
await __reinstallApp(pkg);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
await driver.execute('mobile: clearApp', { appId: pkg });
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
async function __reinstallApp(pkg: string): Promise<void> {
|
|
508
|
+
const caps = (driver.requestedCapabilities || {}) as Record<string, string>;
|
|
509
|
+
const appPath = caps['appium:app'] || caps['app'];
|
|
510
|
+
if (!appPath) {
|
|
511
|
+
throw new Error(
|
|
512
|
+
`reset tier needs the app's build file to reinstall — set APP_APK (Android) / IOS_APP (iOS) so the appium:app capability is present, or drop to @reset:soft.`,
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
try { await driver.removeApp(pkg); } catch { /* not installed */ }
|
|
516
|
+
await driver.installApp(appPath);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// One reset, at the given tier. `none` still restores the NATIVE context and portrait: both leak at
|
|
520
|
+
// OS/session level rather than app level, so skipping them alongside the relaunch would make `none`
|
|
521
|
+
// outright buggy instead of merely stateful. It skips the settle wait, though — nothing relaunched,
|
|
522
|
+
// so there is no blank first frame to wait through, and burning up to 30s per scenario would defeat
|
|
523
|
+
// the whole point of asking for `none`.
|
|
524
|
+
async function __resetApp(tier: string): Promise<void> {
|
|
525
|
+
const __APP_PKG__ = __resolveAppId();
|
|
526
|
+
if (tier !== 'none') {
|
|
527
|
+
if (tier === 'full') {
|
|
528
|
+
try { await driver.terminateApp(__APP_PKG__); } catch { /* app may not be running yet */ }
|
|
529
|
+
await __reinstallApp(__APP_PKG__);
|
|
530
|
+
} else {
|
|
531
|
+
try { await driver.terminateApp(__APP_PKG__); } catch { /* app may not be running yet */ }
|
|
532
|
+
if (tier === 'data') await __clearAppData(__APP_PKG__);
|
|
533
|
+
}
|
|
534
|
+
// Let the termination fully land before relaunching — activateApp racing a still-dying
|
|
535
|
+
// process can RESUME the old instance (prior scenario's scroll position and all) instead
|
|
536
|
+
// of cold-starting, which strands top-of-screen elements above the viewport.
|
|
537
|
+
await driver.pause(500);
|
|
538
|
+
await driver.activateApp(__APP_PKG__);
|
|
539
|
+
}
|
|
540
|
+
// Restore the NATIVE context — a prior scenario that entered a WebView (`switch to [X] frame`)
|
|
541
|
+
// without switching back would otherwise poison every following native find.
|
|
542
|
+
try { await driver.switchContext('NATIVE_APP'); } catch { /* single-context session */ }
|
|
543
|
+
// Restore portrait so a prior scenario's `rotate to landscape` can't leak into this one — the
|
|
544
|
+
// app relaunch above resets in-app state, but device orientation is OS-level and persists.
|
|
545
|
+
try { await driver.setOrientation('PORTRAIT'); } catch { /* orientation control not supported */ }
|
|
546
|
+
// A fresh install paints its first frame slower than a plain relaunch — same oracle, more rope.
|
|
547
|
+
if (tier !== 'none') await __settleUi(tier === 'full' || tier === 'data' ? 45000 : 30000);
|
|
548
|
+
}
|
|
549
|
+
beforeEach(async function () {
|
|
550
|
+
{{#if scenarioResetTiers}}
|
|
551
|
+
// A scenario tier RAISES the feature tier, never lowers it: a @reset:full feature must not be
|
|
552
|
+
// weakened by one scenario asking for less. Use a separate feature when you genuinely need that.
|
|
553
|
+
const __scenarioTier = __tierFor(this.currentTest?.title ?? '');
|
|
554
|
+
const __base = __TIER_ORDER.indexOf(__scenarioTier) > __TIER_ORDER.indexOf(__RUN_TIER) ? __scenarioTier : __RUN_TIER;
|
|
555
|
+
{{else}}
|
|
556
|
+
const __base = __RUN_TIER;
|
|
557
|
+
{{/if}}
|
|
558
|
+
// Same title ⟹ this is a retry of the scenario that just failed: keep its declared tier so a real
|
|
559
|
+
// failure fails both attempts (see __escalateAfter above).
|
|
560
|
+
const __escalate = __escalateAfter !== null && __escalateAfter !== (this.currentTest?.title ?? '');
|
|
561
|
+
const __tier = __escalate ? __ESCALATE[__base] : __base;
|
|
562
|
+
if (__tier !== __base) {
|
|
563
|
+
console.log(` \u2191 reset escalated ${__base} \u2192 ${__tier} (previous scenario failed: "${__escalateAfter}")`);
|
|
564
|
+
}
|
|
565
|
+
await __resetApp(__tier);
|
|
293
566
|
{{#if backgroundSteps}}
|
|
294
567
|
// Background runs here (inside beforeEach) — the app resets to its launch screen every scenario,
|
|
295
568
|
// so Background setup (nav, dismiss launch promos…) must re-run after each relaunch, not once.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TestGeneratorAdapter, TestFileData, ScenarioData } from '../adapter-interface';
|
|
1
|
+
import { TestGeneratorAdapter, TestFileData, ScenarioData, RenderImportsOptions } from '../adapter-interface';
|
|
2
2
|
/**
|
|
3
3
|
* PlaywrightAdapter
|
|
4
4
|
* Generates Playwright TypeScript test code using Handlebars templates
|
|
@@ -11,16 +11,7 @@ export declare class PlaywrightAdapter implements TestGeneratorAdapter {
|
|
|
11
11
|
constructor();
|
|
12
12
|
renderTestFile(data: TestFileData): string;
|
|
13
13
|
renderScenario(data: ScenarioData): string;
|
|
14
|
-
renderImports(options?:
|
|
15
|
-
runtimeData?: boolean;
|
|
16
|
-
basePath?: string;
|
|
17
|
-
isParallel?: boolean;
|
|
18
|
-
needsCleanupImport?: boolean;
|
|
19
|
-
needsDb?: boolean;
|
|
20
|
-
needsApi?: boolean;
|
|
21
|
-
needsMock?: boolean;
|
|
22
|
-
urlAssertImports?: string;
|
|
23
|
-
}): string;
|
|
14
|
+
renderImports(options?: RenderImportsOptions): string;
|
|
24
15
|
renderBeforeEach(data: {
|
|
25
16
|
steps: Array<{
|
|
26
17
|
comment?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playwright-adapter.d.ts","sourceRoot":"","sources":["../../../../../src/generators/test-generator/adapters/playwright/playwright-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"playwright-adapter.d.ts","sourceRoot":"","sources":["../../../../../src/generators/test-generator/adapters/playwright/playwright-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAG9G;;;GAGG;AACH,qBAAa,iBAAkB,YAAW,oBAAoB;IAC5D,QAAQ,CAAC,aAAa,gBAAgB;IACtC,QAAQ,CAAC,aAAa,cAAc;IACpC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,OAAO,CAAC,cAAc,CAAiB;;IAQvC,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM;IAI1C,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM;IAI1C,aAAa,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,MAAM;IAIrD,gBAAgB,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,KAAK,CAAC;YAAE,OAAO,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,GAAG,MAAM;IAIpF,eAAe,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,KAAK,CAAC;YAAE,OAAO,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,GAAG,MAAM;IAInF,eAAe,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,KAAK,CAAC;YAAE,OAAO,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,GAAG,MAAM;IAInF,cAAc,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,KAAK,CAAC;YAAE,OAAO,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,GAAG,MAAM;IAIlF,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,MAAM;CAGpD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playwright-adapter.js","sourceRoot":"","sources":["../../../../../src/generators/test-generator/adapters/playwright/playwright-adapter.ts"],"names":[],"mappings":";;;;;;AAAA,gDAAwB;AAExB,2DAAuD;AAEvD;;;GAGG;AACH,MAAa,iBAAiB;IAM5B;QALS,kBAAa,GAAG,YAAY,CAAC;QAC7B,kBAAa,GAAG,UAAU,CAAC;QAKlC,2CAA2C;QAC3C,IAAI,CAAC,YAAY,GAAG,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;QACtD,IAAI,CAAC,cAAc,GAAG,IAAI,gCAAc,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC9D,CAAC;IAED,cAAc,CAAC,IAAkB;QAC/B,OAAO,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IAClD,CAAC;IAED,cAAc,CAAC,IAAkB;QAC/B,OAAO,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IAClD,CAAC;IAED,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"playwright-adapter.js","sourceRoot":"","sources":["../../../../../src/generators/test-generator/adapters/playwright/playwright-adapter.ts"],"names":[],"mappings":";;;;;;AAAA,gDAAwB;AAExB,2DAAuD;AAEvD;;;GAGG;AACH,MAAa,iBAAiB;IAM5B;QALS,kBAAa,GAAG,YAAY,CAAC;QAC7B,kBAAa,GAAG,UAAU,CAAC;QAKlC,2CAA2C;QAC3C,IAAI,CAAC,YAAY,GAAG,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;QACtD,IAAI,CAAC,cAAc,GAAG,IAAI,gCAAc,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC9D,CAAC;IAED,cAAc,CAAC,IAAkB;QAC/B,OAAO,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IAClD,CAAC;IAED,cAAc,CAAC,IAAkB;QAC/B,OAAO,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IAClD,CAAC;IAED,aAAa,CAAC,OAA8B;QAC1C,OAAO,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IACpD,CAAC;IAED,gBAAgB,CAAC,IAA0D;QACzE,OAAO,IAAI,CAAC,cAAc,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACpD,CAAC;IAED,eAAe,CAAC,IAA0D;QACxE,OAAO,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IACnD,CAAC;IAED,eAAe,CAAC,IAA0D;QACxE,OAAO,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IACnD,CAAC;IAED,cAAc,CAAC,IAA0D;QACvE,OAAO,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IAClD,CAAC;IAED,UAAU,CAAC,YAAoB,EAAE,IAAS;QACxC,OAAO,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;IAC5D,CAAC;CACF;AA3CD,8CA2CC"}
|
|
@@ -15,6 +15,9 @@ import { mock } from '{{basePath}}/mock';
|
|
|
15
15
|
{{#if urlAssertImports}}
|
|
16
16
|
import { {{urlAssertImports}} } from '{{basePath}}/url-assert';
|
|
17
17
|
{{/if}}
|
|
18
|
+
{{#if regexEscapeImport}}
|
|
19
|
+
import { escapeRegExp } from '{{basePath}}/regex-escape';
|
|
20
|
+
{{/if}}
|
|
18
21
|
|
|
19
22
|
// This file is auto-generated from Gherkin feature files
|
|
20
23
|
// DO NOT EDIT MANUALLY - changes will be overwritten
|
package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs
CHANGED
|
@@ -1 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
{{#if probeNative}}
|
|
2
|
+
{{!-- The selector resolved to an ARIA role that a native <select> and a custom trigger share
|
|
3
|
+
(`combobox`/`listbox`). .selectOption() only works on a real <select>; on a Radix/shadcn or
|
|
4
|
+
MUI trigger it throws "Element is not a <select> element". Probe the tag, then take the
|
|
5
|
+
native path or the open-then-pick path. Pin `variant: native` (or bind a `select` recipe in
|
|
6
|
+
qa/app.yaml) to compile one branch only. --}}
|
|
7
|
+
{ const el = {{> locator}};
|
|
8
|
+
if ((await el.evaluate((n) => n.tagName.toLowerCase())) === 'select') {
|
|
9
|
+
await el.selectOption('{{selectValue}}');
|
|
10
|
+
} else {
|
|
11
|
+
await el.click();
|
|
12
|
+
await page.getByText('{{selectValue}}', { exact: true }).first().click();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
{{~else~}}
|
|
16
|
+
await {{> locator}}.selectOption('{{selectValue}}');
|
|
17
|
+
{{~/if}}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
-
{{
|
|
1
|
+
{{!-- `pattern` opts the value into REGEX semantics: the value IS the pattern, so it must
|
|
2
|
+
NOT be regex-escaped. A string container (not a /…/ literal) keeps it that way in
|
|
3
|
+
runtime-data mode — the transformer escapes values landing in regex LITERALS but
|
|
4
|
+
leaves a whole-marker string as a bare accessor call. escapeQuotes doubles
|
|
5
|
+
backslashes, so `\d` in a static pattern reaches RegExp intact. --}}
|
|
6
|
+
{{#if pattern}}await expect({{> locator}}).toHaveAttribute('{{attribute}}', new RegExp('{{escapeQuotes dataValue}}'));
|
|
2
7
|
{{else}}await expect({{> locator}}).toHaveAttribute('{{attribute}}', '{{dataValue}}');
|
|
3
8
|
{{/if}}
|
|
@@ -1 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
{{#if probeNative}}
|
|
2
|
+
{{!-- Ambiguous value target (dropdown/select/combobox): ARIA role `combobox` is worn both by a
|
|
3
|
+
native <select> and by a `<button role="combobox">` trigger (Radix/shadcn, MUI, Headless UI).
|
|
4
|
+
toHaveValue() reads the DOM `value` property — a button has none, so it throws "Not an input
|
|
5
|
+
element". Probe the tag and assert whatever the element actually exposes. Same runtime-probe
|
|
6
|
+
idiom as selected-assertion.hbs. Pin a real native control with `variant: native`, or bind an
|
|
7
|
+
`assertValue` recipe, to skip the probe entirely. --}}
|
|
8
|
+
{ const el = {{> locator}};
|
|
9
|
+
const tag = await el.evaluate((n) => n.tagName.toLowerCase());
|
|
10
|
+
if (tag === 'input' || tag === 'select' || tag === 'textarea') {
|
|
11
|
+
await expect(el).toHaveValue('{{escapeQuotes dataValue}}');
|
|
12
|
+
} else {
|
|
13
|
+
await expect(el).toHaveText('{{escapeQuotes dataValue}}');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
{{~else~}}
|
|
17
|
+
await expect({{> locator}}).toHaveValue('{{escapeQuotes dataValue}}');
|
|
18
|
+
{{~/if}}
|
|
@@ -1 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
{{!-- Same filter contract as the visible/contains siblings (has-text-value): substring by
|
|
2
|
+
default, `exact: true` in the selector YAML anchors. Divergent defaults let the pair
|
|
3
|
+
`see [X] with {{v}}` (substring, matches) + `… is hidden` (anchored, 0 elements)
|
|
4
|
+
BOTH pass on one element — a vacuous-pass contradiction. --}}
|
|
5
|
+
await expect({{> locator-base}}.filter({ hasText: {{> has-text-value dataValue=dataValue exact=exact}} }){{> locator-nth}}).toBeHidden();
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{{!-- Format assertion: the label must match the SHAPE the spec states, not a fixed value.
|
|
2
|
+
toHaveText accepts a RegExp and retries under the standard expect timeout, so a label that
|
|
3
|
+
renders late still passes. The message names the FORMAT (not the compiled regex) because the
|
|
4
|
+
failure is read by whoever wrote the requirement. --}}
|
|
5
|
+
await expect({{> locator}}, 'Expected [{{escapeQuotes selectorRef}}] to match the format "{{escapeQuotes format}}"').toHaveText({{{formatRegex}}});
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
{{!-- Filter contract shared with the visible/hidden/contains siblings (has-text-value):
|
|
2
|
+
substring by default, `exact: true` in the selector YAML anchors. --}}
|
|
1
3
|
{{#if selectedProbe}}
|
|
2
|
-
{ const el = {{> locator-base}}.filter({ hasText:
|
|
4
|
+
{ const el = {{> locator-base}}.filter({ hasText: {{> has-text-value dataValue=dataValue exact=exact}} }){{> locator-nth}};
|
|
3
5
|
if ((await el.getAttribute('aria-selected')) !== null) {
|
|
4
6
|
await expect(el).toHaveAttribute('aria-selected', 'true');
|
|
5
7
|
} else {
|
|
@@ -7,5 +9,5 @@ await expect(el).toBeChecked();
|
|
|
7
9
|
}
|
|
8
10
|
}
|
|
9
11
|
{{else}}
|
|
10
|
-
await expect({{> locator-base}}.filter({ hasText:
|
|
12
|
+
await expect({{> locator-base}}.filter({ hasText: {{> has-text-value dataValue=dataValue exact=exact}} }){{> locator-nth}}).{{{stateAssertion}}};
|
|
11
13
|
{{/if}}
|
|
@@ -1 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
{{!-- Filter contract shared with the see-assertion siblings (has-text-value): substring by
|
|
2
|
+
default, `exact: true` in the selector YAML anchors — `wait for [X] with {{v}}` and
|
|
3
|
+
`see [X] … with {{v}}` must match the same element for the same input. --}}
|
|
4
|
+
await {{> locator-base}}.filter({ hasText: {{> has-text-value dataValue=dataValue exact=exact}} }){{> locator-nth}}.waitFor({ state: '{{state}}' });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
await expect({{{targetExpr}}}).toContainText({{{valueExpr}}});
|
|
1
|
+
await expect({{{targetExpr}}}).{{#if exact}}toHaveText{{else}}toContainText{{/if}}({{{valueExpr}}});
|
|
@@ -33,6 +33,12 @@ export declare class CodeGenerator {
|
|
|
33
33
|
* Generate test file from parsed feature
|
|
34
34
|
*/
|
|
35
35
|
generateTest(feature: ParsedFeature, outputDir: string): Promise<GeneratedTest>;
|
|
36
|
+
/**
|
|
37
|
+
* Report SG-E022 when the emitted spec is not parseable. Reported, never thrown: the
|
|
38
|
+
* caller still writes the file, so the user keeps the artifact to inspect (and `--strict`
|
|
39
|
+
* turns it into a non-zero exit like every other diagnostic).
|
|
40
|
+
*/
|
|
41
|
+
private assertParseable;
|
|
36
42
|
/**
|
|
37
43
|
* Count total steps in feature (excluding background).
|
|
38
44
|
* For @extend scenarios, counts base steps + own steps (merged total).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"code-generator.d.ts","sourceRoot":"","sources":["../../../src/generators/test-generator/code-generator.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAA8B,MAAM,mBAAmB,CAAC;AAI9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"code-generator.d.ts","sourceRoot":"","sources":["../../../src/generators/test-generator/code-generator.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAA8B,MAAM,mBAAmB,CAAC;AAI9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAuOrD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAM;IAErB,OAAO,CAAC,eAAe,CAAc;IAErC,OAAO,CAAC,aAAa,CAAqC;IAE1D,OAAO,CAAC,WAAW,CAA8B;gBAErC,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAO;IAiBtK,oFAAoF;IACpF,cAAc,IAAI,oBAAoB;IAItC;;OAEG;IACG,YAAY,CAChB,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,aAAa,CAAC;IA4FzB;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAavB;;;OAGG;IACH,OAAO,CAAC,UAAU;IAclB;;OAEG;IACH,SAAS,CAAC,aAAa,EAAE,aAAa,GAAG,IAAI;IAS7C;;OAEG;IACH;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IA0C3B;;;;OAIG;IACH,OAAO,CAAC,uBAAuB;IAoB/B;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAY3B,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;YASzB,gBAAgB;YAmWhB,uBAAuB;YAYvB,kBAAkB;YAoBlB,YAAY;IAqB1B;;;;;OAKG;IACH,OAAO,CAAC,wBAAwB;YASlB,gBAAgB;IAoJ9B;;OAEG;IACH,OAAO,CAAC,UAAU;IAKlB;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAS7B;;OAEG;IACH;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,kBAAkB;IAyBpB,gBAAgB,CACpB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EAAE,GACrB,OAAO,CAAC,aAAa,EAAE,CAAC;CA+D5B"}
|