@sun-asterisk/sungen 3.2.0 → 3.2.2-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capabilities/context-router.d.ts +11 -2
- package/dist/capabilities/context-router.d.ts.map +1 -1
- package/dist/capabilities/context-router.js +10 -3
- package/dist/capabilities/context-router.js.map +1 -1
- package/dist/capabilities/discover.js +1 -1
- package/dist/capabilities/discover.js.map +1 -1
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +5 -3
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +31 -0
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/exporters/feature-parser.d.ts +25 -0
- package/dist/exporters/feature-parser.d.ts.map +1 -1
- package/dist/exporters/feature-parser.js +59 -0
- package/dist/exporters/feature-parser.js.map +1 -1
- package/dist/exporters/types.d.ts +38 -0
- package/dist/exporters/types.d.ts.map +1 -1
- package/dist/exporters/xlsx-exporter.d.ts +31 -2
- package/dist/exporters/xlsx-exporter.d.ts.map +1 -1
- package/dist/exporters/xlsx-exporter.js +144 -1
- package/dist/exporters/xlsx-exporter.js.map +1 -1
- package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts +54 -0
- package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts.map +1 -0
- package/dist/generators/test-generator/adapters/appium/appium-adapter.js +52 -0
- package/dist/generators/test-generator/adapters/appium/appium-adapter.js.map +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/after-all.hbs +8 -0
- package/dist/generators/test-generator/adapters/appium/templates/after-each.hbs +8 -0
- package/dist/generators/test-generator/adapters/appium/templates/before-all.hbs +8 -0
- package/dist/generators/test-generator/adapters/appium/templates/before-each.hbs +8 -0
- package/dist/generators/test-generator/adapters/appium/templates/imports.hbs +8 -0
- package/dist/generators/test-generator/adapters/appium/templates/scenario.hbs +8 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/alert-accept-action.hbs +4 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/alert-dismiss-action.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/alert-fill-action.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/check-action.hbs +10 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/clear-action.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-action.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-element-with-text.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-select-action.hbs +4 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/dismiss-action.hbs +3 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/double-click-action.hbs +6 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/drag-action.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/expand-action.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/fill-action.hbs +14 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/fill-editor-action.hbs +12 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/frame-enter-action.hbs +6 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/frame-exit-action.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/hide-keyboard-action.hbs +4 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/hover-action.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/keyboard-global-action.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/press-action.hbs +4 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/radio-select-action.hbs +4 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/scroll-action.hbs +19 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/select-action.hbs +5 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/table-action-in-row.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/toggle-action.hbs +3 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/uncheck-action.hbs +8 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/unknown-element-action.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/upload-action.hbs +3 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/wait-for-page.hbs +3 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/wait-for-role-with-data.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/wait-for-role.hbs +3 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/alert-text-assertion.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/attribute-assertion.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/checked-assertion.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/clipboard-text-assertion.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/column-cell-assertion.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/contain-text-assertion.hbs +14 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/count-assertion.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-assertion.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/empty-assertion.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/enabled-assertion.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/focused-assertion.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/have-text-assertion.hbs +15 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/have-value-assertion.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/is-hidden-assertion.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +12 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/list-item-count-assertion.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/loading-assertion.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/not-checked-assertion.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/page-assertion.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/route-assertion.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/selected-assertion.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/sorted-assertion.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/table-column-exists.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/table-empty.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/table-match-data.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/table-row-count.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/table-row-exists.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/table-row-not-exists.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/background-action.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/grant-permission-action.hbs +11 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/long-press-action.hbs +5 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/open-notifications-action.hbs +3 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/pinch-zoom-action.hbs +5 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/pull-to-refresh-action.hbs +5 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/rotate-action.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/set-clipboard-action.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/set-geolocation-action.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/swipe-action.hbs +5 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/tap-top-action.hbs +24 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/navigation/navigation.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/navigation/wait-for-element-with-text.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/navigation/wait-for-element.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/navigation/wait-timeout.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/partials/appium-selector-expr.hbs +8 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/partials/appium-selector.hbs +15 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +8 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/setup/application-running.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/setup/clear-auth.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/setup/clear-browser-state.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/setup/clear-database.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/setup/user-login-todo.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +226 -0
- package/dist/generators/test-generator/adapters/index.d.ts +1 -0
- package/dist/generators/test-generator/adapters/index.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/index.js +9 -1
- package/dist/generators/test-generator/adapters/index.js.map +1 -1
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +3 -2
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/step-mapper.d.ts +1 -0
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +7 -37
- 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 +13 -1
- package/dist/generators/test-generator/template-engine.js.map +1 -1
- package/dist/harness/audit.d.ts +16 -2
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +74 -11
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/capability-plan.d.ts +2 -0
- package/dist/harness/capability-plan.d.ts.map +1 -1
- package/dist/harness/capability-plan.js +4 -1
- package/dist/harness/capability-plan.js.map +1 -1
- package/dist/harness/catalog/drivers.yaml +1 -1
- package/dist/harness/flow-check.d.ts.map +1 -1
- package/dist/harness/flow-check.js +13 -4
- package/dist/harness/flow-check.js.map +1 -1
- package/dist/harness/parse.d.ts +2 -0
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +13 -2
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/quality-gates.d.ts +6 -0
- package/dist/harness/quality-gates.d.ts.map +1 -1
- package/dist/harness/quality-gates.js +15 -1
- package/dist/harness/quality-gates.js.map +1 -1
- package/dist/harness/sensors.d.ts +27 -0
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +91 -21
- package/dist/harness/sensors.js.map +1 -1
- package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
- package/dist/orchestrator/ai-rules-updater.js +9 -0
- package/dist/orchestrator/ai-rules-updater.js.map +1 -1
- package/dist/orchestrator/templates/ai-instructions/claude-agent-generator.md +44 -0
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +18 -1
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-mobile.md +184 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-delivery.md +27 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +2 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-mobile-gestures.md +109 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-fix-mobile.md +316 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +2 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +2 -1
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mobile.md +184 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +27 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +2 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-mobile-gestures.md +109 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix-mobile.md +316 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +2 -0
- package/dist/orchestrator/templates/env.appium.example +25 -0
- package/dist/orchestrator/templates/specs-api.d.ts +7 -0
- package/dist/orchestrator/templates/specs-api.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-api.js +13 -2
- package/dist/orchestrator/templates/specs-api.js.map +1 -1
- package/dist/orchestrator/templates/specs-api.ts +13 -2
- package/dist/orchestrator/templates/specs-pw-shape-reporter.ts +92 -0
- package/dist/orchestrator/templates/wdio.conf.ts +295 -0
- package/dist/utils/selector-types.d.ts +1 -1
- package/dist/utils/selector-types.d.ts.map +1 -1
- package/dist/utils/selector-types.js +5 -0
- package/dist/utils/selector-types.js.map +1 -1
- package/package.json +4 -4
- package/src/capabilities/context-router.ts +15 -3
- package/src/capabilities/discover.ts +1 -1
- package/src/cli/commands/audit.ts +5 -3
- package/src/cli/commands/delivery.ts +32 -2
- package/src/exporters/feature-parser.ts +57 -0
- package/src/exporters/types.ts +38 -0
- package/src/exporters/xlsx-exporter.ts +176 -2
- package/src/generators/test-generator/adapters/appium/appium-adapter.ts +57 -0
- package/src/generators/test-generator/adapters/appium/templates/after-all.hbs +8 -0
- package/src/generators/test-generator/adapters/appium/templates/after-each.hbs +8 -0
- package/src/generators/test-generator/adapters/appium/templates/before-all.hbs +8 -0
- package/src/generators/test-generator/adapters/appium/templates/before-each.hbs +8 -0
- package/src/generators/test-generator/adapters/appium/templates/imports.hbs +8 -0
- package/src/generators/test-generator/adapters/appium/templates/scenario.hbs +8 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/alert-accept-action.hbs +4 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/alert-dismiss-action.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/alert-fill-action.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/check-action.hbs +10 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/clear-action.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-action.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-element-with-text.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-select-action.hbs +4 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/dismiss-action.hbs +3 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/double-click-action.hbs +6 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/drag-action.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/expand-action.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/fill-action.hbs +14 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/fill-editor-action.hbs +12 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/frame-enter-action.hbs +6 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/frame-exit-action.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/hide-keyboard-action.hbs +4 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/hover-action.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/keyboard-global-action.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/press-action.hbs +4 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/radio-select-action.hbs +4 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/scroll-action.hbs +19 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/select-action.hbs +5 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/table-action-in-row.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/toggle-action.hbs +3 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/uncheck-action.hbs +8 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/unknown-element-action.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/upload-action.hbs +3 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/wait-for-page.hbs +3 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/wait-for-role-with-data.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/wait-for-role.hbs +3 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/alert-text-assertion.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/attribute-assertion.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/checked-assertion.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/clipboard-text-assertion.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/column-cell-assertion.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/contain-text-assertion.hbs +14 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/count-assertion.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-assertion.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/empty-assertion.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/enabled-assertion.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/focused-assertion.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/have-text-assertion.hbs +15 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/have-value-assertion.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/is-hidden-assertion.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +12 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/list-item-count-assertion.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/loading-assertion.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/not-checked-assertion.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/page-assertion.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/route-assertion.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/selected-assertion.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/sorted-assertion.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/table-column-exists.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/table-empty.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/table-match-data.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/table-row-count.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/table-row-exists.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/table-row-not-exists.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/background-action.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/grant-permission-action.hbs +11 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/long-press-action.hbs +5 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/open-notifications-action.hbs +3 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/pinch-zoom-action.hbs +5 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/pull-to-refresh-action.hbs +5 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/rotate-action.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/set-clipboard-action.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/set-geolocation-action.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/swipe-action.hbs +5 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/tap-top-action.hbs +24 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/navigation/navigation.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/navigation/wait-for-element-with-text.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/navigation/wait-for-element.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/navigation/wait-timeout.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/partials/appium-selector-expr.hbs +8 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/partials/appium-selector.hbs +15 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +8 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/setup/application-running.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/setup/clear-auth.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/setup/clear-browser-state.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/setup/clear-database.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/setup/user-login-todo.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +226 -0
- package/src/generators/test-generator/adapters/index.ts +7 -0
- package/src/generators/test-generator/code-generator.ts +3 -2
- package/src/generators/test-generator/step-mapper.ts +8 -5
- package/src/generators/test-generator/template-engine.ts +13 -1
- package/src/harness/audit.ts +84 -14
- package/src/harness/capability-plan.ts +5 -2
- package/src/harness/catalog/drivers.yaml +1 -1
- package/src/harness/flow-check.ts +13 -4
- package/src/harness/parse.ts +15 -2
- package/src/harness/quality-gates.ts +14 -1
- package/src/harness/sensors.ts +110 -22
- package/src/orchestrator/ai-rules-updater.ts +9 -0
- package/src/orchestrator/templates/ai-instructions/claude-agent-generator.md +44 -0
- package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +18 -1
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mobile.md +184 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-delivery.md +27 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +2 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-mobile-gestures.md +109 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-selector-fix-mobile.md +316 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +2 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +2 -1
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mobile.md +184 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +27 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +2 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-mobile-gestures.md +109 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix-mobile.md +316 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +2 -0
- package/src/orchestrator/templates/env.appium.example +25 -0
- package/src/orchestrator/templates/specs-api.ts +13 -2
- package/src/orchestrator/templates/specs-pw-shape-reporter.ts +92 -0
- package/src/orchestrator/templates/wdio.conf.ts +295 -0
- package/src/utils/selector-types.ts +5 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebdriverIO reporter that writes a **Playwright-JSON-shaped** result file per spec, so the
|
|
3
|
+
* existing sungen delivery/dashboard parser (playwright-report-parser.ts) consumes mobile runs
|
|
4
|
+
* unchanged. Output: `<genDir>/<basename>-test-result.<platform>[.<SUNGEN_ENV>].json`.
|
|
5
|
+
*
|
|
6
|
+
* The `<platform>` segment (android|ios, from the session capabilities) is REQUIRED for dual-target:
|
|
7
|
+
* with MOBILE_PLATFORM=both one @platform:mobile spec runs on BOTH capabilities — two workers, two
|
|
8
|
+
* reporter instances — and without a platform component they'd overwrite the same file (the last
|
|
9
|
+
* platform silently erasing the first). Delivery/dashboard discover per-platform variants.
|
|
10
|
+
*
|
|
11
|
+
* Wired in wdio.conf.ts: reporters: ['spec', [PwShapeReporter, {}]].
|
|
12
|
+
*/
|
|
13
|
+
import WDIOReporter from '@wdio/reporter';
|
|
14
|
+
import * as fs from 'node:fs';
|
|
15
|
+
import * as path from 'node:path';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
|
|
18
|
+
interface Rec { title: string; status: string; error?: string; startTime: string }
|
|
19
|
+
|
|
20
|
+
export default class PwShapeReporter extends WDIOReporter {
|
|
21
|
+
private byFeature: Record<string, Rec[]> = {};
|
|
22
|
+
|
|
23
|
+
constructor(options: Record<string, unknown>) {
|
|
24
|
+
super({ ...options, stdout: false });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private add(test: { title?: string; parent?: string; error?: { message?: string } }, status: string): void {
|
|
28
|
+
const feature = test.parent || 'Tests';
|
|
29
|
+
(this.byFeature[feature] ||= []).push({
|
|
30
|
+
title: test.title || '',
|
|
31
|
+
status,
|
|
32
|
+
error: test.error?.message,
|
|
33
|
+
startTime: new Date().toISOString(),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
onTestPass(test: any): void { this.add(test, 'passed'); }
|
|
38
|
+
onTestFail(test: any): void { this.add(test, 'failed'); }
|
|
39
|
+
onTestSkip(test: any): void { this.add(test, 'skipped'); }
|
|
40
|
+
|
|
41
|
+
onRunnerEnd(runner: any): void {
|
|
42
|
+
const specUrl: string | undefined = runner?.specs?.[0];
|
|
43
|
+
if (!specUrl) return;
|
|
44
|
+
const specPath = specUrl.startsWith('file:') ? fileURLToPath(specUrl) : specUrl;
|
|
45
|
+
const dir = path.dirname(specPath);
|
|
46
|
+
const base = path.basename(specPath).replace(/\.spec\.(ts|js)$/, '');
|
|
47
|
+
const env = process.env.SUNGEN_ENV;
|
|
48
|
+
|
|
49
|
+
// Build the runner label shown as "Test Environment": platform + OS version + the
|
|
50
|
+
// actual device the test ran on (e.g. "Android 16 (Appium) — emulator-5554"). The
|
|
51
|
+
// device — not the app activity — is what identifies the test environment.
|
|
52
|
+
const caps = (runner?.capabilities || {}) as Record<string, unknown>;
|
|
53
|
+
const cap = (k: string): string | undefined => {
|
|
54
|
+
const v = caps[k] ?? caps[`appium:${k}`];
|
|
55
|
+
return typeof v === 'string' && v ? v : typeof v === 'number' ? String(v) : undefined;
|
|
56
|
+
};
|
|
57
|
+
const platformName = cap('platformName') || 'Android';
|
|
58
|
+
// Per-platform filename — each capability's worker writes its OWN file so a both-OS run keeps
|
|
59
|
+
// both results: <base>-test-result.<android|ios>[.<env>].json
|
|
60
|
+
const platformSlug = platformName.toLowerCase() === 'ios' ? 'ios' : 'android';
|
|
61
|
+
const outName = env
|
|
62
|
+
? `${base}-test-result.${platformSlug}.${env}.json`
|
|
63
|
+
: `${base}-test-result.${platformSlug}.json`;
|
|
64
|
+
const version = cap('platformVersion') || cap('deviceApiLevel');
|
|
65
|
+
const device = cap('deviceModel') || cap('deviceName') || cap('udid') || cap('deviceUDID');
|
|
66
|
+
const runnerLabel = `${platformName}${version ? ` ${version}` : ''} (Appium)`;
|
|
67
|
+
const platform = device ? `${runnerLabel} — ${device}` : runnerLabel;
|
|
68
|
+
const report = {
|
|
69
|
+
config: { projects: [{ name: platform }] },
|
|
70
|
+
suites: Object.entries(this.byFeature).map(([feature, tests]) => ({
|
|
71
|
+
title: feature,
|
|
72
|
+
specs: tests.map((t) => ({
|
|
73
|
+
title: t.title,
|
|
74
|
+
tests: [
|
|
75
|
+
{
|
|
76
|
+
projectName: platform,
|
|
77
|
+
results: [
|
|
78
|
+
{
|
|
79
|
+
status: t.status,
|
|
80
|
+
startTime: t.startTime,
|
|
81
|
+
...(t.error ? { error: { message: t.error } } : {}),
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
})),
|
|
87
|
+
})),
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
fs.writeFileSync(path.join(dir, outName), JSON.stringify(report, null, 2));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebdriverIO runner config for sungen mobile (Appium) tests.
|
|
3
|
+
* Mobile equivalent of playwright.config.ts. Run with: npm run test:mobile
|
|
4
|
+
*
|
|
5
|
+
* Capabilities default to the app configured at `sungen init --mobile`, but every value can be
|
|
6
|
+
* overridden at runtime via env vars — no need to edit this file to target another app/device:
|
|
7
|
+
* APP_PACKAGE=com.other.app ANDROID_UDID=emulator-5556 npm run test:mobile
|
|
8
|
+
* (the launch activity is auto-detected from APP_PACKAGE; set APP_ACTIVITY only to pin a non-standard one)
|
|
9
|
+
* APP_APK=/path/to/app.apk npm run test:mobile # install + run from an .apk (Appium derives pkg/activity)
|
|
10
|
+
*
|
|
11
|
+
* iOS Simulator (opt in with MOBILE_PLATFORM=ios; needs Xcode + the xcuitest driver):
|
|
12
|
+
* MOBILE_PLATFORM=ios IOS_BUNDLE_ID=com.example.app npm run test:mobile # app already installed on the sim
|
|
13
|
+
* MOBILE_PLATFORM=ios IOS_APP=/path/MyApp.app npm run test:mobile # Appium installs a Simulator build
|
|
14
|
+
* (+ optional IOS_DEVICE='iPhone 16' IOS_VERSION='18.0' IOS_UDID=<sim-udid>)
|
|
15
|
+
*
|
|
16
|
+
* The @wdio/appium-service auto-starts a local Appium server (auto-detecting the uiautomator2 / xcuitest driver).
|
|
17
|
+
*/
|
|
18
|
+
/// <reference types="node" />
|
|
19
|
+
/// <reference types="@wdio/globals/types" />
|
|
20
|
+
// Load .env.appium (if present) into process.env FIRST — so every override below (ANDROID_HOME / JAVA_HOME /
|
|
21
|
+
// ANDROID_UDID / APP_PACKAGE / IOS_BUNDLE_ID / MOBILE_PLATFORM / SUNGEN_ENV …) can come from a dedicated
|
|
22
|
+
// .env.appium file instead of shell exports. A separate file (not the generic .env) keeps Appium config from
|
|
23
|
+
// clashing with other .env vars (e.g. FIGMA_PAT). dotenv is a dependency; a missing .env.appium is a no-op.
|
|
24
|
+
import dotenv from 'dotenv';
|
|
25
|
+
dotenv.config({ path: '.env.appium' });
|
|
26
|
+
import * as fs from 'node:fs';
|
|
27
|
+
import * as path from 'node:path';
|
|
28
|
+
import PwShapeReporter from './specs/reporters/pw-shape-reporter';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Auto-select MOBILE specs and classify each by its target OS (dual-target routing).
|
|
32
|
+
*
|
|
33
|
+
* sungen emits one spec per feature; the test runner differs by platform:
|
|
34
|
+
* - mobile/appium specs import from '@wdio/globals' ← run these with wdio
|
|
35
|
+
* - web/playwright specs import from '@playwright/test' ← run those with `playwright test`
|
|
36
|
+
* The '@wdio/globals' import is the machine-readable "platform = mobile" marker, so we keep
|
|
37
|
+
* only those files and never feed Playwright specs into the (incompatible) mocha runner.
|
|
38
|
+
*
|
|
39
|
+
* Each mobile spec also carries a `// sungen:platform=<mobile|android|ios>` marker (emitted from the
|
|
40
|
+
* feature's @platform:* tag). It routes the spec per OS:
|
|
41
|
+
* - mobile → cross-platform: runs on BOTH the Android and iOS capability sets
|
|
42
|
+
* - android → Android cap only
|
|
43
|
+
* - ios → iOS cap only
|
|
44
|
+
* A spec with no marker (older codegen) defaults to `mobile` (runs on both) so nothing is dropped.
|
|
45
|
+
*
|
|
46
|
+
* Override with SUNGEN_SPECS=<glob-or-path> for a single spec (still classified by its marker).
|
|
47
|
+
*/
|
|
48
|
+
function readPlatformMarker(file: string): 'mobile' | 'android' | 'ios' {
|
|
49
|
+
try {
|
|
50
|
+
const m = /sungen:platform=(mobile|android|ios)/.exec(fs.readFileSync(file, 'utf-8'));
|
|
51
|
+
return (m?.[1] as 'mobile' | 'android' | 'ios') || 'mobile';
|
|
52
|
+
} catch {
|
|
53
|
+
return 'mobile';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function classifyMobileSpecs(root: string): Array<{ file: string; platform: string }> {
|
|
57
|
+
if (!fs.existsSync(root)) return [];
|
|
58
|
+
return (fs.readdirSync(root, { recursive: true }) as string[])
|
|
59
|
+
.filter((rel) => rel.endsWith('.spec.ts'))
|
|
60
|
+
.map((rel) => path.join(root, rel))
|
|
61
|
+
.filter((full) => {
|
|
62
|
+
try {
|
|
63
|
+
return fs.readFileSync(full, 'utf-8').includes('@wdio/globals');
|
|
64
|
+
} catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
.map((file) => ({ file, platform: readPlatformMarker(file) }));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const specEntries = process.env.SUNGEN_SPECS
|
|
72
|
+
? [process.env.SUNGEN_SPECS].map((file) => ({ file, platform: readPlatformMarker(file) }))
|
|
73
|
+
: classifyMobileSpecs(path.join(process.cwd(), 'specs', 'generated'));
|
|
74
|
+
|
|
75
|
+
// Android cap runs android + mobile (everything except ios-only); iOS cap runs ios + mobile.
|
|
76
|
+
const androidSpecs = specEntries.filter((e) => e.platform !== 'ios').map((e) => e.file);
|
|
77
|
+
const iosSpecs = specEntries.filter((e) => e.platform !== 'android').map((e) => e.file);
|
|
78
|
+
const mobileSpecs = specEntries.map((e) => e.file); // union — top-level fallback
|
|
79
|
+
|
|
80
|
+
// App under test — env overrides the init-configured defaults.
|
|
81
|
+
const APP_PACKAGE = process.env.APP_PACKAGE || '__APP_PACKAGE__';
|
|
82
|
+
const APP_ACTIVITY = process.env.APP_ACTIVITY || '__APP_ACTIVITY__';
|
|
83
|
+
const APP_APK = process.env.APP_APK; // optional: install + launch from an .apk file
|
|
84
|
+
// appActivity is OPTIONAL: UiAutomator2 auto-resolves the package's launcher activity when it's
|
|
85
|
+
// omitted, and the per-scenario relaunch uses activateApp(package) (no activity). So Android needs
|
|
86
|
+
// only APP_PACKAGE — set APP_ACTIVITY only to pin a non-standard launcher. iOS has no activity
|
|
87
|
+
// concept at all (bundleId only). Only pass the cap when APP_ACTIVITY is a real value (not the
|
|
88
|
+
// unfilled scaffold placeholder / empty), so a package-only setup launches cleanly.
|
|
89
|
+
const HAS_REAL_APP_ACTIVITY = !!APP_ACTIVITY && APP_ACTIVITY !== '__APP_ACTIVITY__';
|
|
90
|
+
|
|
91
|
+
// ── iOS (Simulator) — opt in with MOBILE_PLATFORM=ios; every value is env-overridable ──────────
|
|
92
|
+
// MOBILE_PLATFORM=ios IOS_BUNDLE_ID=com.example.app npm run test:mobile
|
|
93
|
+
// - IOS_APP=/path/MyApp.app → Appium installs a SIMULATOR build (.app or zipped .app) first.
|
|
94
|
+
// (A device .ipa won't run on a Simulator — wrong binary/platform.)
|
|
95
|
+
// - IOS_BUNDLE_ID=com.x → attach to an app already installed on the sim (simctl install first).
|
|
96
|
+
// - IOS_DEVICE / IOS_VERSION → pick the simulator; IOS_UDID targets a specific one.
|
|
97
|
+
//
|
|
98
|
+
// MOBILE_PLATFORM selects which capability set(s) run:
|
|
99
|
+
// android (default) → Android cap only (runs @platform:android + @platform:mobile specs)
|
|
100
|
+
// ios → iOS cap only (runs @platform:ios + @platform:mobile specs)
|
|
101
|
+
// both → BOTH caps in one run (a @platform:mobile spec executes on each — write once, run both)
|
|
102
|
+
const MOBILE_PLATFORM = (process.env.MOBILE_PLATFORM || 'android').toLowerCase();
|
|
103
|
+
const RUN_ANDROID = MOBILE_PLATFORM === 'android' || MOBILE_PLATFORM === 'both';
|
|
104
|
+
const RUN_IOS = MOBILE_PLATFORM === 'ios' || MOBILE_PLATFORM === 'both';
|
|
105
|
+
const IOS_APP = process.env.IOS_APP;
|
|
106
|
+
const IOS_BUNDLE_ID = process.env.IOS_BUNDLE_ID || '__IOS_BUNDLE_ID__';
|
|
107
|
+
const IOS_DEVICE = process.env.IOS_DEVICE || '__IOS_DEVICE__';
|
|
108
|
+
const IOS_VERSION = process.env.IOS_VERSION || '__IOS_VERSION__';
|
|
109
|
+
// platformVersion is OPTIONAL on a real device: with IOS_UDID set, XCUITest reads the iOS version off
|
|
110
|
+
// the connected device. Only pass the cap when IOS_VERSION is a real value (not the unfilled scaffold
|
|
111
|
+
// placeholder). On a Simulator set it (or IOS_DEVICE) so Appium picks a matching runtime.
|
|
112
|
+
const HAS_REAL_IOS_VERSION = !!IOS_VERSION && IOS_VERSION !== '__IOS_VERSION__';
|
|
113
|
+
const IOS_UDID = process.env.IOS_UDID;
|
|
114
|
+
// Real iPhone (device) — WebDriverAgent must be code-signed onto it. Set IOS_TEAM_ID (your Apple
|
|
115
|
+
// Team ID) to switch the iOS cap into real-device mode; the Simulator ignores these (they're only
|
|
116
|
+
// emitted when IOS_TEAM_ID is present, so Simulator runs are unchanged). For a real device also set
|
|
117
|
+
// IOS_UDID (the device udid, NOT a sim) and the REAL IOS_BUNDLE_ID (Android package ≠ iOS bundle id).
|
|
118
|
+
const IOS_TEAM_ID = process.env.IOS_TEAM_ID; // → appium:xcodeOrgId
|
|
119
|
+
const IOS_SIGNING_ID = process.env.IOS_SIGNING_ID || 'Apple Development'; // → appium:xcodeSigningId
|
|
120
|
+
const IOS_WDA_BUNDLE_ID = process.env.IOS_WDA_BUNDLE_ID || 'com.sungen.wda'; // → appium:updatedWDABundleId; default gives WDA a unique id (required for free teams) so you needn't set it
|
|
121
|
+
|
|
122
|
+
// ── i18n (device locale) ────────────────────────────────────────────────────
|
|
123
|
+
// SUNGEN_ENV doubles as the locale selector (the same var that swaps the test-data overlay).
|
|
124
|
+
// When its value looks like a locale code ("vi", "ja", "vi-VN"), drive the DEVICE locale via
|
|
125
|
+
// appium:language/appium:locale so an app that localizes from device locale on first launch picks
|
|
126
|
+
// it up, and force a data reset (noReset:false) so that first-launch path actually re-runs (clears
|
|
127
|
+
// any persisted in-app language override). Non-locale envs ("staging", "production") are left
|
|
128
|
+
// untouched, so existing suites are unaffected. SUNGEN_LOCALE overrides explicitly when the device
|
|
129
|
+
// locale must differ from the overlay name. Country defaults are used only when the code omits a region.
|
|
130
|
+
const LOCALE_COUNTRY: Record<string, string> = {
|
|
131
|
+
vi: 'VN',
|
|
132
|
+
ja: 'JP',
|
|
133
|
+
en: 'US',
|
|
134
|
+
ko: 'KR',
|
|
135
|
+
zh: 'CN',
|
|
136
|
+
th: 'TH',
|
|
137
|
+
id: 'ID',
|
|
138
|
+
fr: 'FR',
|
|
139
|
+
de: 'DE',
|
|
140
|
+
es: 'ES',
|
|
141
|
+
pt: 'BR',
|
|
142
|
+
ru: 'RU',
|
|
143
|
+
};
|
|
144
|
+
function resolveLocaleCaps(): Record<string, unknown> {
|
|
145
|
+
const raw = process.env.SUNGEN_LOCALE || process.env.SUNGEN_ENV;
|
|
146
|
+
if (!raw) return {};
|
|
147
|
+
const m = /^([a-z]{2})(?:[-_]([A-Za-z]{2}))?$/.exec(raw.trim());
|
|
148
|
+
if (!m) return {}; // not a locale code (e.g. "staging") → don't touch the device locale
|
|
149
|
+
const language = m[1];
|
|
150
|
+
const country = (m[2] || LOCALE_COUNTRY[language] || language).toUpperCase();
|
|
151
|
+
return {
|
|
152
|
+
'appium:language': language,
|
|
153
|
+
'appium:locale': country,
|
|
154
|
+
'appium:noReset': false,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const localeCaps = resolveLocaleCaps();
|
|
158
|
+
|
|
159
|
+
export const config: WebdriverIO.Config = {
|
|
160
|
+
runner: 'local',
|
|
161
|
+
tsConfigPath: './tsconfig.json',
|
|
162
|
+
|
|
163
|
+
specs: mobileSpecs,
|
|
164
|
+
// SUNGEN_MAX_INSTANCES=2 lets a MOBILE_PLATFORM=both run execute the Android and iOS capability
|
|
165
|
+
// in PARALLEL (separate devices; uiautomator2/XCUITest do not share ports; the reporter writes
|
|
166
|
+
// per-platform files so results cannot collide). Default stays 1 — serial is the safe choice on
|
|
167
|
+
// a laptop driving an emulator + a simulator at once.
|
|
168
|
+
maxInstances: Number(process.env.SUNGEN_MAX_INSTANCES || 1),
|
|
169
|
+
|
|
170
|
+
// Global auto-wait for $/$$ matchers (toBeDisplayed, click, …). Default is 3s, too short for a
|
|
171
|
+
// heavy app's cold (re)launch — each scenario terminates+activates its app, so the first
|
|
172
|
+
// find/click must wait out the splash/first-frame. 20s keeps slow native/Flutter apps green.
|
|
173
|
+
waitforTimeout: 20000,
|
|
174
|
+
|
|
175
|
+
// Capability set(s) selected by MOBILE_PLATFORM (android | ios | both). Each cap carries its OWN
|
|
176
|
+
// `specs` (per-OS routing): the Android cap runs android + mobile specs, the iOS cap runs ios +
|
|
177
|
+
// mobile specs, so MOBILE_PLATFORM=both executes a @platform:mobile spec on each in a single run.
|
|
178
|
+
// i18n localeCaps (appium:language/locale + noReset:false) apply to both when SUNGEN_ENV is a locale.
|
|
179
|
+
capabilities: [
|
|
180
|
+
...(RUN_ANDROID
|
|
181
|
+
? [
|
|
182
|
+
{
|
|
183
|
+
platformName: 'Android',
|
|
184
|
+
'appium:automationName': 'UiAutomator2',
|
|
185
|
+
'appium:udid': process.env.ANDROID_UDID || '__ANDROID_UDID__',
|
|
186
|
+
// APP_APK installs from a file (Appium reads pkg/activity from the manifest);
|
|
187
|
+
// otherwise target an already-installed app by package + activity.
|
|
188
|
+
...(APP_APK
|
|
189
|
+
? { 'appium:app': APP_APK }
|
|
190
|
+
: {
|
|
191
|
+
'appium:appPackage': APP_PACKAGE,
|
|
192
|
+
// Omit when unset → UiAutomator2 launches the package's default launcher activity.
|
|
193
|
+
...(HAS_REAL_APP_ACTIVITY ? { 'appium:appActivity': APP_ACTIVITY } : {}),
|
|
194
|
+
}),
|
|
195
|
+
'appium:noReset': true,
|
|
196
|
+
'appium:autoGrantPermissions': true,
|
|
197
|
+
'appium:newCommandTimeout': 300,
|
|
198
|
+
// i18n: when SUNGEN_ENV/SUNGEN_LOCALE is a locale code, this overrides noReset→false and
|
|
199
|
+
// adds appium:language/appium:locale so the app boots in that locale. Empty otherwise.
|
|
200
|
+
...localeCaps,
|
|
201
|
+
// Per-capability spec routing (supported by WDIO at runtime; not in the TS cap type → cast).
|
|
202
|
+
specs: androidSpecs,
|
|
203
|
+
} as any,
|
|
204
|
+
]
|
|
205
|
+
: []),
|
|
206
|
+
...(RUN_IOS
|
|
207
|
+
? [
|
|
208
|
+
{
|
|
209
|
+
platformName: 'iOS',
|
|
210
|
+
'appium:automationName': 'XCUITest',
|
|
211
|
+
'appium:deviceName': IOS_DEVICE,
|
|
212
|
+
// Omit when unknown (real device, no IOS_VERSION) → XCUITest reads it from the device by udid.
|
|
213
|
+
...(HAS_REAL_IOS_VERSION ? { 'appium:platformVersion': IOS_VERSION } : {}),
|
|
214
|
+
// target a specific simulator if given (else Appium uses the matching device/version)
|
|
215
|
+
...(IOS_UDID ? { 'appium:udid': IOS_UDID } : {}),
|
|
216
|
+
// IOS_APP installs a Simulator build (.app/.zip); otherwise attach to an installed bundleId.
|
|
217
|
+
...(IOS_APP
|
|
218
|
+
? { 'appium:app': IOS_APP }
|
|
219
|
+
: IOS_BUNDLE_ID
|
|
220
|
+
? { 'appium:bundleId': IOS_BUNDLE_ID }
|
|
221
|
+
: {}),
|
|
222
|
+
// Real-device WDA signing — emitted only when IOS_TEAM_ID is set (Simulator: omitted → unchanged).
|
|
223
|
+
...(IOS_TEAM_ID
|
|
224
|
+
? {
|
|
225
|
+
'appium:xcodeOrgId': IOS_TEAM_ID,
|
|
226
|
+
'appium:xcodeSigningId': IOS_SIGNING_ID,
|
|
227
|
+
// Pass -allowProvisioningUpdates -allowProvisioningDeviceRegistration to xcodebuild so it
|
|
228
|
+
// auto-creates/refreshes the WDA profile + registers the device (needed for automatic
|
|
229
|
+
// signing on a real device; without it WDA build fails fast with "xcodebuild code 65").
|
|
230
|
+
'appium:allowProvisioningDeviceRegistration': true,
|
|
231
|
+
...(IOS_WDA_BUNDLE_ID ? { 'appium:updatedWDABundleId': IOS_WDA_BUNDLE_ID } : {}),
|
|
232
|
+
}
|
|
233
|
+
: {}),
|
|
234
|
+
'appium:noReset': true,
|
|
235
|
+
'appium:newCommandTimeout': 300,
|
|
236
|
+
...localeCaps,
|
|
237
|
+
specs: iosSpecs,
|
|
238
|
+
} as any,
|
|
239
|
+
]
|
|
240
|
+
: []),
|
|
241
|
+
],
|
|
242
|
+
|
|
243
|
+
logLevel: 'warn',
|
|
244
|
+
framework: 'mocha',
|
|
245
|
+
// 'spec' for console + PwShapeReporter for a Playwright-shaped JSON the delivery/dashboard reuse.
|
|
246
|
+
reporters: ['spec', [PwShapeReporter, {}]],
|
|
247
|
+
|
|
248
|
+
// Auto-start a local Appium server on :4723 (base path '/')
|
|
249
|
+
services: ['appium'],
|
|
250
|
+
port: 4723,
|
|
251
|
+
path: '/',
|
|
252
|
+
|
|
253
|
+
mochaOpts: {
|
|
254
|
+
ui: 'bdd',
|
|
255
|
+
timeout: 120000,
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
// iOS + locale: XCUITest's `noReset:false` does NOT actually clear the app's data container
|
|
259
|
+
// (verified live 2026-06-11) — a Flutter app that persisted its language on a previous launch keeps
|
|
260
|
+
// it and ignores the new device locale, so the i18n run asserts the wrong language. Wipe the app
|
|
261
|
+
// data via simctl BEFORE the session so first-launch locale detection re-runs. Android needs no
|
|
262
|
+
// hook (its noReset:false reset works natively).
|
|
263
|
+
beforeSession: async (_config, capabilities) => {
|
|
264
|
+
if (Object.keys(localeCaps).length === 0) return; // not a locale run
|
|
265
|
+
const caps = capabilities as Record<string, unknown>;
|
|
266
|
+
if (String(caps['platformName'] || '').toLowerCase() !== 'ios') return;
|
|
267
|
+
const { execSync } = await import('node:child_process');
|
|
268
|
+
const fsm = await import('node:fs');
|
|
269
|
+
const udid = (caps['appium:udid'] as string) || 'booted';
|
|
270
|
+
// Wipe the session-default app AND every per-feature dual-id bundle the worker's specs declare
|
|
271
|
+
// (__IOS_BUNDLE__ markers) — a multi-app iOS locale run must reset each app, not just the first.
|
|
272
|
+
const bundleIds = new Set<string>();
|
|
273
|
+
if (typeof caps['appium:bundleId'] === 'string') bundleIds.add(caps['appium:bundleId'] as string);
|
|
274
|
+
for (const spec of specs || []) {
|
|
275
|
+
try {
|
|
276
|
+
const src = fsm.readFileSync(String(spec).replace('file://', ''), 'utf-8');
|
|
277
|
+
const m = /__IOS_BUNDLE__ = '([^']+)'/.exec(src);
|
|
278
|
+
if (m) bundleIds.add(m[1]);
|
|
279
|
+
} catch { /* unreadable spec — skip */ }
|
|
280
|
+
}
|
|
281
|
+
for (const bundleId of bundleIds) {
|
|
282
|
+
try { execSync(`xcrun simctl terminate ${udid} ${bundleId}`, { stdio: 'ignore' }); } catch { /* not running */ }
|
|
283
|
+
try {
|
|
284
|
+
const dataDir = execSync(`xcrun simctl get_app_container ${udid} ${bundleId} data`).toString().trim();
|
|
285
|
+
if (dataDir) execSync(`rm -rf "${dataDir}/Library" "${dataDir}/Documents"`);
|
|
286
|
+
console.log(`[sungen] iOS locale run: wiped app data for ${bundleId} (first-launch locale re-detect)`);
|
|
287
|
+
} catch { /* app not installed yet */ }
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
// Per-scenario app reset lives in each generated spec's `beforeEach` (terminate+activate the app
|
|
292
|
+
// from that feature's `Path:`). One run can therefore host screens from different apps — each spec
|
|
293
|
+
// launches its own — so there's no single-app reset hook here. The capabilities above only bootstrap
|
|
294
|
+
// the session (default app / device); the real app per scenario comes from the spec.
|
|
295
|
+
};
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
* Valid selector types used across scaffold generator, selector resolver, and validator.
|
|
3
3
|
* Single source of truth — import this instead of hardcoding.
|
|
4
4
|
*/
|
|
5
|
-
export declare const VALID_SELECTOR_TYPES: readonly ["placeholder", "role", "testid", "label", "text", "page", "column", "locator", "id", "upload", "frame", "table"];
|
|
5
|
+
export declare const VALID_SELECTOR_TYPES: readonly ["placeholder", "role", "testid", "label", "text", "page", "column", "locator", "id", "upload", "frame", "table", "accessibility-id", "xpath", "android-uiautomator", "ios-predicate"];
|
|
6
6
|
export type SelectorType = (typeof VALID_SELECTOR_TYPES)[number];
|
|
7
7
|
//# sourceMappingURL=selector-types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selector-types.d.ts","sourceRoot":"","sources":["../../src/utils/selector-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,oBAAoB,
|
|
1
|
+
{"version":3,"file":"selector-types.d.ts","sourceRoot":"","sources":["../../src/utils/selector-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,oBAAoB,iMAkBvB,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC"}
|
|
@@ -18,5 +18,10 @@ exports.VALID_SELECTOR_TYPES = [
|
|
|
18
18
|
'upload',
|
|
19
19
|
'frame',
|
|
20
20
|
'table',
|
|
21
|
+
// === Mobile (Appium) strategies — resolved by the appium adapter (MOB-3) ===
|
|
22
|
+
'accessibility-id', // cross-platform — Android content-desc / iOS accessibilityIdentifier
|
|
23
|
+
'xpath', // cross-platform, last resort
|
|
24
|
+
'android-uiautomator', // Android only — UiSelector expressions
|
|
25
|
+
'ios-predicate', // iOS only — NSPredicate strings
|
|
21
26
|
];
|
|
22
27
|
//# sourceMappingURL=selector-types.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selector-types.js","sourceRoot":"","sources":["../../src/utils/selector-types.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACU,QAAA,oBAAoB,GAAG;IAClC,aAAa;IACb,MAAM;IACN,QAAQ;IACR,OAAO;IACP,MAAM;IACN,MAAM;IACN,QAAQ;IACR,SAAS;IACT,IAAI;IACJ,QAAQ;IACR,OAAO;IACP,OAAO;
|
|
1
|
+
{"version":3,"file":"selector-types.js","sourceRoot":"","sources":["../../src/utils/selector-types.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACU,QAAA,oBAAoB,GAAG;IAClC,aAAa;IACb,MAAM;IACN,QAAQ;IACR,OAAO;IACP,MAAM;IACN,MAAM;IACN,QAAQ;IACR,SAAS;IACT,IAAI;IACJ,QAAQ;IACR,OAAO;IACP,OAAO;IACP,8EAA8E;IAC9E,kBAAkB,EAAK,sEAAsE;IAC7F,OAAO,EAAgB,8BAA8B;IACrD,qBAAqB,EAAE,wCAAwC;IAC/D,eAAe,EAAQ,iCAAiC;CAChD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sun-asterisk/sungen",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.2-beta.1",
|
|
4
4
|
"description": "Deterministic E2E Test Compiler - Gherkin + Selectors → Playwright tests",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "rm -rf dist && tsc && npm run copy-templates",
|
|
12
|
-
"copy-templates": "mkdir -p dist/generators/test-generator/adapters/playwright/templates/steps && mkdir -p dist/generators/test-generator/templates && mkdir -p dist/orchestrator/templates && mkdir -p dist/dashboard/templates && cp -r src/generators/test-generator/adapters/playwright/templates/*.hbs dist/generators/test-generator/adapters/playwright/templates/ 2>/dev/null || true && cp -r src/generators/test-generator/adapters/playwright/templates/steps dist/generators/test-generator/adapters/playwright/templates/ && cp src/generators/test-generator/templates/*.hbs dist/generators/test-generator/templates/ 2>/dev/null || true && cp -r src/orchestrator/templates/* dist/orchestrator/templates/ && cp src/dashboard/templates/index.html dist/dashboard/templates/index.html && mkdir -p dist/harness/catalog && cp src/harness/catalog/*.yaml dist/harness/catalog/",
|
|
12
|
+
"copy-templates": "mkdir -p dist/generators/test-generator/adapters/playwright/templates/steps && mkdir -p dist/generators/test-generator/templates && mkdir -p dist/orchestrator/templates && mkdir -p dist/dashboard/templates && cp -r src/generators/test-generator/adapters/playwright/templates/*.hbs dist/generators/test-generator/adapters/playwright/templates/ 2>/dev/null || true && cp -r src/generators/test-generator/adapters/playwright/templates/steps dist/generators/test-generator/adapters/playwright/templates/ && mkdir -p dist/generators/test-generator/adapters/appium/templates/steps && cp -r src/generators/test-generator/adapters/appium/templates/*.hbs dist/generators/test-generator/adapters/appium/templates/ 2>/dev/null || true && cp -r src/generators/test-generator/adapters/appium/templates/steps dist/generators/test-generator/adapters/appium/templates/ && cp src/generators/test-generator/templates/*.hbs dist/generators/test-generator/templates/ 2>/dev/null || true && cp -r src/orchestrator/templates/* dist/orchestrator/templates/ && cp src/dashboard/templates/index.html dist/dashboard/templates/index.html && mkdir -p dist/harness/catalog && cp src/harness/catalog/*.yaml dist/harness/catalog/",
|
|
13
13
|
"build:dashboard": "cd ../../dashboard && npm install --silent && npm run build && cd - && cp ../../dashboard/dist/index.html src/dashboard/templates/index.html",
|
|
14
14
|
"dev": "tsx src/cli/index.ts",
|
|
15
|
-
"test": "tsx tests/golden/run.ts && tsx tests/audit/run.ts && tsx tests/ingest/run.ts && tsx tests/eval/run.ts && tsx tests/exporter/run.ts && tsx tests/capabilities/run.ts && tsx tests/openapi/run.ts && tsx tests/packaging/run.ts",
|
|
15
|
+
"test": "tsx tests/golden/run.ts && tsx tests/audit/run.ts && tsx tests/ingest/run.ts && tsx tests/eval/run.ts && tsx tests/exporter/run.ts && tsx tests/exporter/feature-parser-category.run.ts && tsx tests/exporter/api-detail-sheet.run.ts && tsx tests/api-runtime/base-path-url-join.run.ts && tsx tests/capabilities/run.ts && tsx tests/openapi/run.ts && tsx tests/packaging/run.ts",
|
|
16
16
|
"test:update": "tsx tests/golden/run.ts --update && tsx tests/audit/run.ts --update && tsx tests/ingest/run.ts --update",
|
|
17
17
|
"prepublishOnly": "npm run build:dashboard && npm run build"
|
|
18
18
|
},
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"node": ">=18.0.0"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@sungen/driver-ui": "3.2.
|
|
36
|
+
"@sungen/driver-ui": "3.2.2-beta.1",
|
|
37
37
|
"@anthropic-ai/sdk": "^0.71.0",
|
|
38
38
|
"@babel/parser": "^7.28.5",
|
|
39
39
|
"@babel/traverse": "^7.28.5",
|
|
@@ -20,6 +20,12 @@ export interface RouteTask {
|
|
|
20
20
|
artifact: string;
|
|
21
21
|
/** Capability tags present on the target (e.g. ['@query'], ['@api'], ['@cases']). */
|
|
22
22
|
tags?: string[];
|
|
23
|
+
/**
|
|
24
|
+
* The project's active platform (qa/capabilities.yaml `platform:` — web | mobile | …). A platform
|
|
25
|
+
* capability (mobile/native) is activated by the project's platform, NOT by a tag, so it is keyed
|
|
26
|
+
* separately. `web` has no capability id of its own (the in-core default is `ui`) → no-op.
|
|
27
|
+
*/
|
|
28
|
+
platform?: string;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
export interface RoutePlan {
|
|
@@ -34,11 +40,17 @@ export interface RoutePlan {
|
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
class ContextRouter {
|
|
37
|
-
/**
|
|
38
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Which capabilities a task activates: the default capability + the active platform's capability
|
|
45
|
+
* (when one is registered with `id === platform`) + any capability owning a present tag.
|
|
46
|
+
*/
|
|
47
|
+
capabilitiesFor(tags: string[] = [], platform?: string): string[] {
|
|
39
48
|
const ids = new Set<string>();
|
|
40
49
|
const def = capabilityRegistry.defaultCapabilityId();
|
|
41
50
|
if (def) ids.add(def);
|
|
51
|
+
// A platform capability (mobile/native/…) is activated by the project's platform, not by a tag.
|
|
52
|
+
// `web` has no capability of its own → falls through to the default `ui` (byte-identical).
|
|
53
|
+
if (platform && capabilityRegistry.get(platform)) ids.add(platform);
|
|
42
54
|
for (const cap of capabilityRegistry.all()) {
|
|
43
55
|
if ((cap.annotations ?? []).some((a) => tags.includes(a))) ids.add(cap.id);
|
|
44
56
|
}
|
|
@@ -47,7 +59,7 @@ class ContextRouter {
|
|
|
47
59
|
|
|
48
60
|
/** Compute the routing plan for a task (deterministic). */
|
|
49
61
|
route(task: RouteTask): RoutePlan {
|
|
50
|
-
const capabilities = this.capabilitiesFor(task.tags ?? []);
|
|
62
|
+
const capabilities = this.capabilitiesFor(task.tags ?? [], task.platform);
|
|
51
63
|
// Generic sensors (no capability, or the always-on `core`) run regardless of the task's tags;
|
|
52
64
|
// capability-specific sensors run only when their capability is in scope.
|
|
53
65
|
const inScope = (capId?: string) => capId == null || capId === 'core' || capabilities.includes(capId);
|
|
@@ -23,7 +23,7 @@ import { LOCAL_DRIVERS, registerCoreCapability } from './builtins';
|
|
|
23
23
|
* move to their packages (R5.5/R5.6) they join this list and leave `LOCAL_DRIVERS`; eventually this
|
|
24
24
|
* becomes a scan of installed `@sungen/driver-*` packages (R5.7).
|
|
25
25
|
*/
|
|
26
|
-
const EXTERNAL_DRIVERS = ['@sungen/driver-ui', '@sungen/driver-db', '@sungen/driver-api'];
|
|
26
|
+
const EXTERNAL_DRIVERS = ['@sungen/driver-ui', '@sungen/driver-db', '@sungen/driver-api', '@sungen/driver-mobile'];
|
|
27
27
|
|
|
28
28
|
function loadExternalDriver(name: string): void {
|
|
29
29
|
// Resolve from the user's PROJECT first, then from core's own location. Opt-in drivers
|
|
@@ -117,11 +117,13 @@ export function registerAuditCommand(program: Command): void {
|
|
|
117
117
|
console.log(` Report: ${path.relative(process.cwd(), outPath)}`);
|
|
118
118
|
console.log('');
|
|
119
119
|
}
|
|
120
|
-
// Non-zero exit when the gate fails — usable in CI / repair loop.
|
|
121
|
-
process.exit
|
|
120
|
+
// Non-zero exit when the gate fails — usable in CI / repair loop. Set exitCode (not
|
|
121
|
+
// process.exit) so a large --json report on a PIPE fully flushes before the process exits;
|
|
122
|
+
// process.exit() can truncate buffered stdout mid-write (surfaced by the H3 flowDepth field).
|
|
123
|
+
process.exitCode = report.gateStatus === 'FAIL' ? 2 : 0;
|
|
122
124
|
} catch (error) {
|
|
123
125
|
console.error('Error:', error instanceof Error ? error.message : error);
|
|
124
|
-
process.
|
|
126
|
+
process.exitCode = 1;
|
|
125
127
|
}
|
|
126
128
|
});
|
|
127
129
|
}
|
|
@@ -20,8 +20,8 @@ import {
|
|
|
20
20
|
renderCsv,
|
|
21
21
|
writeCsv,
|
|
22
22
|
} from '../../exporters/csv-exporter';
|
|
23
|
-
import { renderXlsxMultiSheet, writeXlsx } from '../../exporters/xlsx-exporter';
|
|
24
|
-
import { EnvironmentInfo, PreflightCheck, ScreenSummary, TestCaseRow } from '../../exporters/types';
|
|
23
|
+
import { renderXlsxMultiSheet, writeXlsx, buildApiDetailRows, addApiDetailSheet } from '../../exporters/xlsx-exporter';
|
|
24
|
+
import { EnvironmentInfo, PreflightCheck, ScreenSummary, TestCaseRow, ApiCatalogEntry } from '../../exporters/types';
|
|
25
25
|
|
|
26
26
|
const COLOR = {
|
|
27
27
|
reset: '\x1b[0m',
|
|
@@ -135,6 +135,24 @@ function qaDir(cwd: string, target: DeliveryTarget): string {
|
|
|
135
135
|
return path.join(cwd, 'qa', qaParent(target.kind), target.screen);
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Load the apis.yaml catalog for an api-kind unit.
|
|
140
|
+
* The catalog lives at qa/api/<screen>/api/apis.yaml.
|
|
141
|
+
* Returns an empty object when the file is absent (allows graceful degradation).
|
|
142
|
+
*/
|
|
143
|
+
function loadApiCatalog(cwd: string, target: DeliveryTarget): Record<string, ApiCatalogEntry> {
|
|
144
|
+
if (target.kind !== 'api') return {};
|
|
145
|
+
const catalogPath = path.join(cwd, 'qa', 'api', target.screen, 'api', 'apis.yaml');
|
|
146
|
+
if (!fs.existsSync(catalogPath)) return {};
|
|
147
|
+
try {
|
|
148
|
+
const parsed = parseYaml(fs.readFileSync(catalogPath, 'utf-8'));
|
|
149
|
+
if (parsed && typeof parsed === 'object') {
|
|
150
|
+
return parsed as Record<string, ApiCatalogEntry>;
|
|
151
|
+
}
|
|
152
|
+
} catch { /* malformed yaml — skip */ }
|
|
153
|
+
return {};
|
|
154
|
+
}
|
|
155
|
+
|
|
138
156
|
function generatedDir(cwd: string, target: DeliveryTarget): string {
|
|
139
157
|
const sub = target.kind === 'flow' ? path.join('flows', target.screen) : target.kind === 'api' ? path.join('api', target.screen) : target.screen;
|
|
140
158
|
return path.join(cwd, 'specs', 'generated', sub);
|
|
@@ -411,6 +429,10 @@ async function exportTarget(
|
|
|
411
429
|
const specLink = fs.existsSync(specMdFile) ? path.relative(cwd, specMdFile) : '';
|
|
412
430
|
const explicitEnv = process.env.SUNGEN_ENV;
|
|
413
431
|
|
|
432
|
+
// For api-kind units, load the endpoint catalog so we can add the API detail sheet.
|
|
433
|
+
const apiCatalog = loadApiCatalog(cwd, target);
|
|
434
|
+
const hasApiCatalog = target.kind === 'api' && Object.keys(apiCatalog).length > 0;
|
|
435
|
+
|
|
414
436
|
// Decide between single-locale and aggregated multi-locale flows.
|
|
415
437
|
// • SUNGEN_ENV set → single locale (existing behaviour, no change)
|
|
416
438
|
// • SUNGEN_ENV unset → discover every *-test-result*.json variant.
|
|
@@ -439,6 +461,10 @@ async function exportTarget(
|
|
|
439
461
|
{ sheetName: 'Auto', summary: buildSummary(label, autoRows, ''), rows: autoRows, specLink },
|
|
440
462
|
{ sheetName: 'Manual', summary: buildSummary(label, manualRows, ''), rows: manualRows, specLink },
|
|
441
463
|
]);
|
|
464
|
+
// Append the API detail sheet when the unit has a catalog (api-kind only; no-op otherwise).
|
|
465
|
+
if (hasApiCatalog) {
|
|
466
|
+
addApiDetailSheet(wb, buildApiDetailRows(apiCatalog, feature.scenarios));
|
|
467
|
+
}
|
|
442
468
|
await writeXlsx(cwd, target.featureBaseName, wb);
|
|
443
469
|
return buildSummary(label, rows, path.relative(cwd, csvPath));
|
|
444
470
|
}
|
|
@@ -496,6 +522,10 @@ async function exportTarget(
|
|
|
496
522
|
{ sheetName: 'Manual', summary: buildSummary(label, manualRows, ''), rows: manualRows, specLink },
|
|
497
523
|
];
|
|
498
524
|
const wb = renderXlsxMultiSheet(sheets);
|
|
525
|
+
// Append the API detail sheet when the unit has a catalog (api-kind only; no-op otherwise).
|
|
526
|
+
if (hasApiCatalog) {
|
|
527
|
+
addApiDetailSheet(wb, buildApiDetailRows(apiCatalog, feature.scenarios));
|
|
528
|
+
}
|
|
499
529
|
await writeXlsx(cwd, target.featureBaseName, wb);
|
|
500
530
|
|
|
501
531
|
return primarySummary ?? buildSummary(label, (autoSheets[0]?.rows ?? []).concat(manualRows), primaryCsvPath);
|