@sun-asterisk/sungen 3.2.31 → 3.2.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/audit.d.ts +1 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +13 -2
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/capability.d.ts.map +1 -1
- package/dist/cli/commands/capability.js +46 -17
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +39 -18
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +14 -7
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +2 -0
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/results-merge.d.ts.map +1 -1
- package/dist/cli/commands/results-merge.js +2 -12
- package/dist/cli/commands/results-merge.js.map +1 -1
- package/dist/cli/commands/results-sanitize.d.ts.map +1 -1
- package/dist/cli/commands/results-sanitize.js +2 -13
- package/dist/cli/commands/results-sanitize.js.map +1 -1
- package/dist/cli/commands/setup-mobile-env.d.ts +15 -0
- package/dist/cli/commands/setup-mobile-env.d.ts.map +1 -0
- package/dist/cli/commands/setup-mobile-env.js +65 -0
- package/dist/cli/commands/setup-mobile-env.js.map +1 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
- package/dist/dashboard/snapshot-builder.js +6 -10
- package/dist/dashboard/snapshot-builder.js.map +1 -1
- package/dist/exporters/feature-parser.d.ts +21 -0
- package/dist/exporters/feature-parser.d.ts.map +1 -1
- package/dist/exporters/feature-parser.js +32 -1
- package/dist/exporters/feature-parser.js.map +1 -1
- package/dist/exporters/locale-variants.d.ts +7 -1
- package/dist/exporters/locale-variants.d.ts.map +1 -1
- package/dist/exporters/locale-variants.js +23 -16
- package/dist/exporters/locale-variants.js.map +1 -1
- package/dist/exporters/matrix/build.d.ts.map +1 -1
- package/dist/exporters/matrix/build.js +13 -7
- package/dist/exporters/matrix/build.js.map +1 -1
- package/dist/exporters/matrix/carry-manual-results.d.ts +70 -0
- package/dist/exporters/matrix/carry-manual-results.d.ts.map +1 -0
- package/dist/exporters/matrix/carry-manual-results.js +232 -0
- package/dist/exporters/matrix/carry-manual-results.js.map +1 -0
- package/dist/exporters/matrix/export.d.ts +2 -0
- package/dist/exporters/matrix/export.d.ts.map +1 -1
- package/dist/exporters/matrix/export.js +7 -1
- package/dist/exporters/matrix/export.js.map +1 -1
- package/dist/exporters/matrix/wording.d.ts +7 -0
- package/dist/exporters/matrix/wording.d.ts.map +1 -1
- package/dist/exporters/matrix/wording.js +27 -1
- package/dist/exporters/matrix/wording.js.map +1 -1
- package/dist/exporters/xlsx-exporter.d.ts +2 -0
- package/dist/exporters/xlsx-exporter.d.ts.map +1 -1
- package/dist/exporters/xlsx-exporter.js +7 -2
- package/dist/exporters/xlsx-exporter.js.map +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/reinstall-app-action.hbs +2 -2
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-assertion.hbs +1 -2
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-element.hbs +8 -5
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/count-assertion.hbs +5 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-filter-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-filter-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +8 -2
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/list-item-count-assertion.hbs +5 -2
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/state-with-filter-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-with-role-variable-assertion.hbs +7 -3
- package/dist/generators/test-generator/adapters/appium/templates/steps/partials/await-elements.hbs +12 -0
- package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +94 -4
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/capture-variable.hbs +7 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/label-value-assertion.hbs +7 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/row-scoped-column-assertion.hbs +23 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +8 -4
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +4 -4
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/default.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/id.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +4 -4
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +17 -2
- package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +14 -0
- package/dist/generators/test-generator/code-generator.d.ts +2 -1
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +122 -15
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/diagnostics.d.ts +70 -4
- package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
- package/dist/generators/test-generator/diagnostics.js +69 -3
- package/dist/generators/test-generator/diagnostics.js.map +1 -1
- package/dist/generators/test-generator/patterns/index.d.ts +13 -3
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +16 -10
- package/dist/generators/test-generator/patterns/index.js.map +1 -1
- package/dist/generators/test-generator/patterns/types.d.ts +15 -0
- package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/with-parent-scope.d.ts +5 -0
- package/dist/generators/test-generator/patterns/with-parent-scope.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/with-parent-scope.js +6 -0
- package/dist/generators/test-generator/patterns/with-parent-scope.js.map +1 -1
- package/dist/generators/test-generator/step-mapper.d.ts +81 -0
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +187 -13
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
- package/dist/generators/test-generator/template-engine.js +18 -0
- package/dist/generators/test-generator/template-engine.js.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.d.ts +25 -0
- package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.js +39 -8
- package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-entry-fields.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-entry-fields.js +1 -0
- package/dist/generators/test-generator/utils/selector-entry-fields.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts +84 -4
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +229 -82
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +35 -8
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/capability.d.ts +7 -1
- package/dist/harness/capability.d.ts.map +1 -1
- package/dist/harness/capability.js +15 -12
- package/dist/harness/capability.js.map +1 -1
- package/dist/harness/catalog/universal-viewpoints.yaml +5 -5
- package/dist/harness/config-file.d.ts +17 -0
- package/dist/harness/config-file.d.ts.map +1 -0
- package/dist/harness/config-file.js +100 -0
- package/dist/harness/config-file.js.map +1 -0
- package/dist/harness/depth-lint.d.ts.map +1 -1
- package/dist/harness/depth-lint.js +2 -1
- package/dist/harness/depth-lint.js.map +1 -1
- package/dist/harness/flow-check.d.ts.map +1 -1
- package/dist/harness/flow-check.js +6 -2
- package/dist/harness/flow-check.js.map +1 -1
- package/dist/harness/keyword-match.d.ts +59 -0
- package/dist/harness/keyword-match.d.ts.map +1 -0
- package/dist/harness/keyword-match.js +86 -0
- package/dist/harness/keyword-match.js.map +1 -0
- package/dist/harness/next-step.d.ts.map +1 -1
- package/dist/harness/next-step.js +2 -1
- package/dist/harness/next-step.js.map +1 -1
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +42 -7
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/perf.d.ts +7 -1
- package/dist/harness/perf.d.ts.map +1 -1
- package/dist/harness/perf.js +13 -10
- package/dist/harness/perf.js.map +1 -1
- package/dist/harness/quality-gates.d.ts +9 -2
- package/dist/harness/quality-gates.d.ts.map +1 -1
- package/dist/harness/quality-gates.js +16 -4
- package/dist/harness/quality-gates.js.map +1 -1
- package/dist/harness/query-catalog.js +0 -0
- package/dist/harness/query-catalog.js.map +1 -1
- package/dist/harness/read-text.d.ts +6 -0
- package/dist/harness/read-text.d.ts.map +1 -1
- package/dist/harness/read-text.js +7 -1
- package/dist/harness/read-text.js.map +1 -1
- package/dist/harness/secret-scan.d.ts +27 -1
- package/dist/harness/secret-scan.d.ts.map +1 -1
- package/dist/harness/secret-scan.js +57 -4
- package/dist/harness/secret-scan.js.map +1 -1
- package/dist/harness/sensors.d.ts +12 -0
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +41 -18
- package/dist/harness/sensors.js.map +1 -1
- package/dist/harness/spec-branches.d.ts +7 -20
- package/dist/harness/spec-branches.d.ts.map +1 -1
- package/dist/harness/spec-branches.js +85 -13
- package/dist/harness/spec-branches.js.map +1 -1
- package/dist/harness/spec-coverage.d.ts +16 -0
- package/dist/harness/spec-coverage.d.ts.map +1 -1
- package/dist/harness/spec-coverage.js +131 -12
- package/dist/harness/spec-coverage.js.map +1 -1
- package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
- package/dist/harness/viewpoint-ledger.js +2 -1
- package/dist/harness/viewpoint-ledger.js.map +1 -1
- package/dist/harness/write-atomic.d.ts +2 -0
- package/dist/harness/write-atomic.d.ts.map +1 -0
- package/dist/harness/write-atomic.js +62 -0
- package/dist/harness/write-atomic.js.map +1 -0
- package/dist/orchestrator/ai-skills/writer.d.ts.map +1 -1
- package/dist/orchestrator/ai-skills/writer.js +15 -7
- package/dist/orchestrator/ai-skills/writer.js.map +1 -1
- package/dist/orchestrator/appium-environment-setup.d.ts +24 -0
- package/dist/orchestrator/appium-environment-setup.d.ts.map +1 -0
- package/dist/orchestrator/appium-environment-setup.js +157 -0
- package/dist/orchestrator/appium-environment-setup.js.map +1 -0
- package/dist/orchestrator/assets-drift.d.ts.map +1 -1
- package/dist/orchestrator/assets-drift.js +11 -6
- package/dist/orchestrator/assets-drift.js.map +1 -1
- package/dist/orchestrator/mobile-runtime-scaffolder.d.ts +2 -0
- package/dist/orchestrator/mobile-runtime-scaffolder.d.ts.map +1 -1
- package/dist/orchestrator/mobile-runtime-scaffolder.js.map +1 -1
- package/dist/orchestrator/project-deps-sync.d.ts.map +1 -1
- package/dist/orchestrator/project-deps-sync.js +22 -1
- package/dist/orchestrator/project-deps-sync.js.map +1 -1
- package/dist/orchestrator/project-initializer.d.ts +9 -0
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +63 -7
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/security-overrides-catalog.d.ts +39 -0
- package/dist/orchestrator/security-overrides-catalog.d.ts.map +1 -0
- package/dist/orchestrator/security-overrides-catalog.js +63 -0
- package/dist/orchestrator/security-overrides-catalog.js.map +1 -0
- package/dist/orchestrator/security-overrides.d.ts +51 -0
- package/dist/orchestrator/security-overrides.d.ts.map +1 -0
- package/dist/orchestrator/security-overrides.js +165 -0
- package/dist/orchestrator/security-overrides.js.map +1 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +8 -2
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +64 -2
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +13 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +20 -5
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +3 -3
- package/dist/orchestrator/templates/appium-environment/README.md +83 -0
- package/dist/orchestrator/templates/appium-environment/scripts/common/install-appium.ps1 +44 -0
- package/dist/orchestrator/templates/appium-environment/scripts/common/install-appium.sh +50 -0
- package/dist/orchestrator/templates/appium-environment/scripts/common/lib.ps1 +108 -0
- package/dist/orchestrator/templates/appium-environment/scripts/common/lib.sh +141 -0
- package/dist/orchestrator/templates/appium-environment/scripts/common/verify-appium.ps1 +39 -0
- package/dist/orchestrator/templates/appium-environment/scripts/common/verify-appium.sh +43 -0
- package/dist/orchestrator/templates/appium-environment/scripts/common/verify-node.ps1 +13 -0
- package/dist/orchestrator/templates/appium-environment/scripts/common/verify-node.sh +16 -0
- package/dist/orchestrator/templates/appium-environment/scripts/macos/install-android.sh +110 -0
- package/dist/orchestrator/templates/appium-environment/scripts/macos/install-ios.sh +32 -0
- package/dist/orchestrator/templates/appium-environment/scripts/macos/verify-android.sh +32 -0
- package/dist/orchestrator/templates/appium-environment/scripts/macos/verify-ios.sh +15 -0
- package/dist/orchestrator/templates/appium-environment/scripts/windows/install-android.ps1 +153 -0
- package/dist/orchestrator/templates/appium-environment/scripts/windows/verify-android.ps1 +26 -0
- package/dist/orchestrator/templates/appium-environment/setup.ps1 +58 -0
- package/dist/orchestrator/templates/appium-environment/setup.sh +88 -0
- package/dist/orchestrator/templates/specs-checked-state.d.ts +1 -1
- package/dist/orchestrator/templates/specs-checked-state.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-checked-state.js +26 -3
- package/dist/orchestrator/templates/specs-checked-state.js.map +1 -1
- package/dist/orchestrator/templates/specs-checked-state.ts +31 -6
- package/dist/orchestrator/templates/specs-locale-fixture.ts +21 -9
- package/dist/orchestrator/templates/specs-mock.d.ts +2 -1
- package/dist/orchestrator/templates/specs-mock.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-mock.js +10 -3
- package/dist/orchestrator/templates/specs-mock.js.map +1 -1
- package/dist/orchestrator/templates/specs-mock.ts +12 -4
- package/dist/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
- package/dist/orchestrator/templates/specs-url-assert.d.ts +17 -1
- package/dist/orchestrator/templates/specs-url-assert.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-url-assert.js +14 -2
- package/dist/orchestrator/templates/specs-url-assert.js.map +1 -1
- package/dist/orchestrator/templates/specs-url-assert.ts +24 -2
- package/dist/orchestrator/templates/wdio.conf.ts +24 -1
- package/package.json +3 -3
- package/src/cli/commands/audit.ts +12 -3
- package/src/cli/commands/capability.ts +63 -19
- package/src/cli/commands/delivery.ts +38 -16
- package/src/cli/commands/generate.ts +15 -7
- package/src/cli/commands/init.ts +3 -0
- package/src/cli/commands/results-merge.ts +2 -8
- package/src/cli/commands/results-sanitize.ts +2 -9
- package/src/cli/commands/setup-mobile-env.ts +64 -0
- package/src/cli/index.ts +2 -0
- package/src/dashboard/snapshot-builder.ts +6 -8
- package/src/exporters/feature-parser.ts +32 -2
- package/src/exporters/locale-variants.ts +23 -17
- package/src/exporters/matrix/build.ts +14 -8
- package/src/exporters/matrix/carry-manual-results.ts +229 -0
- package/src/exporters/matrix/export.ts +10 -4
- package/src/exporters/matrix/wording.ts +26 -1
- package/src/exporters/xlsx-exporter.ts +7 -2
- package/src/generators/test-generator/adapters/adapter-interface.ts +2 -2
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/reinstall-app-action.hbs +2 -2
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-assertion.hbs +1 -2
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-element.hbs +8 -5
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/count-assertion.hbs +5 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-filter-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-filter-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +8 -2
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/list-item-count-assertion.hbs +5 -2
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/state-with-filter-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-with-role-variable-assertion.hbs +7 -3
- package/src/generators/test-generator/adapters/appium/templates/steps/partials/await-elements.hbs +12 -0
- package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +94 -4
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/capture-variable.hbs +7 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/label-value-assertion.hbs +7 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/row-scoped-column-assertion.hbs +23 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +8 -4
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +4 -4
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/default.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/id.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +4 -4
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +17 -2
- package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +14 -0
- package/src/generators/test-generator/code-generator.ts +119 -19
- package/src/generators/test-generator/diagnostics.ts +70 -4
- package/src/generators/test-generator/patterns/index.ts +16 -9
- package/src/generators/test-generator/patterns/types.ts +15 -0
- package/src/generators/test-generator/patterns/with-parent-scope.ts +6 -0
- package/src/generators/test-generator/step-mapper.ts +196 -13
- package/src/generators/test-generator/template-engine.ts +18 -0
- package/src/generators/test-generator/utils/data-resolver.ts +41 -12
- package/src/generators/test-generator/utils/selector-entry-fields.ts +1 -0
- package/src/generators/test-generator/utils/selector-resolver.ts +256 -85
- package/src/harness/audit.ts +41 -12
- package/src/harness/capability.ts +23 -12
- package/src/harness/catalog/universal-viewpoints.yaml +5 -5
- package/src/harness/config-file.ts +66 -0
- package/src/harness/depth-lint.ts +2 -1
- package/src/harness/flow-check.ts +6 -2
- package/src/harness/keyword-match.ts +85 -0
- package/src/harness/next-step.ts +2 -1
- package/src/harness/parse.ts +42 -7
- package/src/harness/perf.ts +15 -7
- package/src/harness/quality-gates.ts +16 -4
- package/src/harness/query-catalog.ts +0 -0
- package/src/harness/read-text.ts +7 -1
- package/src/harness/secret-scan.ts +60 -4
- package/src/harness/sensors.ts +50 -19
- package/src/harness/spec-branches.ts +72 -14
- package/src/harness/spec-coverage.ts +133 -13
- package/src/harness/viewpoint-ledger.ts +2 -1
- package/src/harness/write-atomic.ts +22 -0
- package/src/orchestrator/ai-skills/writer.ts +18 -3
- package/src/orchestrator/appium-environment-setup.ts +127 -0
- package/src/orchestrator/assets-drift.ts +10 -5
- package/src/orchestrator/mobile-runtime-scaffolder.ts +2 -0
- package/src/orchestrator/project-deps-sync.ts +23 -1
- package/src/orchestrator/project-initializer.ts +69 -7
- package/src/orchestrator/security-overrides-catalog.ts +69 -0
- package/src/orchestrator/security-overrides.ts +149 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +8 -2
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +64 -2
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +13 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +20 -5
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +3 -3
- package/src/orchestrator/templates/appium-environment/README.md +83 -0
- package/src/orchestrator/templates/appium-environment/scripts/common/install-appium.ps1 +44 -0
- package/src/orchestrator/templates/appium-environment/scripts/common/install-appium.sh +50 -0
- package/src/orchestrator/templates/appium-environment/scripts/common/lib.ps1 +108 -0
- package/src/orchestrator/templates/appium-environment/scripts/common/lib.sh +141 -0
- package/src/orchestrator/templates/appium-environment/scripts/common/verify-appium.ps1 +39 -0
- package/src/orchestrator/templates/appium-environment/scripts/common/verify-appium.sh +43 -0
- package/src/orchestrator/templates/appium-environment/scripts/common/verify-node.ps1 +13 -0
- package/src/orchestrator/templates/appium-environment/scripts/common/verify-node.sh +16 -0
- package/src/orchestrator/templates/appium-environment/scripts/macos/install-android.sh +110 -0
- package/src/orchestrator/templates/appium-environment/scripts/macos/install-ios.sh +32 -0
- package/src/orchestrator/templates/appium-environment/scripts/macos/verify-android.sh +32 -0
- package/src/orchestrator/templates/appium-environment/scripts/macos/verify-ios.sh +15 -0
- package/src/orchestrator/templates/appium-environment/scripts/windows/install-android.ps1 +153 -0
- package/src/orchestrator/templates/appium-environment/scripts/windows/verify-android.ps1 +26 -0
- package/src/orchestrator/templates/appium-environment/setup.ps1 +58 -0
- package/src/orchestrator/templates/appium-environment/setup.sh +88 -0
- package/src/orchestrator/templates/specs-checked-state.ts +31 -6
- package/src/orchestrator/templates/specs-locale-fixture.ts +21 -9
- package/src/orchestrator/templates/specs-mock.ts +12 -4
- package/src/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
- package/src/orchestrator/templates/specs-url-assert.ts +24 -2
- package/src/orchestrator/templates/wdio.conf.ts +24 -1
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
{
|
|
8
8
|
const __expected = String('{{escapeQuotes dataValue}}').trim();
|
|
9
9
|
const __norm = (s: string) => s.replace(/\s+/g, ' ').trim().toLowerCase();
|
|
10
|
-
const __items =
|
|
10
|
+
const __items = {{> await-elements mustExist=true}};
|
|
11
11
|
let __found = false;
|
|
12
12
|
for (const __el of __items) {
|
|
13
13
|
const __sources = driver.isIOS
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
{{!-- Role variable, visible (mobile best-effort): ARIA role has no native-mobile analog — reuse
|
|
2
2
|
the role→accessibility-id mapping in appium-selector.hbs (`~name`) when a static name
|
|
3
3
|
exists; the resolver already folds dataValue into name when there is none, so this is only
|
|
4
|
-
the fallback for a caller that omits both.
|
|
4
|
+
the fallback for a caller that omits both.
|
|
5
|
+
|
|
6
|
+
SCOPE (#747): an `in [Parent] <type>` clause chains this under the parent element. Both
|
|
7
|
+
branches locate by accessibility id / predicate, neither of which carries the absolute-xpath
|
|
8
|
+
trap the label-value twin has to work around. --}}
|
|
5
9
|
{{#if name}}
|
|
6
|
-
await expect($('~{{escapeQuotes name}}')).toBeDisplayed();
|
|
10
|
+
await expect({{#if parentLocator}}{{parentLocator}}.$('~{{escapeQuotes name}}'){{else}}$('~{{escapeQuotes name}}'){{/if}}).toBeDisplayed();
|
|
7
11
|
{{else}}
|
|
8
12
|
{
|
|
9
13
|
const __expected = String('{{escapeQuotes dataValue}}').trim();
|
|
10
14
|
const __sel = driver.isIOS ? `-ios predicate string:label == "${__expected}" OR value == "${__expected}"` : `android=new UiSelector().text("${__expected}")`;
|
|
11
|
-
await expect($(__sel)).toBeDisplayed();
|
|
15
|
+
await expect({{#if parentLocator}}{{parentLocator}}.$(__sel){{else}}$(__sel){{/if}}).toBeDisplayed();
|
|
12
16
|
}
|
|
13
17
|
{{/if}}
|
package/src/generators/test-generator/adapters/appium/templates/steps/partials/await-elements.hbs
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{{~!--
|
|
2
|
+
The settled read behind every collection assertion on this adapter. `await $$(sel)` is a one-shot
|
|
3
|
+
read, so a loop over its result asserts nothing at all when the list has not painted yet — the
|
|
4
|
+
false-green class #672 closed on the Playwright side and left here.
|
|
5
|
+
|
|
6
|
+
Hash params:
|
|
7
|
+
mustExist the assertion is meaningless over an empty set (positive assertions). Omitted means
|
|
8
|
+
emptiness is a legitimate verdict (the negated `is hidden` forms).
|
|
9
|
+
useRoleName the two role-variable templates search by accessibility id built from `name:`
|
|
10
|
+
rather than through the selector entry.
|
|
11
|
+
--~}}
|
|
12
|
+
await __settleElements({{#if useRoleName}}'~{{escapeQuotes name}}'{{else}}{{> appium-selector-expr}}{{/if}}, {{#if mustExist}}true{{else}}false{{/if}}, '{{escapeQuotes selectorRef}}')
|
|
@@ -119,6 +119,32 @@ async function __ensureVisible(selector: string): Promise<WebdriverIO.Element> {
|
|
|
119
119
|
return $(selector);
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
// Read a live element ARRAY after giving the tree a chance to render.
|
|
123
|
+
//
|
|
124
|
+
// `await $$(sel)` is a ONE-SHOT read — unlike a Playwright Locator it does not re-query, so an
|
|
125
|
+
// assertion that loops over the result silently asserts NOTHING when the list has not painted yet:
|
|
126
|
+
// zero matches, zero iterations, green. #672 fixed that class on the Playwright side and left the
|
|
127
|
+
// appium adapter untouched, so every collection assertion here still raced the first render.
|
|
128
|
+
//
|
|
129
|
+
// `mustExist` says which verdict emptiness is. A positive assertion ("all of them contain X",
|
|
130
|
+
// "every match is disabled") is meaningless over an empty set, so it waits the full budget and
|
|
131
|
+
// then fails naming the reference. A negated one ("none of them is displayed") is legitimately
|
|
132
|
+
// satisfied by absence, so it only waits long enough to outlast a render race and then proceeds
|
|
133
|
+
// with whatever it found — waiting the full budget there would add 15s to every correct `is
|
|
134
|
+
// hidden` step, which is a worse trade than the race it would close.
|
|
135
|
+
async function __settleElements(
|
|
136
|
+
selector: string, mustExist: boolean, ref: string,
|
|
137
|
+
): Promise<WebdriverIO.Element[]> {
|
|
138
|
+
try {
|
|
139
|
+
await (await $(selector)).waitForExist({ timeout: mustExist ? 15000 : 3000 });
|
|
140
|
+
} catch {
|
|
141
|
+
if (mustExist) {
|
|
142
|
+
throw new Error(`[sungen] Expected at least one ${ref ? `[${ref}]` : `element matching ${selector}`} — none appeared within 15s`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return $$(selector);
|
|
146
|
+
}
|
|
147
|
+
|
|
122
148
|
// Visibility oracle for simple-case `see [X]` assertions. Android: WDIO's toBeDisplayed()
|
|
123
149
|
// (reliable on UiAutomator2). iOS: XCUITest's `visible` attribute false-negatives on real,
|
|
124
150
|
// on-screen elements (snapshot artifact — seen live even on a screen title every prior run
|
|
@@ -476,9 +502,36 @@ describe('{{featureName}}', () => {
|
|
|
476
502
|
// lose the escalation between them — SG-W024 already reports that shape when tiers are involved.)
|
|
477
503
|
let __escalateAfter: string | null = null;
|
|
478
504
|
|
|
505
|
+
// `@known-defect` marks a scenario that is EXPECTED to be red — it asserts a live defect. The
|
|
506
|
+
// scaffolded /sungen:run-test command tells authors exactly that ("expected-red scenarios stay red;
|
|
507
|
+
// never 'fix' them"), so the ladder honours it here: such a failure is the suite working as
|
|
508
|
+
// designed, not evidence of leftover state. Arming escalation off it would make the NEXT scenario
|
|
509
|
+
// pay a harder reset for nothing — on every run, forever, because the defect is known.
|
|
510
|
+
//
|
|
511
|
+
// Read off the TITLE because `@known-defect` is a pass-through tag: appium appends it there as the
|
|
512
|
+
// `[@a @b]` suffix (which is what keeps it greppable via `--grep`), and that is how the field
|
|
513
|
+
// report shows it. Anchored to that trailing group, so a scenario merely NAMED `… known-defect …`
|
|
514
|
+
// cannot exempt itself — the same reason __tierFor refuses to strip a trailing `[Selector]` ref.
|
|
515
|
+
//
|
|
516
|
+
// Matched as a whole TAG TOKEN, never as a substring. `@known-defect-list` is a DIFFERENT tag, and
|
|
517
|
+
// reading it as this one would silently stop the next scenario's real dirty-state failure from
|
|
518
|
+
// getting the escalated reset it needs — with no log line at all, because "never armed" is not the
|
|
519
|
+
// "capped" path and prints nothing. The `:<ref>` parameter form is accepted because that is what
|
|
520
|
+
// the rest of the vocabulary does (`@manual:M10`, `@reset:data`).
|
|
521
|
+
//
|
|
522
|
+
// Caveat, inherited rather than introduced: a @cases scenario builds its title at runtime as
|
|
523
|
+
// `name — <row label>`, so a row LABEL that itself ends in `[@…]` would be read as the tag group.
|
|
524
|
+
// Same trailing-bracket ambiguity __tierFor accepts for a `[Selector]` ref.
|
|
525
|
+
function __isExpectedFail(title: string): boolean {
|
|
526
|
+
const tagGroup = title.match(/ \[@[^\]]*\]$/);
|
|
527
|
+
if (!tagGroup) return false;
|
|
528
|
+
return tagGroup[0].slice(2, -1).split(/\s+/)
|
|
529
|
+
.some((t) => t === '@known-defect' || t.startsWith('@known-defect:'));
|
|
530
|
+
}
|
|
531
|
+
|
|
479
532
|
// Registered BEFORE any user @afterEach block so a throwing user hook cannot skip the bookkeeping.
|
|
480
533
|
afterEach(async function () {
|
|
481
|
-
__escalateAfter = this.currentTest?.state === 'failed' ? (this.currentTest?.title ?? '') : null;
|
|
534
|
+
__escalateAfter = this.currentTest?.state === 'failed' && !__isExpectedFail(this.currentTest?.title ?? '') ? (this.currentTest?.title ?? '') : null;
|
|
482
535
|
});
|
|
483
536
|
|
|
484
537
|
{{#if scenarioResetTiers}}
|
|
@@ -557,9 +610,31 @@ describe('{{featureName}}', () => {
|
|
|
557
610
|
await driver.execute('mobile: clearApp', { appId: pkg });
|
|
558
611
|
}
|
|
559
612
|
|
|
560
|
-
|
|
613
|
+
/** The app's build file, when the session was given one. Absent when the app was attached BY PACKAGE. */
|
|
614
|
+
function __appBuildPath(): string | undefined {
|
|
561
615
|
const caps = (driver.requestedCapabilities || {}) as Record<string, string>;
|
|
562
|
-
|
|
616
|
+
return caps['appium:app'] || caps['app'];
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* The strongest tier THIS SESSION can actually carry out.
|
|
621
|
+
*
|
|
622
|
+
* Reinstalling needs the app's build file, and `data` on iOS IS a reinstall (no `mobile: clearApp`
|
|
623
|
+
* outside UiAutomator2). Attaching the app by package — the common local setup — leaves no build
|
|
624
|
+
* file, so those tiers cannot run at all.
|
|
625
|
+
*
|
|
626
|
+
* This exists so ESCALATION can cap itself. Escalation is a recovery the runner CHOSE; nothing
|
|
627
|
+
* asked for it. Escalating into a tier that throws turns one failed scenario into a dead spec file,
|
|
628
|
+
* because the throw lands in `beforeEach` and mocha then skips every remaining test in the file.
|
|
629
|
+
* A DECLARED tier is never capped — that is an unmet contract and must still throw by name.
|
|
630
|
+
*/
|
|
631
|
+
function __performableTier(): string {
|
|
632
|
+
if (__appBuildPath()) return 'full';
|
|
633
|
+
return driver.isIOS ? 'soft' : 'data';
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
async function __reinstallApp(pkg: string): Promise<void> {
|
|
637
|
+
const appPath = __appBuildPath();
|
|
563
638
|
if (!appPath) {
|
|
564
639
|
throw new Error(
|
|
565
640
|
`reset tier needs the app's build file to reinstall — set APP_APK (Android) / IOS_APP (iOS) so the appium:app capability is present, or drop to @reset:soft.`,
|
|
@@ -611,9 +686,24 @@ describe('{{featureName}}', () => {
|
|
|
611
686
|
// Same title ⟹ this is a retry of the scenario that just failed: keep its declared tier so a real
|
|
612
687
|
// failure fails both attempts (see __escalateAfter above).
|
|
613
688
|
const __escalate = __escalateAfter !== null && __escalateAfter !== (this.currentTest?.title ?? '');
|
|
614
|
-
|
|
689
|
+
// Escalation caps at what this session can perform, with the DECLARED tier as the FLOOR.
|
|
690
|
+
// The cap stops a tier NOBODY ASKED FOR from throwing in this hook and taking the rest of the
|
|
691
|
+
// spec file with it. The floor stops the cap from silently WEAKENING what the feature declared:
|
|
692
|
+
// a declared tier this session cannot perform still reaches __resetApp and still throws by name,
|
|
693
|
+
// because that one IS an unmet contract.
|
|
694
|
+
const __want = __escalate ? __ESCALATE[__base] : __base;
|
|
695
|
+
const __tier = __escalate
|
|
696
|
+
? __TIER_ORDER[Math.max(
|
|
697
|
+
__TIER_ORDER.indexOf(__base),
|
|
698
|
+
Math.min(__TIER_ORDER.indexOf(__want), __TIER_ORDER.indexOf(__performableTier())),
|
|
699
|
+
)]
|
|
700
|
+
: __want;
|
|
615
701
|
if (__tier !== __base) {
|
|
616
702
|
console.log(` \u2191 reset escalated ${__base} \u2192 ${__tier} (previous scenario failed: "${__escalateAfter}")`);
|
|
703
|
+
} else if (__want !== __base) {
|
|
704
|
+
// Said per occurrence, not once per run: this names the scenario that did NOT get the stronger
|
|
705
|
+
// reset it was owed, which is a fact about THIS scenario, not just about the session.
|
|
706
|
+
console.warn(` ! reset could not escalate ${__base} \u2192 ${__want} — this session has no app build file to reinstall from (the app was attached by package). Staying at ${__base}; set APP_APK (Android) / IOS_APP (iOS) to enable the stronger reset.`);
|
|
617
707
|
}
|
|
618
708
|
await __resetApp(__tier);
|
|
619
709
|
{{#if backgroundSteps}}
|
|
@@ -1 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
{{!-- `.first()` unless the entry pinned an index. A capture reads ONE element; without it,
|
|
2
|
+
Playwright's strict mode turned any selector matching more than one node into a hard failure
|
|
3
|
+
naming the count rather than the step — and the documented workaround was to add `nth: 0`,
|
|
4
|
+
i.e. to ask for exactly this. `{{> locator}}` already appends `.nth(n)` when `nth:` is
|
|
5
|
+
declared, so the two never stack. The compiler warns (SG-W036) when the selector is a SHAPE
|
|
6
|
+
that routinely matches a list, so "the first one" stays a decision rather than an accident. --}}
|
|
7
|
+
testData.set('{{varName}}', ((await {{> locator}}{{#unless (isDefined nth)}}.first(){{/unless}}.{{capture}}()) ?? '').trim());
|
|
@@ -1 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
{{!-- Caption+value read: the ref names on-screen TEXT ("学科 -"), not an element, so the locator is
|
|
2
|
+
built inline from the caption rather than through the `locator` partial. That is why the scope
|
|
3
|
+
clause needs its own slot here — the partial's `parentLocator` handling never runs (#747).
|
|
4
|
+
Unscoped, `… in [Education Card 6] card` compiled away to nothing: on a screen repeating ten
|
|
5
|
+
学歴・職歴 cards the assertion strict-mode-failed where the caption repeats, and where it
|
|
6
|
+
happened to be unique elsewhere it passed having read a DIFFERENT card than the sentence names. --}}
|
|
7
|
+
await expect({{#if parentLocator}}{{parentLocator}}{{else}}page{{/if}}.getByText(/{{#if label}}{{escapeRegex label}}\s*{{else}}(?:^|\s){{/if}}{{escapeRegex dataValue}}$/)).toBeVisible();
|
|
@@ -1 +1,23 @@
|
|
|
1
|
-
{{!-- Column assertion scoped to the current row (`tableRow`, bound by the row-in-table entry step
|
|
1
|
+
{{!-- Column assertion scoped to the current row (`tableRow`, bound by the row-in-table entry step
|
|
2
|
+
that ran before this one).
|
|
3
|
+
|
|
4
|
+
`cellLocator` is the DECLARED cell — `columns.<col>.elements.cell` on the table entry, already
|
|
5
|
+
rendered against `tableRow`. It comes first because it is the only branch that works on a row
|
|
6
|
+
built from <div>s: those rows carry no ARIA `cell` role, so both branches below match zero
|
|
7
|
+
elements there, and `columns.<col>.index` cannot help — it indexes that same empty set. The
|
|
8
|
+
compiler warns (SG-W034) when a `rows:` shape is declared and no cell is.
|
|
9
|
+
|
|
10
|
+
columnIndex uses `isDefined` rather than a bare truthy check: column 0 is a valid index.
|
|
11
|
+
|
|
12
|
+
includeHidden + visible filter: when a modal (e.g. Radix Dialog) is open the background gets
|
|
13
|
+
aria-hidden="true", which empties plain getByRole() even though the cells are still rendered
|
|
14
|
+
— measure what the user SEES, not the a11y tree. --}}
|
|
15
|
+
{{~#if cellLocator~}}
|
|
16
|
+
{{#if isGiven}}await {{cellLocator}}.filter({ hasText: '{{escapeQuotes cellValue}}' }).waitFor();{{else}}await expect({{cellLocator}}).toHaveText('{{escapeQuotes cellValue}}');{{/if}}
|
|
17
|
+
{{~else if (isDefined columnIndex)~}}
|
|
18
|
+
await expect(tableRow.getByRole('cell', { includeHidden: true }).filter({ visible: true }).nth({{columnIndex}})).toHaveText('{{escapeQuotes cellValue}}');
|
|
19
|
+
{{~else if isGiven~}}
|
|
20
|
+
await tableRow.getByRole('cell', { includeHidden: true }).filter({ hasText: '{{escapeQuotes cellValue}}' }).filter({ visible: true }).waitFor();
|
|
21
|
+
{{~else~}}
|
|
22
|
+
await expect(tableRow.getByRole('cell', { includeHidden: true }).filter({ hasText: '{{escapeQuotes cellValue}}' }).filter({ visible: true })).toBeVisible();
|
|
23
|
+
{{~/if~}}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
{{!-- `getByRole` is built inline from role/name rather than through the `locator` partial, so the
|
|
2
|
+
scope clause needs its own slot here exactly as in label-value-assertion — the label branch's
|
|
3
|
+
twin (#747). `see [Logo] image with {{alt}} in [Product Card 3] card` searched every card on
|
|
4
|
+
the page before this. --}}
|
|
1
5
|
{{#if name}}
|
|
2
6
|
{{#if dataValue}}
|
|
3
|
-
await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeVisible();
|
|
7
|
+
await expect({{#if parentLocator}}{{parentLocator}}{{else}}page{{/if}}.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeVisible();
|
|
4
8
|
{{else}}
|
|
5
|
-
await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }){{> locator-nth}}).toBeVisible();
|
|
9
|
+
await expect({{#if parentLocator}}{{parentLocator}}{{else}}page{{/if}}.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }){{> locator-nth}}).toBeVisible();
|
|
6
10
|
{{/if}}
|
|
7
11
|
{{else}}
|
|
8
|
-
await expect(page.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeVisible();
|
|
9
|
-
{{/if}}
|
|
12
|
+
await expect({{#if parentLocator}}{{parentLocator}}{{else}}page{{/if}}.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeVisible();
|
|
13
|
+
{{/if}}
|
package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
{{~#case 'label'}}getByLabel('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
6
6
|
{{~#case 'text'}}getByText('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
7
7
|
{{~#case 'locator'}}locator('{{escapeQuotes value}}'){{/case~}}
|
|
8
|
-
{{~#case 'id'}}locator('{{escapeQuotes value}}'){{/case~}}
|
|
9
|
-
{{~#default}}locator('{{escapeQuotes value}}'){{/default~}}
|
|
8
|
+
{{~#case 'id'}}locator('{{escapeQuotes (cssIdSelector locator value)}}'){{/case~}}
|
|
9
|
+
{{~#default}}locator('{{#if locator}}{{escapeQuotes locator}}{{else}}{{escapeQuotes value}}{{/if}}'){{/default~}}
|
|
10
10
|
{{~/switch}}{{else~}}
|
|
11
11
|
{{#if scope}}{{#if (eq scope "dialog")}}{{pageRoot}}.{{else}}{{pageRoot}}.getByLabel('{{escapeQuotes scope}}').{{/if}}{{#switch strategy~}}
|
|
12
12
|
{{~#case 'testid'}}getByTestId('{{escapeQuotes value}}'){{/case~}}
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
{{~#case 'label'}}getByLabel('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
16
16
|
{{~#case 'text'}}getByText('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
17
17
|
{{~#case 'locator'}}locator('{{escapeQuotes value}}'){{/case~}}
|
|
18
|
-
{{~#case 'id'}}locator('{{escapeQuotes value}}'){{/case~}}
|
|
19
|
-
{{~#default}}locator('{{escapeQuotes value}}'){{/default~}}
|
|
18
|
+
{{~#case 'id'}}locator('{{escapeQuotes (cssIdSelector locator value)}}'){{/case~}}
|
|
19
|
+
{{~#default}}locator('{{#if locator}}{{escapeQuotes locator}}{{else}}{{escapeQuotes value}}{{/if}}'){{/default~}}
|
|
20
20
|
{{~/switch}}{{else}}{{#switch strategy~}}
|
|
21
21
|
{{~#case 'testid'}}{{> locator-strategies/testid}}{{/case~}}
|
|
22
22
|
{{~#case 'role'}}{{> locator-strategies/role}}{{/case~}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
{{pageRoot}}.locator('{{#if locator}}{{escapeQuotes locator}}{{else}}{{escapeQuotes value}}{{/if}}')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
{{pageRoot}}.locator('{{escapeQuotes (cssIdSelector locator value)}}')
|
package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
{{~#case 'label'}}getByLabel('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
6
6
|
{{~#case 'text'}}getByText('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
7
7
|
{{~#case 'locator'}}locator('{{escapeQuotes value}}'){{/case~}}
|
|
8
|
-
{{~#case 'id'}}locator('{{escapeQuotes value}}'){{/case~}}
|
|
9
|
-
{{~#default}}locator('{{escapeQuotes value}}'){{/default~}}
|
|
8
|
+
{{~#case 'id'}}locator('{{escapeQuotes (cssIdSelector locator value)}}'){{/case~}}
|
|
9
|
+
{{~#default}}locator('{{#if locator}}{{escapeQuotes locator}}{{else}}{{escapeQuotes value}}{{/if}}'){{/default~}}
|
|
10
10
|
{{~/switch}}{{> locator-nth}}{{else~}}
|
|
11
11
|
{{#if scope}}{{#if (eq scope "dialog")}}{{pageRoot}}.{{else}}{{pageRoot}}.getByLabel('{{escapeQuotes scope}}').{{/if}}{{#switch strategy~}}
|
|
12
12
|
{{~#case 'testid'}}getByTestId('{{escapeQuotes value}}'){{/case~}}
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
{{~#case 'label'}}getByLabel('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
16
16
|
{{~#case 'text'}}getByText('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
17
17
|
{{~#case 'locator'}}locator('{{escapeQuotes value}}'){{/case~}}
|
|
18
|
-
{{~#case 'id'}}locator('{{escapeQuotes value}}'){{/case~}}
|
|
19
|
-
{{~#default}}locator('{{escapeQuotes value}}'){{/default~}}
|
|
18
|
+
{{~#case 'id'}}locator('{{escapeQuotes (cssIdSelector locator value)}}'){{/case~}}
|
|
19
|
+
{{~#default}}locator('{{#if locator}}{{escapeQuotes locator}}{{else}}{{escapeQuotes value}}{{/if}}'){{/default~}}
|
|
20
20
|
{{~/switch}}{{> locator-nth}}{{else}}{{#switch strategy~}}
|
|
21
21
|
{{~#case 'testid'}}{{> locator-strategies/testid}}{{/case~}}
|
|
22
22
|
{{~#case 'role'}}{{> locator-strategies/role}}{{/case~}}
|
package/src/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs
CHANGED
|
@@ -15,5 +15,20 @@
|
|
|
15
15
|
|
|
16
16
|
`expectPage`, not a bare `toHaveURL`: a polling URL assertion passes on the first transient
|
|
17
17
|
match, before a route guard has bounced the user away (#662). The helper arrives, lets the
|
|
18
|
-
page settle, holds, and re-asserts — read specs/url-assert.ts.
|
|
19
|
-
|
|
18
|
+
page settle, holds, and re-asserts — read specs/url-assert.ts.
|
|
19
|
+
|
|
20
|
+
`hostname` rides separately and only for an absolute page value, exactly as in the
|
|
21
|
+
`wait-for-page` twin: it is read off `u.hostname`, which never appears in `u.pathname`, so
|
|
22
|
+
one concatenated regex cannot carry both. Without it a page declared as
|
|
23
|
+
`https://admin.example.com/jobs` asserted the PATH on any host, and the public site's /jobs
|
|
24
|
+
satisfied it.
|
|
25
|
+
|
|
26
|
+
`ignoreQuery` comes from the page ENTRY's `query: any`, not from the step, because it is a fact
|
|
27
|
+
about the page rather than about one assertion (#748). It relaxes only the bare-page rule; the
|
|
28
|
+
declared-value half below is untouched, so the two compose rather than override each other.
|
|
29
|
+
|
|
30
|
+
The declared-value half is gated on `dataRef`, not on `query`. The step wrote `with {{v}}`
|
|
31
|
+
either way; gating on the resolved VALUE meant an empty test-data entry silently dropped the
|
|
32
|
+
whole check and fell back to the bare-page rule, which is the one case specs/url-assert.ts
|
|
33
|
+
exists to throw on. --}}
|
|
34
|
+
await expectPage(page, { pathname: /^{{pathRegex}}\/?$/{{#if hostRegex}}, hostname: /^{{hostRegex}}$/{{/if}}{{#if ignoreQuery}}, ignoreQuery: true{{/if}}{{#if dataRef}}, dataRef: '{{escapeQuotes dataRef}}', declared: '{{escapeQuotes query}}'{{/if}} });
|
|
@@ -49,6 +49,20 @@ test.describe('{{featureName}}', () => {
|
|
|
49
49
|
{{#each scenarios}}
|
|
50
50
|
{{indent this 2}}
|
|
51
51
|
|
|
52
|
+
{{/each}}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
{{else if noAuth}}
|
|
56
|
+
{{!-- @no-auth demands an EMPTY session (#735). Leaving the group bare inherits whatever
|
|
57
|
+
playwright.config.ts set through `use.storageState`, so "an anonymous visitor is redirected to
|
|
58
|
+
sign-in" ran signed in and passed for the wrong reason. Omitting storageState inherits it —
|
|
59
|
+
an inline empty state is the only way to say "no session". --}}
|
|
60
|
+
test.describe('no-auth', () => {
|
|
61
|
+
test.use({ storageState: { cookies: [], origins: [] } });
|
|
62
|
+
|
|
63
|
+
{{#each scenarios}}
|
|
64
|
+
{{indent this 2}}
|
|
65
|
+
|
|
52
66
|
{{/each}}
|
|
53
67
|
});
|
|
54
68
|
|
|
@@ -167,23 +167,51 @@ function extractAuthRole(tags: string[]): string | undefined {
|
|
|
167
167
|
* Precedence: Scenario > Feature > None
|
|
168
168
|
*/
|
|
169
169
|
function getEffectiveAuthRole(
|
|
170
|
-
scenarioTags: string[],
|
|
170
|
+
scenarioTags: string[],
|
|
171
171
|
featureTags: string[]
|
|
172
172
|
): string | undefined {
|
|
173
173
|
// Check for explicit @no-auth override
|
|
174
174
|
if (scenarioTags.some(tag => tag === '@no-auth')) {
|
|
175
175
|
return undefined;
|
|
176
176
|
}
|
|
177
|
-
|
|
177
|
+
|
|
178
178
|
// Scenario-level takes precedence
|
|
179
179
|
const scenarioRole = extractAuthRole(scenarioTags);
|
|
180
180
|
if (scenarioRole) return scenarioRole;
|
|
181
|
-
|
|
181
|
+
|
|
182
182
|
// Fall back to feature-level
|
|
183
183
|
const featureRole = extractAuthRole(featureTags);
|
|
184
184
|
return featureRole;
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
/**
|
|
188
|
+
* The three auth states a scenario can be in — they are NOT two (#735).
|
|
189
|
+
*
|
|
190
|
+
* `@no-auth` and "no auth tag at all" both leave `getEffectiveAuthRole` undefined, but they mean
|
|
191
|
+
* opposite things: the first demands an EMPTY session, the second inherits whatever
|
|
192
|
+
* `playwright.config.ts` set via `use.storageState`. Collapsing them put both in one group, so a
|
|
193
|
+
* scenario written to prove "an anonymous visitor is bounced to sign-in" ran signed in and passed
|
|
194
|
+
* for the wrong reason — while the exported test case still printed "Not authenticated".
|
|
195
|
+
*
|
|
196
|
+
* Returned as a flag beside the role rather than a tagged union so every existing `authRole` reader
|
|
197
|
+
* keeps working unchanged; only the grouping and the template consult `noAuth`.
|
|
198
|
+
*/
|
|
199
|
+
function getAuthMode(
|
|
200
|
+
scenarioTags: string[],
|
|
201
|
+
featureTags: string[],
|
|
202
|
+
): { noAuth: boolean; authRole: string | undefined } {
|
|
203
|
+
if (scenarioTags.some(tag => tag === '@no-auth')) {
|
|
204
|
+
return { noAuth: true, authRole: undefined };
|
|
205
|
+
}
|
|
206
|
+
return { noAuth: false, authRole: getEffectiveAuthRole(scenarioTags, featureTags) };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Grouping key for the anonymous bucket. A literal that no `@auth:<role>` can produce, so an
|
|
211
|
+
* imaginative role name can never land in the same group as `@no-auth`.
|
|
212
|
+
*/
|
|
213
|
+
const NO_AUTH_GROUP_KEY = '\0no-auth';
|
|
214
|
+
|
|
187
215
|
|
|
188
216
|
/**
|
|
189
217
|
* True when the scenario has at least one step in the Then bucket —
|
|
@@ -238,6 +266,56 @@ function warnMultipleAuthTags(tags: string[], context: string): void {
|
|
|
238
266
|
}
|
|
239
267
|
}
|
|
240
268
|
|
|
269
|
+
/**
|
|
270
|
+
* Drop the YAML comment from ONE line: a `#` at line start or preceded by whitespace,
|
|
271
|
+
* and not inside a quoted scalar. Line-level only — see `yamlWithoutComments` for the
|
|
272
|
+
* block-scalar case, which this function cannot see from a single line.
|
|
273
|
+
*/
|
|
274
|
+
function stripYamlComment(line: string): string {
|
|
275
|
+
let quote: string | null = null;
|
|
276
|
+
for (let i = 0; i < line.length; i++) {
|
|
277
|
+
const c = line[i];
|
|
278
|
+
if (quote) {
|
|
279
|
+
if (quote === '"' && c === '\\') { i++; continue; }
|
|
280
|
+
if (c === quote) quote = null;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (c === '"' || c === "'") { quote = c; continue; }
|
|
284
|
+
if (c === '#' && (i === 0 || /\s/.test(line[i - 1]))) return line.slice(0, i);
|
|
285
|
+
}
|
|
286
|
+
return line;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* The YAML text a `${QA_*}` scan may look at: comments removed, block scalars kept whole.
|
|
291
|
+
*
|
|
292
|
+
* SG-W009's reference scan used to run over the whole raw file, so a `${QA_*}` that only ever
|
|
293
|
+
* appeared in a `#` comment counted as a live reference — including the one sungen's own
|
|
294
|
+
* scaffolded "HOW TO FILL VALUES" block writes into every test-data file. The declared side has
|
|
295
|
+
* always been line-wise; this brings the reference side to the same footing.
|
|
296
|
+
*
|
|
297
|
+
* Inside a `|`/`>` block scalar every character is literal content, `#` included, so those lines
|
|
298
|
+
* pass through untouched. Stripping them would send the check the other way — a real, undeclared
|
|
299
|
+
* `${QA_*}` written after a `#` in a block scalar would go unreported, and the run would only fail
|
|
300
|
+
* later with "environment variable not set". A missed warning is worse than a noisy one.
|
|
301
|
+
*/
|
|
302
|
+
function yamlWithoutComments(text: string): string {
|
|
303
|
+
const out: string[] = [];
|
|
304
|
+
let blockIndent: number | null = null;
|
|
305
|
+
for (const line of text.split('\n')) {
|
|
306
|
+
if (blockIndent !== null) {
|
|
307
|
+
const indent = line.search(/\S/);
|
|
308
|
+
// blank lines and anything indented deeper than the key stay inside the scalar
|
|
309
|
+
if (indent === -1 || indent > blockIndent) { out.push(line); continue; }
|
|
310
|
+
blockIndent = null;
|
|
311
|
+
}
|
|
312
|
+
const block = /^(\s*)[^#\s][^:]*:\s*[|>][+-]?\d*[+-]?\s*$/.exec(line);
|
|
313
|
+
if (block) { blockIndent = block[1].length; out.push(line); continue; }
|
|
314
|
+
out.push(stripYamlComment(line));
|
|
315
|
+
}
|
|
316
|
+
return out.join('\n');
|
|
317
|
+
}
|
|
318
|
+
|
|
241
319
|
/**
|
|
242
320
|
* Result of test generation for a single feature file
|
|
243
321
|
*/
|
|
@@ -699,7 +777,7 @@ export class CodeGenerator {
|
|
|
699
777
|
// absent compiles to a skip stub instead of a real test (read once per feature).
|
|
700
778
|
const enabledCaps = new Set<string>(readCapabilities(process.cwd()).enabled.map(d => d.toLowerCase()));
|
|
701
779
|
|
|
702
|
-
const renderedScenarios: Array<{ code: string; authRole?: string }> = [];
|
|
780
|
+
const renderedScenarios: Array<{ code: string; authRole?: string; noAuth?: boolean }> = [];
|
|
703
781
|
for (const scenario of feature.scenarios) {
|
|
704
782
|
if (isManual(scenario.tags)) {
|
|
705
783
|
// SG-W008 — two-direction rule for @manual scenarios:
|
|
@@ -758,7 +836,14 @@ export class CodeGenerator {
|
|
|
758
836
|
const reqAbsent = requiresCaps(scenario.tags).filter(c => !enabledCaps.has(c));
|
|
759
837
|
if (reqAbsent.length) {
|
|
760
838
|
if (this.options.verbose) console.log(` ⏸ Pending capability (${reqAbsent.join(', ')}): ${scenario.name}`);
|
|
761
|
-
|
|
839
|
+
// Carries its `@no-auth` tag like any other scenario so the stub lands in the group the
|
|
840
|
+
// author declared — a skipped test that reappears once the capability is enabled must not
|
|
841
|
+
// quietly change auth bucket.
|
|
842
|
+
renderedScenarios.push({
|
|
843
|
+
code: this.generateRequiresSkipStub(scenario.name, reqAbsent),
|
|
844
|
+
authRole: undefined,
|
|
845
|
+
noAuth: scenario.tags.includes('@no-auth'),
|
|
846
|
+
});
|
|
762
847
|
continue;
|
|
763
848
|
}
|
|
764
849
|
|
|
@@ -770,7 +855,7 @@ export class CodeGenerator {
|
|
|
770
855
|
authFeatureTags = [...baseScenario.tags, ...authFeatureTags];
|
|
771
856
|
}
|
|
772
857
|
}
|
|
773
|
-
const authRole =
|
|
858
|
+
const { authRole, noAuth } = getAuthMode(scenario.tags, authFeatureTags);
|
|
774
859
|
|
|
775
860
|
const code = await this.generateScenario(
|
|
776
861
|
scenario,
|
|
@@ -778,15 +863,16 @@ export class CodeGenerator {
|
|
|
778
863
|
feature.tags || [],
|
|
779
864
|
isParallel
|
|
780
865
|
);
|
|
781
|
-
renderedScenarios.push({ code, authRole });
|
|
866
|
+
renderedScenarios.push({ code, authRole, noAuth });
|
|
782
867
|
}
|
|
783
868
|
|
|
784
869
|
// Group scenarios by auth role for nested test.describe blocks
|
|
785
|
-
// This ensures test.use({ storageState }) only applies to its group
|
|
870
|
+
// This ensures test.use({ storageState }) only applies to its group. `@no-auth` gets its OWN
|
|
871
|
+
// bucket (#735) — it is a demand for an empty session, not the absence of a demand.
|
|
786
872
|
const authGroupMap = new Map<string, string[]>();
|
|
787
873
|
const groupOrder: string[] = [];
|
|
788
|
-
for (const { code, authRole } of renderedScenarios) {
|
|
789
|
-
const key = authRole || '';
|
|
874
|
+
for (const { code, authRole, noAuth } of renderedScenarios) {
|
|
875
|
+
const key = noAuth ? NO_AUTH_GROUP_KEY : (authRole || '');
|
|
790
876
|
if (!authGroupMap.has(key)) {
|
|
791
877
|
authGroupMap.set(key, []);
|
|
792
878
|
groupOrder.push(key);
|
|
@@ -795,7 +881,8 @@ export class CodeGenerator {
|
|
|
795
881
|
}
|
|
796
882
|
|
|
797
883
|
const authGroups = groupOrder.map(key => ({
|
|
798
|
-
authRole: key || undefined,
|
|
884
|
+
authRole: key === NO_AUTH_GROUP_KEY ? undefined : (key || undefined),
|
|
885
|
+
noAuth: key === NO_AUTH_GROUP_KEY,
|
|
799
886
|
scenarios: authGroupMap.get(key)!,
|
|
800
887
|
}));
|
|
801
888
|
|
|
@@ -804,6 +891,15 @@ export class CodeGenerator {
|
|
|
804
891
|
// - Multiple groups: nested describes per auth role
|
|
805
892
|
const needsGrouping = authGroups.length > 1;
|
|
806
893
|
|
|
894
|
+
// Rendering the per-group blocks is a SEPARATE question from the guards below (#735). A lone
|
|
895
|
+
// `@no-auth` group is one group — `needsGrouping` is false — yet it still has to emit its empty
|
|
896
|
+
// storageState, or an all-anonymous feature silently runs on the config's session. Keeping the
|
|
897
|
+
// guards keyed on `needsGrouping` means this never trips the appium/serial errors, which are
|
|
898
|
+
// about MIXING roles. Serial is excluded because its layout builds the context by hand with
|
|
899
|
+
// `browser.newContext()`, which never picks up `use.storageState` — already anonymous, and a
|
|
900
|
+
// `test.use` there would apply to nothing.
|
|
901
|
+
const renderAuthBlocks = needsGrouping || (isParallel && authGroups.some(g => g.noAuth));
|
|
902
|
+
|
|
807
903
|
// D6 — the Appium test-file template has no per-group before-hook (only `singleAuthRole` runs
|
|
808
904
|
// `__ensureAuth`); >1 auth group would render every group's scenarios with NO authentication at
|
|
809
905
|
// all, silently. Mirrors the web `@parallel` guard below, but unconditional (isParallel has no
|
|
@@ -819,15 +915,18 @@ export class CodeGenerator {
|
|
|
819
915
|
|
|
820
916
|
if (needsGrouping && !isParallel) {
|
|
821
917
|
throw new Error(
|
|
822
|
-
`Feature "${feature.name}" has multiple auth groups but
|
|
918
|
+
`Feature "${feature.name}" has multiple auth groups but runs serially.\n` +
|
|
823
919
|
`Serial mode uses a shared browser context — it cannot mix different auth roles.\n` +
|
|
824
|
-
`Fix:
|
|
920
|
+
`Fix: remove the @serial tag (features are parallel by default), or split "${feature.name}" ` +
|
|
921
|
+
`into one feature file per auth role.`
|
|
825
922
|
);
|
|
826
923
|
}
|
|
827
924
|
const scenarios = renderedScenarios.map(s => s.code);
|
|
828
925
|
|
|
829
|
-
// For single group, extract the auth role to put test.use at describe level
|
|
830
|
-
|
|
926
|
+
// For single group, extract the auth role to put test.use at describe level. Keyed on
|
|
927
|
+
// `renderAuthBlocks`, not `needsGrouping`: a lone `@no-auth` group now renders its own block, so
|
|
928
|
+
// hoisting anything to the feature describe would double up. Its authRole is undefined anyway.
|
|
929
|
+
const singleAuthRole = !renderAuthBlocks && authGroups.length === 1
|
|
831
930
|
? authGroups[0].authRole
|
|
832
931
|
: undefined;
|
|
833
932
|
|
|
@@ -932,8 +1031,8 @@ export class CodeGenerator {
|
|
|
932
1031
|
platform,
|
|
933
1032
|
cleanup,
|
|
934
1033
|
backgroundSteps,
|
|
935
|
-
scenarios:
|
|
936
|
-
authGroups:
|
|
1034
|
+
scenarios: renderAuthBlocks ? [] : scenarios,
|
|
1035
|
+
authGroups: renderAuthBlocks ? authGroups : undefined,
|
|
937
1036
|
singleAuthRole,
|
|
938
1037
|
appPackage,
|
|
939
1038
|
iosBundleId,
|
|
@@ -1265,7 +1364,8 @@ export class CodeGenerator {
|
|
|
1265
1364
|
* a name in the committed .env.qa.example. An undeclared ref means the test throws
|
|
1266
1365
|
* "environment variable not set" the moment the key is used — surface it at compile
|
|
1267
1366
|
* time instead. Only the `${QA_*}` shape is checked: any other `${...}` text is
|
|
1268
|
-
* ordinary literal data by design (never resolved, never warned about).
|
|
1367
|
+
* ordinary literal data by design (never resolved, never warned about). A `${QA_*}`
|
|
1368
|
+
* that appears only inside a `#` comment is prose, not a reference, and is ignored.
|
|
1269
1369
|
*/
|
|
1270
1370
|
private reportedEnvRefs = new Set<string>();
|
|
1271
1371
|
private checkEnvSecretRefs(featureFile: string): void {
|
|
@@ -1273,7 +1373,7 @@ export class CodeGenerator {
|
|
|
1273
1373
|
if (!fs.existsSync(testDataDir)) return;
|
|
1274
1374
|
const referenced = new Set<string>();
|
|
1275
1375
|
for (const f of fs.readdirSync(testDataDir).filter((x) => x.endsWith('.yaml') || x.endsWith('.yml'))) {
|
|
1276
|
-
const text = fs.readFileSync(path.join(testDataDir, f), 'utf-8');
|
|
1376
|
+
const text = yamlWithoutComments(fs.readFileSync(path.join(testDataDir, f), 'utf-8'));
|
|
1277
1377
|
for (const m of text.matchAll(/\$\{(QA_[A-Z0-9_]+)\}/g)) referenced.add(m[1]);
|
|
1278
1378
|
}
|
|
1279
1379
|
if (!referenced.size) return;
|