@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
|
@@ -3,9 +3,39 @@
|
|
|
3
3
|
* Reuses the existing GherkinParser.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { GherkinParser, ParsedFeature, ParsedScenario } from '../generators/gherkin-parser';
|
|
6
|
+
import { GherkinParser, ParsedDataTable, ParsedFeature, ParsedScenario } from '../generators/gherkin-parser';
|
|
7
7
|
import { FeatureMetadata, OrderedStep, ScenarioMetadata } from './types';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Fold a step's data table into the text the exporters carry (#771). The table
|
|
11
|
+
* IS the step's content — `tab order:` names every stop, `table match data:`
|
|
12
|
+
* every expected row — so without it a delivered case says "See tab order:."
|
|
13
|
+
* and a table edit leaves the scenario fingerprint unchanged.
|
|
14
|
+
*
|
|
15
|
+
* Folded once, here, so every consumer (matrix + legacy cells, fingerprint,
|
|
16
|
+
* oracle shape) sees the same thing. A step with no table keeps its text
|
|
17
|
+
* byte-identical: no existing fingerprint moves on upgrade.
|
|
18
|
+
*
|
|
19
|
+
* `tab order:` | Ref | [A] | [B] | → `tab order: [A] → [B]` (order is the meaning;
|
|
20
|
+
* `Ref` is a keyword header, not a column name)
|
|
21
|
+
* any other | Name | Age | … → `…: Name: Alice, Age: 30; Name: Bob, Age: 25`
|
|
22
|
+
* (one column too: `Title: x; Title: y` — its header names what x is)
|
|
23
|
+
*
|
|
24
|
+
* A blank cell renders as `(empty)` — the matrix's data-fidelity word. Left blank it would
|
|
25
|
+
* read `Age: ;`, indistinguishable from the truncation this fixes, and in `table match
|
|
26
|
+
* data:` a blank cell is itself an assertion (the cell is empty).
|
|
27
|
+
*/
|
|
28
|
+
export function withDataTable(text: string, table: ParsedDataTable | undefined): string {
|
|
29
|
+
if (!table || table.rows.length === 0) return text;
|
|
30
|
+
const base = text.replace(/\s*:\s*$/, '');
|
|
31
|
+
const cell = (v: string | undefined): string => (v === undefined || v.trim() === '' ? '(empty)' : v);
|
|
32
|
+
// Only `tab order` is a sequence of refs — the one shape matrix/wording.ts reads as `→`.
|
|
33
|
+
const body = /\btab order$/i.test(base)
|
|
34
|
+
? table.rows.map((r) => cell(r.cells[0])).join(' → ')
|
|
35
|
+
: table.rows.map((r) => table.headers.map((h, i) => `${h}: ${cell(r.cells[i])}`).join(', ')).join('; ');
|
|
36
|
+
return `${base}: ${body}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
9
39
|
/**
|
|
10
40
|
* Variables referenced in a scenario: find all {{var_name}} in step text.
|
|
11
41
|
*/
|
|
@@ -60,7 +90,7 @@ function classifySteps(scenario: ParsedScenario): OrderedStep[] {
|
|
|
60
90
|
else if (keyword === 'Then') currentBucket = 'then';
|
|
61
91
|
// And / But → inherit currentBucket
|
|
62
92
|
|
|
63
|
-
ordered.push({ text: step.text, bucket: currentBucket });
|
|
93
|
+
ordered.push({ text: withDataTable(step.text, step.dataTable), bucket: currentBucket });
|
|
64
94
|
}
|
|
65
95
|
|
|
66
96
|
return ordered;
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
*/
|
|
35
35
|
import * as fs from 'fs';
|
|
36
36
|
import * as path from 'path';
|
|
37
|
-
import {
|
|
37
|
+
import { readYamlConfig, warnUnreadableConfig } from '../harness/config-file';
|
|
38
38
|
|
|
39
39
|
export interface LocaleVariant {
|
|
40
40
|
/** Result-file suffix as found on disk ('' = base). */
|
|
@@ -65,23 +65,29 @@ export interface LocaleConfig {
|
|
|
65
65
|
|
|
66
66
|
export const DEFAULT_BASE_LOCALE = 'en';
|
|
67
67
|
|
|
68
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* Read the i18n declaration from qa/capabilities.yaml (absent → no locales, base 'en').
|
|
70
|
+
*
|
|
71
|
+
* An unreadable file previously took the same path as an absent one — the `existsSync` check sat
|
|
72
|
+
* INSIDE the try, so a parse error fell through to the same "undeclared" return. Every declared
|
|
73
|
+
* locale then dropped out of the deliverable with no line anywhere saying why.
|
|
74
|
+
*/
|
|
69
75
|
export function readLocaleConfig(cwd: string): LocaleConfig {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return { baseLocale
|
|
76
|
+
const p = path.join(cwd, 'qa', 'capabilities.yaml');
|
|
77
|
+
const read = readYamlConfig<{ locales?: unknown; base_locale?: unknown } | null>(p);
|
|
78
|
+
if (read.state === 'absent') return { baseLocale: DEFAULT_BASE_LOCALE, locales: [] };
|
|
79
|
+
if (read.state === 'unreadable') {
|
|
80
|
+
warnUnreadableConfig(p, read.error, 'no locale variants are discovered — the deliverable covers the base locale only');
|
|
81
|
+
return { baseLocale: DEFAULT_BASE_LOCALE, locales: [] };
|
|
82
|
+
}
|
|
83
|
+
const parsed = read.value;
|
|
84
|
+
const locales = Array.isArray(parsed?.locales)
|
|
85
|
+
? parsed!.locales!.filter((l): l is string => typeof l === 'string' && l.length > 0)
|
|
86
|
+
: [];
|
|
87
|
+
const baseLocale = typeof parsed?.base_locale === 'string' && parsed.base_locale
|
|
88
|
+
? parsed.base_locale
|
|
89
|
+
: DEFAULT_BASE_LOCALE;
|
|
90
|
+
return { baseLocale, locales };
|
|
85
91
|
}
|
|
86
92
|
|
|
87
93
|
const OS_SUFFIX = /^(android|ios)(\.|$)/;
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
import { getCasesDatasetRows, resolveResultVariants } from '../result-variants';
|
|
20
20
|
import { renderDynamicTokens } from '../test-data-resolver';
|
|
21
21
|
import { maskCredentialValue } from '../sensitive-key';
|
|
22
|
-
import { classifyManualComments, renderAction, renderExpected, renderPrecondition, renderSetupInstruction, runtimeVarsOf } from './wording';
|
|
22
|
+
import { afterSubstitution, classifyManualComments, renderAction, renderExpected, renderPrecondition, renderSetupInstruction, runtimeVarsOf } from './wording';
|
|
23
23
|
import { scenarioFingerprint, combinedFingerprint, mapContentFingerprint } from './fingerprint';
|
|
24
24
|
import {
|
|
25
25
|
CoverageVariant,
|
|
@@ -251,10 +251,16 @@ export function deriveVariants(inputs: Pick<BuildInputs, 'feature' | 'merged' |
|
|
|
251
251
|
verification: manual?.verification.map((t) => sentenceOf(t)) ?? [],
|
|
252
252
|
};
|
|
253
253
|
|
|
254
|
-
/**
|
|
254
|
+
/**
|
|
255
|
+
* Render the display cells for one data context. Steps are worded FIRST and values
|
|
256
|
+
* substituted AFTER (#772): wording strips the DSL's `[Ref]` brackets, and a value
|
|
257
|
+
* such as `^[A-Za-z]{3}` or `[E2E-01] title` must never be mistaken for one.
|
|
258
|
+
* Preconditions (rendered at build time above) already went in this order.
|
|
259
|
+
*/
|
|
255
260
|
const renderCells = (vars: Record<string, string>): { trigger: string[]; oracle: string[]; precondition: string[]; verification: string[] } => {
|
|
256
261
|
const sub = (s: string): string => substituteDisplayVars(s, vars, runtimeVars);
|
|
257
|
-
const
|
|
262
|
+
const fill = (rendered: string): string => afterSubstitution(sub(rendered));
|
|
263
|
+
const preconditionOut = precondition.map(fill);
|
|
258
264
|
const verificationOut = base.verification.map(sub);
|
|
259
265
|
if (procedureProfile === 'sequence' && !manual) {
|
|
260
266
|
// Ordered flow (review B-03): keep the event order — actions stay numbered in
|
|
@@ -266,9 +272,9 @@ export function deriveVariants(inputs: Pick<BuildInputs, 'feature' | 'merged' |
|
|
|
266
272
|
const trigger: string[] = [];
|
|
267
273
|
const oracle: string[] = [];
|
|
268
274
|
steps.forEach((s, i) => {
|
|
269
|
-
if (i > lastActionIdx) oracle.push(renderExpected(
|
|
270
|
-
else if (s.bucket === 'then') trigger.push(`Verify: ${renderExpected(
|
|
271
|
-
else if (s.bucket === 'when') trigger.push(renderAction(
|
|
275
|
+
if (i > lastActionIdx) oracle.push(fill(renderExpected(s.text))); // trailing Then block
|
|
276
|
+
else if (s.bucket === 'then') trigger.push(`Verify: ${fill(renderExpected(s.text))}`);
|
|
277
|
+
else if (s.bucket === 'when') trigger.push(fill(renderAction(s.text)));
|
|
272
278
|
// leading given steps already live in the precondition
|
|
273
279
|
});
|
|
274
280
|
return { trigger, oracle, precondition: preconditionOut, verification: verificationOut };
|
|
@@ -277,8 +283,8 @@ export function deriveVariants(inputs: Pick<BuildInputs, 'feature' | 'merged' |
|
|
|
277
283
|
// Method/API/Header/Body lines are already reader-facing, not step DSL).
|
|
278
284
|
const trigger = apiCallsAreTrigger
|
|
279
285
|
? apiRequestLines.map(sub)
|
|
280
|
-
: rawTrigger.map((s) => renderAction(
|
|
281
|
-
const oracle = rawOracle.map((s) => renderExpected(
|
|
286
|
+
: rawTrigger.map((s) => fill(renderAction(s)));
|
|
287
|
+
const oracle = rawOracle.map((s) => fill(renderExpected(s)));
|
|
282
288
|
return {
|
|
283
289
|
// A pure-render check (Given + Then only) is checked on page load — say so
|
|
284
290
|
// in words instead of a placeholder token (review §10.3).
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Carry a tester's hand-entered Manual results across a `sungen delivery` re-run (#756).
|
|
3
|
+
*
|
|
4
|
+
* The matrix workbook is rebuilt from scratch on every run, and a manual variant has no
|
|
5
|
+
* run-report entry, so its Result cell renders `Pending` and M–P render blank. Without
|
|
6
|
+
* this step the re-run overwrote the file and the tester's record was gone.
|
|
7
|
+
*
|
|
8
|
+
* Before the new workbook is written, the existing file's Manual sheet is read, and each
|
|
9
|
+
* variant sub-row's L–P cells (Test Result, Executed Date, Test Executor, Test Environment,
|
|
10
|
+
* Note) are carried into the new workbook by `v.ref` — column A.
|
|
11
|
+
*
|
|
12
|
+
* A result is only true of the procedure it was executed against, so every write stamps
|
|
13
|
+
* `ref → fingerprint` into a veryHidden sheet. The next run carries a result only while the
|
|
14
|
+
* stamp still matches; a changed procedure resets it to `Pending` and keeps the previous
|
|
15
|
+
* values in Note. Spec: docs/spec/delivery-coverage-matrix-spec.md §8.4.
|
|
16
|
+
*
|
|
17
|
+
* Manual sheet only: on Auto the run report is the source of truth for Result.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import * as fs from 'fs';
|
|
21
|
+
import * as ExcelJS from 'exceljs';
|
|
22
|
+
import { MatrixModel } from './types';
|
|
23
|
+
import { HEADER_ROW, RESULT_COL } from './render-testcase-sheet';
|
|
24
|
+
|
|
25
|
+
const MANUAL_SHEET = 'Manual';
|
|
26
|
+
/** veryHidden: Excel's UI cannot unhide it, so a tester cannot edit the guard by accident. */
|
|
27
|
+
export const STAMP_SHEET = '_sungen_manual';
|
|
28
|
+
/** L..P — the tester-owned columns of a variant sub-row. */
|
|
29
|
+
const CARRIED_COLS = [RESULT_COL, RESULT_COL + 1, RESULT_COL + 2, RESULT_COL + 3, RESULT_COL + 4];
|
|
30
|
+
const NOTE_INDEX = 4;
|
|
31
|
+
const RESET_MARKER = '[sungen] Procedure changed since this result was recorded';
|
|
32
|
+
|
|
33
|
+
interface CarriedCell { value: ExcelJS.CellValue; numFmt?: string }
|
|
34
|
+
|
|
35
|
+
interface PriorRow { cells: CarriedCell[] }
|
|
36
|
+
|
|
37
|
+
export interface PriorManualResults {
|
|
38
|
+
rows: Map<string, PriorRow>;
|
|
39
|
+
/** Later copies of a ref already read — only the first is carried, so these are reported. */
|
|
40
|
+
duplicates: { ref: string; result: string }[];
|
|
41
|
+
/** `ref → fingerprint` from the stamp sheet; null when the file predates #756. */
|
|
42
|
+
stamps: Map<string, string> | null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ManualCarryReport {
|
|
46
|
+
/** Carried with a matching fingerprint. */
|
|
47
|
+
carried: number;
|
|
48
|
+
/** Carried from a file with no stamp — no drift check was possible. */
|
|
49
|
+
unchecked: number;
|
|
50
|
+
/** Result reset to Pending because the procedure changed. */
|
|
51
|
+
reset: string[];
|
|
52
|
+
/** Prior rows with an entry that no longer match any manual variant. */
|
|
53
|
+
notPlaced: { ref: string; result: string }[];
|
|
54
|
+
/** Prior rows with an entry that repeat a ref above them — not carried. */
|
|
55
|
+
duplicates: { ref: string; result: string }[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isFormula(v: ExcelJS.CellValue): boolean {
|
|
59
|
+
return typeof v === 'object' && v !== null && ('formula' in v || 'sharedFormula' in v);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function cellText(v: ExcelJS.CellValue): string {
|
|
63
|
+
if (v === null || v === undefined) return '';
|
|
64
|
+
if (v instanceof Date) return isNaN(v.getTime()) ? '' : v.toISOString().slice(0, 10);
|
|
65
|
+
if (typeof v === 'object') {
|
|
66
|
+
if ('richText' in v) return v.richText.map((r) => r.text).join('').trim();
|
|
67
|
+
if ('text' in v) return String(v.text).trim();
|
|
68
|
+
if ('result' in v) return cellText(v.result as ExcelJS.CellValue);
|
|
69
|
+
}
|
|
70
|
+
return String(v).trim();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* ExcelJS reads a formula's text result under a date format as an Invalid Date. Drop that
|
|
75
|
+
* cached result — Excel recomputes it on open — instead of writing NaN back into the file.
|
|
76
|
+
*/
|
|
77
|
+
function readable(v: ExcelJS.CellValue): ExcelJS.CellValue {
|
|
78
|
+
if (isFormula(v)) {
|
|
79
|
+
const { result, ...formula } = v as ExcelJS.CellFormulaValue;
|
|
80
|
+
if (result instanceof Date && isNaN(result.getTime())) return formula as ExcelJS.CellValue;
|
|
81
|
+
}
|
|
82
|
+
return v;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** A row still at the rendered default carries nothing. */
|
|
86
|
+
function isUntouched(cells: CarriedCell[]): boolean {
|
|
87
|
+
const [result, ...rest] = cells.map((c) => cellText(c.value));
|
|
88
|
+
return (result === '' || result === 'Pending') && rest.every((t) => t === '');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Variant sub-rows of a Manual sheet, in row order: `[ref, row]`. Either signal is enough:
|
|
93
|
+
* the renderer puts variants on outline level 1, and a parent's Result is a roll-up formula.
|
|
94
|
+
* Outline levels are lost by some spreadsheet tools on save, and a tester can paste a
|
|
95
|
+
* formula into a variant's Result, so relying on one alone would hide the row silently.
|
|
96
|
+
*/
|
|
97
|
+
function variantRows(ws: ExcelJS.Worksheet): [string, ExcelJS.Row][] {
|
|
98
|
+
const out: [string, ExcelJS.Row][] = [];
|
|
99
|
+
for (let r = HEADER_ROW + 1; r <= ws.rowCount; r++) {
|
|
100
|
+
const row = ws.getRow(r);
|
|
101
|
+
const ref = cellText(row.getCell(1).value);
|
|
102
|
+
if (ref && (row.outlineLevel === 1 || !isFormula(row.getCell(RESULT_COL).value))) out.push([ref, row]);
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Read the Manual results of the workbook about to be overwritten. Returns null when there is
|
|
109
|
+
* no such file. A file that exists but cannot be read THROWS: overwriting it would destroy the
|
|
110
|
+
* very record this step exists to keep.
|
|
111
|
+
*/
|
|
112
|
+
export async function readPriorManualResults(file: string): Promise<PriorManualResults | null> {
|
|
113
|
+
if (!fs.existsSync(file)) return null;
|
|
114
|
+
const wb = new ExcelJS.Workbook();
|
|
115
|
+
try {
|
|
116
|
+
await wb.xlsx.readFile(file);
|
|
117
|
+
} catch (e) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`cannot read ${file} to carry its Manual results (${(e as Error).message}). ` +
|
|
120
|
+
'Close it in Excel, or move it aside to start a fresh workbook, then re-run.',
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Every workbook sungen writes has a Manual sheet, even an empty one. Without it the
|
|
125
|
+
// tester's results cannot be located — carrying nothing would look like a clean run.
|
|
126
|
+
const ws = wb.getWorksheet(MANUAL_SHEET);
|
|
127
|
+
if (!ws) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`${file} has no "${MANUAL_SHEET}" sheet, so its Manual results cannot be carried. ` +
|
|
130
|
+
`Rename the sheet back to "${MANUAL_SHEET}", or move the file aside to start a fresh workbook, then re-run.`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const rows = new Map<string, PriorRow>();
|
|
135
|
+
const duplicates: PriorManualResults['duplicates'] = [];
|
|
136
|
+
for (const [ref, row] of variantRows(ws)) {
|
|
137
|
+
const cells = CARRIED_COLS.map((c) => {
|
|
138
|
+
const cell = row.getCell(c);
|
|
139
|
+
return { value: readable(cell.value), ...(cell.numFmt ? { numFmt: cell.numFmt } : {}) };
|
|
140
|
+
});
|
|
141
|
+
if (!rows.has(ref)) rows.set(ref, { cells });
|
|
142
|
+
else if (!isUntouched(cells)) duplicates.push({ ref, result: cellText(cells[0].value) || '(no result)' });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const stampWs = wb.getWorksheet(STAMP_SHEET);
|
|
146
|
+
let stamps: Map<string, string> | null = null;
|
|
147
|
+
if (stampWs) {
|
|
148
|
+
stamps = new Map();
|
|
149
|
+
stampWs.eachRow((row, n) => {
|
|
150
|
+
if (n > 1) stamps!.set(cellText(row.getCell(1).value), cellText(row.getCell(2).value));
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return { rows, duplicates, stamps };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Note for a reset row: what was invalidated, then whatever the tester had written. A note
|
|
158
|
+
* that is a hyperlink (evidence, a defect ticket) stays one — only its text gains the marker.
|
|
159
|
+
*/
|
|
160
|
+
function resetNote(prior: CarriedCell[]): ExcelJS.CellValue {
|
|
161
|
+
const [result, date, executor] = prior.map((c) => cellText(c.value));
|
|
162
|
+
const was = [result, date, executor].filter(Boolean).join(' · ');
|
|
163
|
+
const header = `${RESET_MARKER} — previous: ${was}. Re-run required.`;
|
|
164
|
+
const note = prior[NOTE_INDEX].value;
|
|
165
|
+
const own = cellText(note);
|
|
166
|
+
if (typeof note === 'object' && note !== null && 'hyperlink' in note) {
|
|
167
|
+
return { ...note, text: `${header}\n${own}` };
|
|
168
|
+
}
|
|
169
|
+
return own ? `${header}\n${own}` : header;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Carry `prior` into the freshly rendered `wb`, then stamp every manual variant's fingerprint.
|
|
174
|
+
* Only the Manual sheet's variant sub-rows are touched.
|
|
175
|
+
*/
|
|
176
|
+
export function carryManualResults(
|
|
177
|
+
wb: ExcelJS.Workbook,
|
|
178
|
+
model: MatrixModel,
|
|
179
|
+
prior: PriorManualResults | null,
|
|
180
|
+
): ManualCarryReport {
|
|
181
|
+
const report: ManualCarryReport = { carried: 0, unchecked: 0, reset: [], notPlaced: [], duplicates: prior?.duplicates ?? [] };
|
|
182
|
+
const fingerprints = new Map(
|
|
183
|
+
model.items.flatMap((i) => i.variants).filter((v) => v.mode === 'manual').map((v) => [v.ref, v.fingerprint]),
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const ws = wb.getWorksheet(MANUAL_SHEET);
|
|
187
|
+
const placed = new Set<string>();
|
|
188
|
+
if (ws && prior) {
|
|
189
|
+
for (const [ref, row] of variantRows(ws)) {
|
|
190
|
+
const before = prior.rows.get(ref);
|
|
191
|
+
if (!before) continue;
|
|
192
|
+
placed.add(ref);
|
|
193
|
+
if (isUntouched(before.cells)) continue;
|
|
194
|
+
|
|
195
|
+
const stamped = prior.stamps?.get(ref);
|
|
196
|
+
// Only a recorded result is a claim about the procedure. A row with no result (a
|
|
197
|
+
// blocker note, or one already reset) is carried as-is, so a second procedure
|
|
198
|
+
// change does not stack a second marker on it.
|
|
199
|
+
const hasResult = !['', 'Pending'].includes(cellText(before.cells[0].value));
|
|
200
|
+
if (stamped !== undefined && stamped !== fingerprints.get(ref) && hasResult) {
|
|
201
|
+
row.getCell(RESULT_COL).value = 'Pending';
|
|
202
|
+
row.getCell(CARRIED_COLS[NOTE_INDEX]).value = resetNote(before.cells);
|
|
203
|
+
report.reset.push(ref);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
CARRIED_COLS.forEach((c, i) => {
|
|
208
|
+
const cell = row.getCell(c);
|
|
209
|
+
cell.value = before.cells[i].value;
|
|
210
|
+
if (before.cells[i].numFmt) cell.numFmt = before.cells[i].numFmt!;
|
|
211
|
+
});
|
|
212
|
+
if (stamped === undefined) report.unchecked++;
|
|
213
|
+
else report.carried++;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (prior) {
|
|
218
|
+
for (const [ref, before] of prior.rows) {
|
|
219
|
+
if (!placed.has(ref) && !isUntouched(before.cells)) {
|
|
220
|
+
report.notPlaced.push({ ref, result: cellText(before.cells[0].value) || '(no result)' });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const stampWs = wb.addWorksheet(STAMP_SHEET, { state: 'veryHidden' });
|
|
226
|
+
stampWs.addRow(['ref', 'fingerprint']);
|
|
227
|
+
for (const [ref, fp] of fingerprints) stampWs.addRow([ref, fp]);
|
|
228
|
+
return report;
|
|
229
|
+
}
|
|
@@ -16,7 +16,8 @@ import { loadPlaywrightReport } from '../playwright-report-parser';
|
|
|
16
16
|
import { mergeFeatureAndSpec } from '../scenario-merger';
|
|
17
17
|
import { getPackageVersion } from '../package-info';
|
|
18
18
|
import { writeCsv } from '../csv-exporter';
|
|
19
|
-
import { writeXlsx } from '../xlsx-exporter';
|
|
19
|
+
import { deliverableXlsxPath, writeXlsx } from '../xlsx-exporter';
|
|
20
|
+
import { carryManualResults, ManualCarryReport, readPriorManualResults } from './carry-manual-results';
|
|
20
21
|
import { loadPerfConfig, perfVerdict } from '../../harness/perf';
|
|
21
22
|
import { loadDeliveryMap, writeDeliveryMap } from './map-loader';
|
|
22
23
|
import { mapContentFingerprint } from './fingerprint';
|
|
@@ -163,13 +164,18 @@ export async function writeMatrixDeliverables(
|
|
|
163
164
|
paths: MatrixTargetPaths,
|
|
164
165
|
model: MatrixModel,
|
|
165
166
|
format: MatrixFormat = 'xlsx',
|
|
166
|
-
): Promise<{ csvPath?: string; xlsxPath?: string }> {
|
|
167
|
-
const out: { csvPath?: string; xlsxPath?: string } = {};
|
|
167
|
+
): Promise<{ csvPath?: string; xlsxPath?: string; manualCarry?: ManualCarryReport }> {
|
|
168
|
+
const out: { csvPath?: string; xlsxPath?: string; manualCarry?: ManualCarryReport } = {};
|
|
169
|
+
const xlsx = format === 'xlsx' || format === 'both';
|
|
170
|
+
// Read the workbook about to be overwritten BEFORE writing anything, so the tester's Manual
|
|
171
|
+
// results survive (#756) and a refusal leaves no half-written target behind.
|
|
172
|
+
const prior = xlsx ? await readPriorManualResults(deliverableXlsxPath(paths.cwd, paths.unit)) : null;
|
|
168
173
|
if (format === 'csv' || format === 'both') {
|
|
169
174
|
out.csvPath = writeCsv(paths.cwd, paths.unit, renderMatrixCsv(model));
|
|
170
175
|
}
|
|
171
|
-
if (
|
|
176
|
+
if (xlsx) {
|
|
172
177
|
const wb = renderMatrixXlsx(model, getPackageVersion());
|
|
178
|
+
out.manualCarry = carryManualResults(wb, model, prior);
|
|
173
179
|
out.xlsxPath = await writeXlsx(paths.cwd, paths.unit, wb);
|
|
174
180
|
}
|
|
175
181
|
return out;
|
|
@@ -40,8 +40,25 @@ export function runtimeVarsOf(stepTexts: string[], tags: string[]): Set<string>
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
// `[Email] field` → `Email field` (the visible label + its element type).
|
|
43
|
+
// Only the DSL's own refs: a `"quoted literal"` and a `{{token}}` are data, so their
|
|
44
|
+
// brackets stay (#772) — `"[DRAFT] report"`, `{{q.rows[0].id}}`. Callers render BEFORE
|
|
45
|
+
// substituting values, so a value's brackets (`^[A-Za-z]{3}`) never reach this.
|
|
46
|
+
// A literal opens only where a word does not end: in free text (manual procedures) `5"`
|
|
47
|
+
// is an inch mark, and two of them must not shield the `[Ref]` between them.
|
|
43
48
|
function deRef(text: string): string {
|
|
44
|
-
return text.replace(
|
|
49
|
+
return text.replace(/(?<![\p{L}\p{N}])"[^"]*"|\{\{[^}]*\}\}|\[([^\]]+)\]/gu, (m, ref?: string) => ref ?? m);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Finish a cell rendered with `{{tokens}}` still in it, once the values are substituted
|
|
54
|
+
* (#772: render first, substitute after, so deRef never sees a value). Keeps what
|
|
55
|
+
* `sentence()` did when values went in first: a leading value is capitalised, and a
|
|
56
|
+
* value that already ends a sentence does not gain a second mark (`value..`).
|
|
57
|
+
*/
|
|
58
|
+
export function afterSubstitution(text: string): string {
|
|
59
|
+
if (!text) return text;
|
|
60
|
+
const s = text.charAt(0).toUpperCase() + text.slice(1);
|
|
61
|
+
return s.replace(/([.!?…"])\.$/, '$1');
|
|
45
62
|
}
|
|
46
63
|
|
|
47
64
|
function sentence(text: string): string {
|
|
@@ -115,7 +132,15 @@ export function renderExpected(raw: string): string {
|
|
|
115
132
|
if (RUNTIME_CAPTURE.test(raw) && /\bas\s+\{\{/.test(raw)) return renderAction(raw);
|
|
116
133
|
let s = raw.trim().replace(/^(User|The user)\s+/i, '');
|
|
117
134
|
|
|
135
|
+
// `table match data:` rows (folded in by feature-parser, #771) are the EXPECTED VALUES —
|
|
136
|
+
// `[E2E-01] Q1` is data, not a ref — so only the table's own ref is de-ref'd (#772).
|
|
137
|
+
const md = s.match(/^sees? \[([^\]]+)\]\s*table match(?:es)? data: (.+)$/i);
|
|
138
|
+
if (md) return sentence(`The ${md[1]} table shows these rows: ${md[2]}`);
|
|
139
|
+
|
|
118
140
|
const rules: Array<[RegExp, (m: RegExpMatchArray) => string]> = [
|
|
141
|
+
// tab order: (#771) row 1 is the focused origin, each later row is one Tab press —
|
|
142
|
+
// its cells ARE refs, so they are de-ref'd like any step's.
|
|
143
|
+
[/^sees? tab order: (.+)$/i, (m) => `Pressing Tab moves focus in this order: ${m[1]}`],
|
|
119
144
|
// expect A is (not) B — comparing two captured values (order matters: "is not" first)
|
|
120
145
|
[/^expects? (.+?) is not (.+)$/i, (m) => `${m[1]} differs from ${m[2]}`],
|
|
121
146
|
[/^expects? (.+?) is (.+)$/i, (m) => `${m[1]} equals ${m[2]}`],
|
|
@@ -113,15 +113,20 @@ export function buildApiDetailRows(
|
|
|
113
113
|
* used by the multi-locale delivery aggregator. Pass `null` / empty string
|
|
114
114
|
* to force the base (no suffix) variant.
|
|
115
115
|
*/
|
|
116
|
+
/** Where `writeXlsx` puts a unit's workbook — shared so a reader targets the identical file. */
|
|
117
|
+
export function deliverableXlsxPath(cwd: string, screen: string, locale?: string | null): string {
|
|
118
|
+
return path.join(cwd, 'qa', 'deliverables', `${deliverableBasename(screen, locale)}.xlsx`);
|
|
119
|
+
}
|
|
120
|
+
|
|
116
121
|
export async function writeXlsx(
|
|
117
122
|
cwd: string,
|
|
118
123
|
screen: string,
|
|
119
124
|
wb: ExcelJS.Workbook,
|
|
120
125
|
locale?: string | null,
|
|
121
126
|
): Promise<string> {
|
|
122
|
-
const
|
|
127
|
+
const outPath = deliverableXlsxPath(cwd, screen, locale);
|
|
128
|
+
const outDir = path.dirname(outPath);
|
|
123
129
|
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
|
|
124
|
-
const outPath = path.join(outDir, `${deliverableBasename(screen, locale)}.xlsx`);
|
|
125
130
|
const buffer = await wb.xlsx.writeBuffer();
|
|
126
131
|
const locked = await lockEmbeddedImages(Buffer.from(buffer as ArrayBuffer));
|
|
127
132
|
fs.writeFileSync(outPath, locked);
|
|
@@ -42,7 +42,7 @@ export interface TestFileData {
|
|
|
42
42
|
runtimeData?: boolean; // --runtime-data flag: testData.get() instead of hardcoded values
|
|
43
43
|
screenName?: string; // Screen name for TestDataLoader.load()
|
|
44
44
|
featureFileName?: string; // Feature file name for TestDataLoader.load()
|
|
45
|
-
isParallel?: boolean; //
|
|
45
|
+
isParallel?: boolean; // !@serial: fresh page per test. Parallel is the default; @parallel only names it
|
|
46
46
|
flowMode?: boolean; // flow: cross-screen → longer per-test timeout (bug #243)
|
|
47
47
|
platform?: string; // mobile @platform:* value (mobile|android|ios) → `// sungen:platform=` marker for wdio per-OS routing
|
|
48
48
|
cleanup?: { overlay?: boolean; forms?: boolean; scroll?: boolean; storage?: boolean };
|
|
@@ -65,7 +65,7 @@ export interface ScenarioData {
|
|
|
65
65
|
scenarioName: string;
|
|
66
66
|
steps: Array<{ comment?: string; code: string }>;
|
|
67
67
|
authRole?: string; // Auth role for storage state
|
|
68
|
-
isParallel?: boolean; //
|
|
68
|
+
isParallel?: boolean; // !@serial: use fresh page from fixture
|
|
69
69
|
tags?: string; // Pass-through tags for Playwright { tag: [...] }, e.g. "'@smoke', '@high'"
|
|
70
70
|
casesDataset?: string; // @cases:<dataset> — emit one test() per row of the runtime dataset
|
|
71
71
|
resetTier?: string; // Mobile (appium): scenario-level `@reset:` override; raises (never lowers) the feature tier for this scenario only
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// Persistence oracles (dismissals, pins) hinge on what survives a reinstall.
|
|
3
3
|
await (async () => {
|
|
4
4
|
const __pkg = __resolveAppId();
|
|
5
|
-
|
|
6
|
-
const __appPath =
|
|
5
|
+
// Same reader the reset tiers use — one place decides what "the session has a build file" means.
|
|
6
|
+
const __appPath = __appBuildPath();
|
|
7
7
|
if (!__appPath) {
|
|
8
8
|
throw new Error('reinstall the app: the session must install from a build file — set APP_APK (Android) / IOS_APP (iOS) so the appium:app capability is present.');
|
|
9
9
|
}
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
as contain-text-assertion), substring check on every match — no CSS filter() combinator
|
|
3
3
|
exists on Appium, so this loop replaces it. --}}
|
|
4
4
|
{
|
|
5
|
-
const __items_{{stepCounter}} =
|
|
6
|
-
if (__items_{{stepCounter}}.length === 0) throw new Error('[sungen] Expected at least one [{{escapeQuotes selectorRef}}]');
|
|
5
|
+
const __items_{{stepCounter}} = {{> await-elements mustExist=true}};
|
|
7
6
|
const __expected_{{stepCounter}} = String('{{escapeQuotes expectedText}}').trim();
|
|
8
7
|
for (const __el_{{stepCounter}} of __items_{{stepCounter}}) {
|
|
9
8
|
const __sources_{{stepCounter}} = driver.isIOS
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{{!-- Q2 all-card structural assertion (mobile): every container element must hold the child.
|
|
2
2
|
WebdriverIO's $$() returns a live element ARRAY, not a lazy Locator — there is no `.nth()`
|
|
3
3
|
(that is Playwright's API). Enumerate by index and chain off the array element itself via
|
|
4
|
-
`.$()` (WDIO findElementFromElement).
|
|
4
|
+
`.$()` (WDIO findElementFromElement).
|
|
5
|
+
|
|
6
|
+
`appium-selector-expr`, not a hand-quoted `appium-selector`: the expr partial emits its own
|
|
7
|
+
quotes, and it is the only form that can carry a per-OS `android:`/`ios:` entry (it renders
|
|
8
|
+
that as a runtime ternary). The raw partial silently emitted the Android side alone, so an
|
|
9
|
+
iOS run drove Android selectors; wrapping the expr output in quotes of our own instead
|
|
10
|
+
produces `$$(''~card'')`, a syntax error that costs the WHOLE .spec.ts, not just this step. --}}
|
|
5
11
|
{
|
|
6
|
-
const {{containerVar}} =
|
|
7
|
-
if ({{containerVar}}.length === 0) {
|
|
8
|
-
throw new Error('[sungen] Expected at least one [{{escapeQuotes selectorRef}}]');
|
|
9
|
-
}
|
|
12
|
+
const {{containerVar}} = {{> await-elements mustExist=true}};
|
|
10
13
|
for (let {{containerIndexVar}} = 0; {{containerIndexVar}} < {{containerVar}}.length; {{containerIndexVar}}++) {
|
|
11
14
|
await expect({{#with child}}{{> locator parentLocator=(add (add (add ../containerVar "[") ../containerIndexVar) "]")}}{{/with}}).toBeDisplayed();
|
|
12
15
|
}
|
package/src/generators/test-generator/adapters/appium/templates/steps/assertions/count-assertion.hbs
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
{{!-- `appium-selector-expr` emits its own quotes and is the only form that can carry a per-OS
|
|
2
|
+
`android:`/`ios:` entry (rendered as a runtime ternary). The raw partial silently emitted the
|
|
3
|
+
Android side alone; quoting the expr output by hand instead produces `$$(''~card'')`, a syntax
|
|
4
|
+
error that costs the whole .spec.ts. --}}
|
|
5
|
+
await expect($$({{> appium-selector-expr}})).toBeElementsArrayOfSize({{expectedCount}});
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
has no such exemption — absent is not disabled.) --}}
|
|
14
14
|
{
|
|
15
15
|
const __expected = String('{{escapeQuotes dataValue}}').trim();
|
|
16
|
-
const __items =
|
|
16
|
+
const __items = {{> await-elements mustExist=true}};
|
|
17
17
|
let __found = false;
|
|
18
18
|
for (const __el of __items) {
|
|
19
19
|
const __sources = driver.isIOS
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
{{#if name}}
|
|
6
6
|
{
|
|
7
7
|
const __expected = String('{{escapeQuotes dataValue}}').trim();
|
|
8
|
-
const __items =
|
|
8
|
+
const __items = {{> await-elements useRoleName=true mustExist=true}};
|
|
9
9
|
let __found = false;
|
|
10
10
|
for (const __el of __items) {
|
|
11
11
|
const __s = driver.isIOS ? [await __el.getAttribute('label'), await __el.getAttribute('value')] : [await __el.getText(), await __el.getAttribute('content-desc')];
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
is hand-rolled. Assert none of the matching elements is displayed. --}}
|
|
4
4
|
{
|
|
5
5
|
const __expected = String('{{escapeQuotes dataValue}}').trim();
|
|
6
|
-
const __items =
|
|
6
|
+
const __items = {{> await-elements}};
|
|
7
7
|
for (const __el of __items) {
|
|
8
8
|
const __sources = driver.isIOS
|
|
9
9
|
? [await __el.getAttribute('label'), await __el.getAttribute('value')]
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
{{#if name}}
|
|
7
7
|
{
|
|
8
8
|
const __expected = String('{{escapeQuotes dataValue}}').trim();
|
|
9
|
-
const __items =
|
|
9
|
+
const __items = {{> await-elements useRoleName=true}};
|
|
10
10
|
for (const __el of __items) {
|
|
11
11
|
const __s = driver.isIOS ? [await __el.getAttribute('label'), await __el.getAttribute('value')] : [await __el.getText(), await __el.getAttribute('content-desc')];
|
|
12
12
|
if (String(__s.find(s => s && String(s).trim() !== '') ?? '').trim() === __expected) await expect(__el).not.toBeDisplayed();
|
|
@@ -9,12 +9,18 @@
|
|
|
9
9
|
Flutter screen; UiSelector has no OR, hence the xpath. iOS scatters it across
|
|
10
10
|
label/value, which the predicate already ORs.
|
|
11
11
|
|
|
12
|
+
SCOPE (#747): an `in [Parent] <type>` clause chains this under the parent element. The android
|
|
13
|
+
xpath must go RELATIVE (`.//`) to do so — a child xpath beginning with `//` is ABSOLUTE in
|
|
14
|
+
Appium even when chained, so keeping it would emit code that LOOKS scoped and searches the whole
|
|
15
|
+
screen, which is the same silent widening the scope clause exists to prevent. The iOS predicate
|
|
16
|
+
carries no such trap and chains as written.
|
|
17
|
+
|
|
12
18
|
The expected value must stay a standalone quoted literal so the runtime-data transform
|
|
13
19
|
can swap it for testData.get(...). --}}
|
|
14
20
|
{
|
|
15
21
|
const __expected = String('{{escapeQuotes dataValue}}').trim();
|
|
16
22
|
const __sel = driver.isIOS
|
|
17
23
|
? `-ios predicate string:label == "${__expected}" OR value == "${__expected}"`
|
|
18
|
-
:
|
|
19
|
-
await expect($(__sel)).toBeDisplayed();
|
|
24
|
+
: `{{#if parentLocator}}.{{/if}}//*[@text="${__expected}" or @content-desc="${__expected}"]`;
|
|
25
|
+
await expect({{#if parentLocator}}{{parentLocator}}.$(__sel){{else}}$(__sel){{/if}}).toBeDisplayed();
|
|
20
26
|
}
|
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
{{!-- Count the elements matching the list locator (mobile has no role="listitem").
|
|
2
|
-
|
|
1
|
+
{{!-- Count the elements matching the list locator (mobile has no role="listitem").
|
|
2
|
+
`appium-selector-expr` emits its own quotes and is the only form that can carry a per-OS
|
|
3
|
+
`android:`/`ios:` entry; quoting it by hand produced `$$(''~card'')`, a syntax error that
|
|
4
|
+
takes the whole .spec.ts down. --}}
|
|
5
|
+
await expect($$({{> appium-selector-expr}})).toBeElementsArrayOfSize({{expectedCount}});
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
house style. --}}
|
|
8
8
|
{
|
|
9
9
|
const __expected = String('{{escapeQuotes dataValue}}').trim();
|
|
10
|
-
const __items =
|
|
10
|
+
const __items = {{> await-elements mustExist=true}};
|
|
11
11
|
let __el: WebdriverIO.Element | undefined;
|
|
12
12
|
for (const __c of __items) {
|
|
13
13
|
const __s = driver.isIOS ? [await __c.getAttribute('label'), await __c.getAttribute('value')] : [await __c.getText(), await __c.getAttribute('content-desc')];
|