@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
|
@@ -88,6 +88,33 @@ Multi-locale (no `SUNGEN_ENV`): one **`<LOCALE> Auto`** sheet per locale + a sin
|
|
|
88
88
|
|
|
89
89
|
---
|
|
90
90
|
|
|
91
|
+
## API delivery — extra worksheet
|
|
92
|
+
|
|
93
|
+
For **api-kind units** (`qa/api/<area>/`), the `.xlsx` gains a third worksheet **`API detail`** (appended after Auto/Manual). The main BM-2-901-13 Testcases layout is unchanged. The CSV is unchanged (16-column, no extra sheet).
|
|
94
|
+
|
|
95
|
+
### Required sources (API detail sheet only)
|
|
96
|
+
|
|
97
|
+
| Source | Path | Created by |
|
|
98
|
+
|--------|------|------------|
|
|
99
|
+
| Endpoint catalog | `qa/api/<area>/api/apis.yaml` | `sungen add --api` or `sungen api import` |
|
|
100
|
+
| Scenario annotations | `qa/api/<area>/features/<feature>.feature` | `create-test` |
|
|
101
|
+
|
|
102
|
+
### API detail column mapping
|
|
103
|
+
|
|
104
|
+
| Column | Source |
|
|
105
|
+
|--------|--------|
|
|
106
|
+
| Endpoint | `path` from `apis.yaml` catalog entry |
|
|
107
|
+
| Method | `method` from catalog entry (uppercased) |
|
|
108
|
+
| Auth / Datasource | catalog `datasource` + any `@auth:<role>` tag from scenarios calling this endpoint |
|
|
109
|
+
| Request shape | catalog `body` + `params` fields composed as `body: {…}; params: [a, b]` |
|
|
110
|
+
| Expected-status matrix | `@cases:<dataset>` label for data-driven scenarios; catalog `expect.status` as fallback |
|
|
111
|
+
| Flow steps | Ordered `@api:<name>` call chain from multi-call scenarios (e.g. `register → count_users`) |
|
|
112
|
+
| Concurrency invariant | `@concurrent:<N>` + `@query:<oracle>` from concurrent scenarios (e.g. `ok_count=2; @query user_count`) |
|
|
113
|
+
|
|
114
|
+
**Sources are catalog + annotations only** — Field Metadata (FM) is not required for this sheet.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
91
118
|
## Excluded from CSV
|
|
92
119
|
|
|
93
120
|
- `@steps:<name>` **base** scenarios — these are setup-only, inlined into `@extend:...` scenarios at compile time
|
|
@@ -214,6 +214,8 @@ Options: `nth` `exact` `scope` `match` `variant` `frame` `contenteditable` `colu
|
|
|
214
214
|
| `@cases:dataset` | Data-driven: run the scenario once per row of the `dataset` LIST in test-data → one `test()` per row |
|
|
215
215
|
| `@query:name` | Database: run the named query from `database/queries.yaml` (precondition) and bind its rows to `{{name}}`; assert with `expect {{name.count}} …` + path access. Override params `@query:name(p={{v}})`. Repeatable. (Optional Data Driver — see Database verification above) |
|
|
216
216
|
| `@api:name` | API: run the named request from `api/apis.yaml` (precondition) and bind the response to `{{name}}`; assert with `expect {{name.status}} …` + path access (`{{name.body.<path>}}`). Override params `@api:name(p={{v}})`. Repeatable. (Optional API Driver) |
|
|
217
|
+
| `@concurrent:N` | API idempotency: fire the bound `@api` request N times in parallel, then bind aggregates on the `@api` name — `{{name.ok_count}}` (2xx count) and `{{name.status_counts}}` (status→count map). Assert the exactly-once invariant (`expect {{name.ok_count}} is 1`); pair with `@query` as a DB oracle. Tag order = run order: `@api` (mutate) before `@query` (verify). (Optional API Driver) |
|
|
218
|
+
| `@hybrid` | One unit, two capabilities: a signed-in browser session (UI) authorizes the `@api` call — the API request reuses the UI `storageState`. (Optional API + UI Drivers) |
|
|
217
219
|
|
|
218
220
|
### Data-driven scenarios (`@cases`)
|
|
219
221
|
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sungen-mobile-gestures
|
|
3
|
+
description: 'Mobile gesture patterns (swipe, long-press, scroll-to, pinch, pull-to-refresh) — Gherkin syntax + Appium MCP mapping. Auto-loaded for @platform:mobile/android/ios screens.'
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
Document the **mobile-only interactions** that have no web equivalent, so the AI can (a) drive them
|
|
10
|
+
during exploration via Appium MCP and (b) write Gherkin steps for them. These are gestures the web
|
|
11
|
+
patterns (`click`, `hover`, `fill`) don't cover.
|
|
12
|
+
|
|
13
|
+
> Codegen status (Phase 3): the Appium adapter now **compiles** these gesture steps to WebdriverIO:
|
|
14
|
+
> - **`tap` / `taps`** — synonym for `click` (→ `.click()`); **`double-tap`** → double-click.
|
|
15
|
+
> - **`scroll to [X]`** → `.scrollIntoView()` (shared `scroll-action` template).
|
|
16
|
+
> - **`swipe <dir> on [X]`** → `mobile: swipeGesture`.
|
|
17
|
+
> - **`long-press [X] [for N seconds]`** → `mobile: longClickGesture`.
|
|
18
|
+
> - **`rotate to landscape|portrait`** → `driver.setOrientation(...)`.
|
|
19
|
+
> - **`pull-to-refresh on [X]`** → fast `mobile: swipeGesture` (direction down).
|
|
20
|
+
> - **`pinch-zoom in|out on [X]`** → `mobile: pinchOpenGesture` / `pinchCloseGesture`.
|
|
21
|
+
> - **`send app to background for N seconds`** → `driver.background(N)`.
|
|
22
|
+
> - **`open notification panel`** → `driver.openNotifications()`. **Android-only** — XCUITest has no
|
|
23
|
+
> notification-shade automation; on iOS the generated step throws loud. Keep such scenarios `@platform:android`.
|
|
24
|
+
> - **`tap top of [X]`** / **`tap [X] at top`** → tap the element's **visible top edge** (`mobile: clickGesture`
|
|
25
|
+
> at top-centre from the element bounds) instead of its centre — use when the centre is occluded by a
|
|
26
|
+
> floating bottom bar so a normal centre tap would hit the bar.
|
|
27
|
+
>
|
|
28
|
+
> Still exploration-only (no codegen yet): grant/deny permissions, clipboard set/get, dismiss system
|
|
29
|
+
> dialog — author with the `appium_*` tool calls below; templates land in a later phase.
|
|
30
|
+
>
|
|
31
|
+
> 📜 **`scroll to [X]` — two failure modes seen, with the real cause (measured):**
|
|
32
|
+
> 1. *"Default scrollable element '//android.widget.ScrollView' not found"* — wdio's mobile scroll runs
|
|
33
|
+
> `$$('//android.widget.ScrollView')` and throws if 0 match. This is **state-dependent**: that node
|
|
34
|
+
> exists on a list/Home screen but NOT on every screen (tool/tab screens may lack it). If scroll runs
|
|
35
|
+
> while the app is on a screen without one → this error. Not "the screen can't scroll".
|
|
36
|
+
> 2. **`.scrollIntoView()` is a NO-OP when the target is already inside the scroll viewport** — even if it's
|
|
37
|
+
> visually **occluded by a floating bottom nav**. scrollIntoView only scrolls things that are *off-screen*;
|
|
38
|
+
> it has no concept of z-order occlusion (verified: element bounds identical before/after, centre tap
|
|
39
|
+
> still hit the nav). So scroll **cannot** fix an occluded-by-overlay element → use **`tap top of [X]`**
|
|
40
|
+
> (visible-point tap), not scroll.
|
|
41
|
+
>
|
|
42
|
+
> `scroll to [X]` is still useful for genuinely **off-screen** items: it uses Android `UiScrollable`
|
|
43
|
+
> (accessibility-id targets) or a `mobile: scrollGesture` fallback.
|
|
44
|
+
>
|
|
45
|
+
> ⚠️ **Flutter note:** `.scrollIntoView()` looks for a native `android.widget.ScrollView`. Flutter
|
|
46
|
+
> (and some RN) scrollables don't expose one, and a screen whose content fits the viewport isn't
|
|
47
|
+
> scrollable at all — there `scroll to` fails or no-ops. For an element merely **occluded by a floating
|
|
48
|
+
> bottom nav** on a non-scrollable screen, neither scroll nor `element.click()` (taps the occluded
|
|
49
|
+
> centre) works; that needs a visible-point/coordinate tap (not yet supported) → tag such a scenario
|
|
50
|
+
> `@manual` for now.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Gesture catalog
|
|
55
|
+
|
|
56
|
+
All map to the `appium_gesture` MCP tool (action + params). Element-relative gestures pass `elementUUID`
|
|
57
|
+
from `appium_find_element`; screen gestures pass `direction` or coordinates.
|
|
58
|
+
|
|
59
|
+
| Intent | Proposed Gherkin | `appium_gesture` |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| Tap | `User tap [Settings]` | `action=tap, elementUUID` |
|
|
62
|
+
| Double-tap | `User double-tap [Photo]` | `action=double_tap, elementUUID` |
|
|
63
|
+
| Long-press | `User long-press [Item] for 2 seconds` | `action=long_press, elementUUID, duration=2000` |
|
|
64
|
+
| Swipe (dismiss/switch/carousel) | `User swipe left on [Card]` | `action=swipe, elementUUID, direction=left` |
|
|
65
|
+
| Pull-to-refresh | `User pull-to-refresh on [Feed]` | `action=swipe, direction=down, speed=fast` |
|
|
66
|
+
| Scroll a list | `User scroll down on [Feed]` | `action=scroll, direction=down` |
|
|
67
|
+
| Scroll until visible | `User scroll to [Footer]` | `action=scroll_to_element, strategy, selector, direction` |
|
|
68
|
+
| Pinch zoom in/out | `User pinch-zoom in on [Map]` | `action=pinch_zoom, elementUUID, scale` (>1 in, <1 out) |
|
|
69
|
+
| System back | `User go back` | `action=back` |
|
|
70
|
+
| Drag & drop | `User drag [A] onto [B]` | `appium_drag_and_drop` (separate tool) |
|
|
71
|
+
|
|
72
|
+
Other device-level actions (separate MCP tools, future Gherkin):
|
|
73
|
+
- Rotate: `appium_orientation` — `User rotate to landscape`
|
|
74
|
+
- Permission dialog: `appium_mobile_permissions` / `appium_alert` — `User grant [Location] permission`
|
|
75
|
+
- Background/foreground: `appium_app_lifecycle` — `User send app to background for 5 seconds`
|
|
76
|
+
- Clipboard: `appium_mobile_clipboard` — `User paste into [Field]`
|
|
77
|
+
- Notifications: open panel via `appium_mobile_device_control`
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Authoring guidance
|
|
82
|
+
|
|
83
|
+
- **Prefer `scroll_to_element` over blind scrolling.** When a target may be off-screen, use
|
|
84
|
+
`appium_gesture action=scroll_to_element` (same strategy+selector as the find) rather than repeated
|
|
85
|
+
`appium_find_element` — it stops when the element appears or the page stops scrolling.
|
|
86
|
+
- **Direction semantics**: `swipe` = dismiss / switch screen / carousel / pull-to-refresh (use
|
|
87
|
+
`speed=fast`); `scroll` = browse content in a list/feed. Choose by intent, not interchangeably.
|
|
88
|
+
- **Long-press duration**: default 2000ms; pass `duration` for context menus that need a longer hold.
|
|
89
|
+
- **Element vs screen**: pass `elementUUID` to gesture relative to an element; omit it (+ `direction`)
|
|
90
|
+
to gesture on the whole screen.
|
|
91
|
+
- **Gestures in the navigation recipe**: a `Background:` step like `When User scroll to [X]` or
|
|
92
|
+
`And User swipe up on [Feed]` is a valid nav hop — `sungen-capture-mobile` replays it to reach the
|
|
93
|
+
target screen before scanning (see "Background = the navigation recipe" in `sungen-gherkin-syntax`).
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Selectors for gestures
|
|
98
|
+
|
|
99
|
+
Same rules as `sungen-selector-fix-mobile`: `accessibility-id` first, then `id`, platform-native, xpath.
|
|
100
|
+
For `scroll_to_element`, the `strategy`+`selector` you pass are the target you're scrolling toward — keep
|
|
101
|
+
them stable (accessibility-id) so the scroll terminates reliably.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## What this skill does NOT do
|
|
106
|
+
|
|
107
|
+
- Does not implement gesture codegen (templates land in a later phase).
|
|
108
|
+
- Does not replace `sungen-gherkin-syntax` — it supplements it with the mobile-only step vocabulary.
|
|
109
|
+
- Does not cover tap/set-value/assertions (those are the shared Tier-1 patterns already supported).
|
package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix-mobile.md
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sungen-selector-fix-mobile
|
|
3
|
+
description: 'Mobile selector fixing — Appium MCP exploration, accessibility-id-first strategy, run via wdio. Auto-loaded by run-test for @platform:mobile/android/ios screens.'
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Strategy: Phased Execution (mobile)
|
|
8
|
+
|
|
9
|
+
Mobile analogue of `sungen-selector-fix`. Same philosophy — pre-generate selectors, smoke-check, fix
|
|
10
|
+
in priority waves — but using **Appium MCP** for exploration and **WebdriverIO** (`npm run test:mobile`)
|
|
11
|
+
to run, instead of Playwright MCP / `playwright test`.
|
|
12
|
+
|
|
13
|
+
**Never run blindly.** Pre-generate selectors → smoke check → widen.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Phase 0: Pre-run Selector Generation (Appium MCP)
|
|
18
|
+
|
|
19
|
+
Populate `selectors.yaml` from the live app before compiling so tests don't fail on missing keys.
|
|
20
|
+
|
|
21
|
+
### When to run
|
|
22
|
+
- `selectors.yaml` missing/empty, or `[Reference]` keys lack entries and aren't auto-inferable.
|
|
23
|
+
- User re-scans after a UI change.
|
|
24
|
+
|
|
25
|
+
### Steps
|
|
26
|
+
1. **Confirm**: *"Generate selectors from the live app via Appium MCP now?"* —
|
|
27
|
+
**Yes, scan device** / **Skip (use existing)** / **Cancel**.
|
|
28
|
+
2. **Collect references**: parse the `.feature` for every `[Reference]` + type. Deduplicate.
|
|
29
|
+
3. **Launch the app** (delegate to `sungen-capture-mobile` or inline):
|
|
30
|
+
`select_device` → `appium_session_management(create)` with appPackage/appActivity from the feature `Path:`.
|
|
31
|
+
4. **⚠️ Wait for render** before capturing — a Flutter/RN splash exposes zero elements. Screenshot first;
|
|
32
|
+
if blank, wait and re-check.
|
|
33
|
+
5. **Capture**: `generate_locators` (priority-ranked locators) for the current screen. Navigate
|
|
34
|
+
(`appium_gesture tap` / `back`) to reach each screen referenced in the feature, capturing per screen.
|
|
35
|
+
6. **Generate YAML entries**:
|
|
36
|
+
- Keys: follow `sungen-selector-keys` (lowercase, Unicode preserved, `--N` suffix for duplicates).
|
|
37
|
+
- **Selector priority (mobile)** — see Step 3 table: `accessibility-id` > `id` > platform-native > `xpath`.
|
|
38
|
+
- Copy `content-desc`/`accessibility id` **character-for-character** from `generate_locators`. Never
|
|
39
|
+
infer from the Gherkin label.
|
|
40
|
+
- **i18n / Flutter**: `content-desc` often equals the visible text and is locale-dependent. Use
|
|
41
|
+
`{{variable}}` in `value` and add `lbl_*` keys to test-data + locale overlays (see `sungen-locale`).
|
|
42
|
+
7. **Duplicate check**: if a `content-desc` appears more than once in the tree, add `nth`.
|
|
43
|
+
8. **Merge, don't overwrite**: keep user-authored entries; add only missing keys.
|
|
44
|
+
9. **Confirm + write**, then **compile**: `sungen generate --screen <screen> --framework appium`.
|
|
45
|
+
10. **End session** (`action=delete`) when done exploring.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Phase 0.5: Mobile auth — the auth contract (guided builder)
|
|
50
|
+
|
|
51
|
+
Mobile has **no `storageState`** like web. Instead, `@auth:<role>` features authenticate at runtime
|
|
52
|
+
through an **auth contract** at `specs/.auth-mobile/<role>.json` (gitignored): the generated spec's
|
|
53
|
+
`before(all)` hook (`__ensureAuth`) reaches the logged-in state — **idempotent** (skips login when the
|
|
54
|
+
authed marker is already present thanks to `noReset:true`) and **fail-loud** (throws when the contract
|
|
55
|
+
is missing or login fails, so @auth scenarios are blocked instead of silently running logged-out).
|
|
56
|
+
|
|
57
|
+
**Pre-flight (BEFORE Phase 1):** when the feature carries `@auth:<role>`, check the contract exists:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
ls specs/.auth-mobile/<role>.json 2>/dev/null || echo MISSING
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Present → continue to Phase 1 (the hook handles everything). Missing → run the **guided builder**:
|
|
64
|
+
|
|
65
|
+
### Guided contract builder
|
|
66
|
+
|
|
67
|
+
1. `AskUserQuestion`:
|
|
68
|
+
- **Build the contract now (Recommended)** — AI drives the app via Appium MCP and writes the file
|
|
69
|
+
- **I'll write it by hand** — print the schema below and stop
|
|
70
|
+
- **Skip** — @auth scenarios will fail loud with instructions
|
|
71
|
+
2. **Logged-out baseline**: Appium session (`select_device` → `appium_session_management(create)`,
|
|
72
|
+
`noReset:true`) → activate the app → `appium_get_page_source` → keep the element list.
|
|
73
|
+
3. **Reach the login form**: ask the user how to navigate there (typical: account/profile tab → login
|
|
74
|
+
entry). Record EVERY tap on the way as a `loginSteps` entry, in order.
|
|
75
|
+
4. **Credentials**: never echo passwords into chat. Reference them as `{{email}}` / `{{password}}` in
|
|
76
|
+
`loginSteps` and have the user fill the `credentials` map in the file themselves (it is gitignored).
|
|
77
|
+
5. **Log in once** — either AI drives (`appium_gesture(tap)` + `appium_set_value`) with values the user
|
|
78
|
+
provides, or the user logs in by hand on the device. Both are fine; what matters is the end state.
|
|
79
|
+
6. **Derive the `authedMarker`**: fresh `appium_get_page_source` → DIFF against the step-2 baseline.
|
|
80
|
+
Candidates = elements present ONLY after login (member-only menu item, avatar, logout). Prefer
|
|
81
|
+
accessibility-id; verify with `appium_find_element`. Never pick an element guests also have.
|
|
82
|
+
7. **Write the contract** (schema below) and **validate**: terminate + relaunch the app → the marker is
|
|
83
|
+
still present (session persisted). Full loop: run one @auth scenario via
|
|
84
|
+
`SUNGEN_SPECS=<spec> npm run test:mobile`.
|
|
85
|
+
|
|
86
|
+
### Contract schema — `specs/.auth-mobile/<role>.json` (gitignored)
|
|
87
|
+
|
|
88
|
+
```jsonc
|
|
89
|
+
{
|
|
90
|
+
"authedMarker": { "type": "accessibility-id", "value": "<element only present when logged in>" },
|
|
91
|
+
"credentials": { "email": "...", "password": "..." }, // keys are free-form per auth method
|
|
92
|
+
"loginSteps": [
|
|
93
|
+
{ "action": "tap", "selector": { "type": "accessibility-id", "value": "<account tab>" } },
|
|
94
|
+
{ "action": "tap", "selector": { "type": "accessibility-id", "value": "<login entry>" } },
|
|
95
|
+
{ "action": "fill", "selector": { "type": "accessibility-id", "value": "<email field>" }, "value": "{{email}}" },
|
|
96
|
+
{ "action": "fill", "selector": { "type": "accessibility-id", "value": "<password field>" }, "value": "{{password}}" },
|
|
97
|
+
{ "action": "tap", "selector": { "type": "accessibility-id", "value": "<submit>" } }
|
|
98
|
+
// app shows a launch interstitial/ad? add its dismiss tap as the FIRST step
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
- `selector.type`: `accessibility-id` | `xpath` | `android-uiautomator` | `ios-predicate` | `id` | `text`/`label`/`placeholder`
|
|
104
|
+
- `action`: `tap` | `fill` (robust fill: type → read back → retry) | `wait`
|
|
105
|
+
- Session model: `noReset:true` keeps the login alive across scenarios. i18n runs flip `noReset:false`
|
|
106
|
+
(device-locale re-detect) which wipes the session — re-login in the contract or keep @auth suites on
|
|
107
|
+
the base locale.
|
|
108
|
+
- The non-automatable parts (OTP, captcha, biometrics) stay manual: have the user complete them once in
|
|
109
|
+
step 5; the persisted session + `authedMarker` check carries every later run.
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Phases 1–4: Run waves via WebdriverIO
|
|
113
|
+
|
|
114
|
+
Mobile runs go through the wdio runner (auto-selects `@wdio/globals` specs — the mobile ones):
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Smoke: a single scenario / spec
|
|
118
|
+
SUNGEN_SPECS='./specs/generated/<screen>/<feature>.spec.ts' npm run test:mobile
|
|
119
|
+
|
|
120
|
+
# Full mobile suite
|
|
121
|
+
npm run test:mobile
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
- **Phase 1 (smoke)**: run the one screen's spec. Max 2 fix attempts.
|
|
125
|
+
- **Phase 2 (priority)**: run the screen/feature suite. Max 2 fix attempts.
|
|
126
|
+
- **Phase 3 (full)**: `npm run test:mobile`. Max 1 fix attempt.
|
|
127
|
+
- **Phase 4 (regression)**: one final `npm run test:mobile`. No fix loop.
|
|
128
|
+
|
|
129
|
+
> wdio/mocha tag-grep differs from Playwright. For a subset, scope with `SUNGEN_SPECS` or
|
|
130
|
+
> `--mochaOpts.grep '<scenario name>'`.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Diagnosis & Fix (mobile)
|
|
135
|
+
|
|
136
|
+
### Step 1: Parse failures — and distinguish selector vs. state
|
|
137
|
+
|
|
138
|
+
| Error pattern | Likely root cause | Fix target |
|
|
139
|
+
|---|---|---|
|
|
140
|
+
| `element wasn't found` / `can't call click on "~X"` | **selector mismatch** *or* **app on the wrong screen** | selectors.yaml **or** scenario state (see ⚠️ below) |
|
|
141
|
+
| `waitForDisplayed` timeout | wrong locator, or element not yet rendered | selectors.yaml / add wait |
|
|
142
|
+
| `toHaveText` mismatch | wrong expected data (or locale) | test-data.yaml |
|
|
143
|
+
| `session not created` / `app not installed` | capability / install issue | wdio.conf capabilities / `adb install` |
|
|
144
|
+
|
|
145
|
+
**⚠️ State, not selector.** The most common mobile false alarm: a selector "not found" because the app
|
|
146
|
+
is **on a different screen than the scenario assumed** (e.g. a prior scenario left it deep in a sub-page;
|
|
147
|
+
`appium:noReset=true` keeps state across the whole run). Before "fixing" a selector, **verify the app's
|
|
148
|
+
current screen** with `appium_screenshot`. If the app is simply on the wrong screen, the fix is in the
|
|
149
|
+
**scenario/navigation** (ensure each scenario starts from a known state — e.g. an explicit "navigate to
|
|
150
|
+
Home" step, `back`, or relaunch), not in `selectors.yaml`.
|
|
151
|
+
|
|
152
|
+
### Step 2: Targeted MCP exploration
|
|
153
|
+
1. `select_device` + `appium_session_management(create)` (or reuse an open session).
|
|
154
|
+
2. Navigate to the failing screen (`appium_gesture`), **wait for render**.
|
|
155
|
+
3. `generate_locators` (or `appium_find_element` to test one candidate). Fix all broken selectors from
|
|
156
|
+
this snapshot. Use `appium_ai` (vision) only as a last resort for low-accessibility screens.
|
|
157
|
+
|
|
158
|
+
### Step 3: Fix broken selectors — mobile priority
|
|
159
|
+
|
|
160
|
+
| Priority | type | When |
|
|
161
|
+
|---|---|---|
|
|
162
|
+
| 1 | `accessibility-id` | element has `content-desc` / accessibilityIdentifier (most stable) |
|
|
163
|
+
| 2 | `id` | Android resource-id present |
|
|
164
|
+
| 3 | `android-uiautomator` / `ios-predicate` | platform-native, when 1–2 unavailable |
|
|
165
|
+
| 4 | `xpath` | last resort — slow & brittle; prefer `contains(@content-desc,'…')` |
|
|
166
|
+
|
|
167
|
+
Common fixes:
|
|
168
|
+
- Name mismatch → copy exact `content-desc` from `generate_locators`.
|
|
169
|
+
- Multiple matches → add `nth`.
|
|
170
|
+
- Composite `\n` content-desc → `xpath` with `contains` on a unique substring.
|
|
171
|
+
- No label (icon button, search field) → `xpath` / class, or ask devs to add a Semantics label / `Key`.
|
|
172
|
+
- Locale-dependent text → `{{variable}}` + locale overlay (don't hardcode "English"/"Good afternoon!").
|
|
173
|
+
|
|
174
|
+
### Step 4: Recompile after fix
|
|
175
|
+
```bash
|
|
176
|
+
sungen generate --screen <screen> --framework appium
|
|
177
|
+
```
|
|
178
|
+
Then re-run only the current phase.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Cross-platform selectors (`@platform:mobile` — write once, run both)
|
|
183
|
+
|
|
184
|
+
A `@platform:mobile` feature runs on **both** the Android and iOS capability sets in one
|
|
185
|
+
`MOBILE_PLATFORM=both` run, so **every selector must resolve on both OSes**. Only `accessibility-id`
|
|
186
|
+
is truly cross-platform: a Flutter `Semantics(label/identifier)` surfaces as Android `content-desc`
|
|
187
|
+
**and** iOS `name`/`accessibilityIdentifier`, so a single `~Label` matches on each.
|
|
188
|
+
|
|
189
|
+
**Rule for `@platform:mobile`: use `type: accessibility-id` for everything.** Avoid the platform-only
|
|
190
|
+
strategies — a spec that uses them passes on the OS you tested and silently fails on the other:
|
|
191
|
+
|
|
192
|
+
| Strategy | OK on | NOT on | Why it breaks cross-platform |
|
|
193
|
+
|---|---|---|---|
|
|
194
|
+
| `accessibility-id` (`~Label`) | Android + iOS | — | ✅ the one to use |
|
|
195
|
+
| `//*[contains(@content-desc,'…')]` / `@text` xpath | Android | iOS | iOS has no `content-desc`/`text` attribute |
|
|
196
|
+
| `android-uiautomator` (UiScrollable, UiSelector) | Android | iOS | UiAutomator2 only |
|
|
197
|
+
| `id=` (resource-id) | Android | iOS | Android resource-id only |
|
|
198
|
+
| `-ios predicate string:` / `-ios class chain` | iOS | Android | XCUITest only |
|
|
199
|
+
|
|
200
|
+
When scanning a `@platform:mobile` screen with Appium MCP, **verify on both a booted Android device
|
|
201
|
+
and an iOS simulator** and keep only selectors whose value matches on each (for Flutter the label is
|
|
202
|
+
normally identical). If a required element is labeled on one OS but not the other (e.g. an icon button
|
|
203
|
+
with no Semantics label on Android), either ask devs to add a matching `Semantics`/`accessibilityIdentifier`,
|
|
204
|
+
or **split the feature** into `@platform:android` + `@platform:ios` files (each may then use its native
|
|
205
|
+
strategy). Do NOT smuggle an Android-only xpath into a `@platform:mobile` selector set.
|
|
206
|
+
|
|
207
|
+
> The app id may differ per OS (e.g. a Flutter `.dev` flavor on iOS). You don't handle that in
|
|
208
|
+
> selectors — the generated spec resolves it at runtime (`driver.isIOS` → iOS bundle id from the
|
|
209
|
+
> session caps, else the Android package from `Path:`). Selectors only need to be label-stable.
|
|
210
|
+
|
|
211
|
+
For `@platform:android`-only or `@platform:ios`-only features, the **full** Step 3 priority table
|
|
212
|
+
applies — platform-native strategies are fine because the spec runs on that one OS.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Per-platform selector variants (`android:` / `ios:` in selectors.yaml)
|
|
217
|
+
|
|
218
|
+
When a logical element can't be one cross-platform selector — **composite** content-desc, **partial/dynamic**
|
|
219
|
+
text (greeting, substrings), or **per-OS-divergent** labels — give the key `android:` and `ios:` sub-selectors
|
|
220
|
+
instead of a flat `type`/`value`. For a `@platform:mobile` feature the generated spec renders a runtime
|
|
221
|
+
`driver.isIOS ? '<ios>' : '<android>'` per key (so ONE shared scenario resolves the right native selector on
|
|
222
|
+
each OS); a `@platform:android` / `@platform:ios` feature picks that variant directly.
|
|
223
|
+
|
|
224
|
+
```yaml
|
|
225
|
+
# composite on Android (contains) / predicate on iOS — one shared scenario, both OSes
|
|
226
|
+
"settings--title":
|
|
227
|
+
android: { type: xpath, value: "//*[contains(@content-desc,'{{settings_subtitle}}')]" }
|
|
228
|
+
ios: { type: ios-predicate, value: "name CONTAINS '{{settings_subtitle}}' OR label CONTAINS '{{settings_subtitle}}'" }
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
- Define **both** variants for a `@platform:mobile` key (a missing side falls back to the bare entry).
|
|
232
|
+
- `{{var}}` interpolation works inside each variant. **A shared `nth`/`scope`/`name` (at the key's top
|
|
233
|
+
level) applies to both OSes, but `nth`/`scope`/`name` CANNOT diverge per-platform** — the codegen ternary
|
|
234
|
+
swaps only strategy+value (`appium-selector-expr.hbs`). If Android and iOS need a DIFFERENT `nth`/`scope`,
|
|
235
|
+
that's a sub-feature, not a variant.
|
|
236
|
+
- **Prefer a single exact `accessibility-id`** when the label is genuinely exact on both OSes (e.g. a clean
|
|
237
|
+
Flutter Semantics label) — only reach for variants when exact can't work. Verified example: "Output Format"
|
|
238
|
+
is exact → one `accessibility-id` passes both; "Select Image"/"Select Video" are composite → need variants.
|
|
239
|
+
- This is the in-place alternative to splitting into per-OS sub-features: variants keep the scenario shared
|
|
240
|
+
and only the SELECTOR differs per OS (no scenario duplication). Use sub-features when the *scenario itself*
|
|
241
|
+
(steps/flow) differs per OS, variants when only the *locator* differs.
|
|
242
|
+
|
|
243
|
+
> Below-fold on iOS: `scroll to` IS cross-platform (Android UiScrollable / iOS swipe-loop), and
|
|
244
|
+
> `tap top of` on iOS taps the TOP-LEFT of the element — on short iOS viewports a bottom-row card may
|
|
245
|
+
> peek only a few pt above a floating bottom nav whose raised centre FAB occludes the top-CENTRE
|
|
246
|
+
> (verified live on the converter; top-left tap opens it). If a list genuinely cannot scroll further
|
|
247
|
+
> and the element stays fully occluded, only then split that case to `@platform:android`.
|
|
248
|
+
|
|
249
|
+
## Layered workflow & per-OS sub-features
|
|
250
|
+
|
|
251
|
+
A screen's mobile coverage is **layered**. The shared file is the DEFAULT — exact accessibility-id OR
|
|
252
|
+
`android:`/`ios:` selector variants (when only the locator differs). Push a case to a per-OS sub-feature
|
|
253
|
+
ONLY on genuine SCENARIO divergence (different steps/flow, OS-only element, per-platform `nth`/`scope`):
|
|
254
|
+
|
|
255
|
+
| File | Tag | Runs on | Selectors |
|
|
256
|
+
|---|---|---|---|
|
|
257
|
+
| `<screen>.feature` | `@platform:mobile` | Android **and** iOS | `accessibility-id` only |
|
|
258
|
+
| `<screen>-android.feature` | `@platform:android` | Android only | full Android arsenal (`id`, `-android uiautomator`, `@content-desc` xpath, …) |
|
|
259
|
+
| `<screen>-ios.feature` | `@platform:ios` | iOS only | full iOS arsenal (`-ios predicate`, …) |
|
|
260
|
+
|
|
261
|
+
**Discovery order (explore one OS, run the other):**
|
|
262
|
+
1. Explore the available OS (`sungen-capture-mobile`), write the **shared** cases into `<screen>.feature`
|
|
263
|
+
with accessibility-id selectors.
|
|
264
|
+
2. Run them on the other OS: `MOBILE_PLATFORM=both npm run test:mobile`. Green ⇒ done, no second
|
|
265
|
+
exploration needed.
|
|
266
|
+
3. For any element that **fails** on the 2nd OS, first add an `android:`/`ios:` selector VARIANT to its key
|
|
267
|
+
(the usual fix — composite content-desc / predicate / partial text) and re-run; the scenario stays
|
|
268
|
+
shared. Move the case into `<screen>-<os>.feature` ONLY if the FLOW itself diverges or a per-platform
|
|
269
|
+
`nth`/`scope` is required (a variant can't diverge those).
|
|
270
|
+
|
|
271
|
+
**Running the layers** (each `.feature` compiles to its own spec, routed by its `// sungen:platform=` marker):
|
|
272
|
+
```bash
|
|
273
|
+
MOBILE_PLATFORM=both npm run test:mobile # shared specs on BOTH + each OS's own sub-feature specs
|
|
274
|
+
MOBILE_PLATFORM=android npm run test:mobile # shared + @platform:android only
|
|
275
|
+
MOBILE_PLATFORM=ios npm run test:mobile # shared + @platform:ios only
|
|
276
|
+
```
|
|
277
|
+
(`wdio.conf` gives the Android cap the android+mobile specs and the iOS cap the ios+mobile specs, so a
|
|
278
|
+
shared spec runs on each and a sub-feature spec runs only on its OS.)
|
|
279
|
+
|
|
280
|
+
## Scrolling to off-screen elements (Flutter caveat)
|
|
281
|
+
|
|
282
|
+
`scroll to [X]` compiles to `.scrollIntoView()`, which needs a native `android.widget.ScrollView`.
|
|
283
|
+
Flutter/RN scrollables usually don't expose one → it errors ("Default scrollable element not found").
|
|
284
|
+
For a **Flutter scrollable list**, prefer making the *target selector* auto-scroll via an
|
|
285
|
+
`android-uiautomator` UiScrollable instead of a separate scroll step:
|
|
286
|
+
|
|
287
|
+
```yaml
|
|
288
|
+
"hindi option":
|
|
289
|
+
type: android-uiautomator
|
|
290
|
+
value: 'new UiScrollable(new UiSelector().scrollable(true)).scrollIntoView(new UiSelector().descriptionContains("हिन्दी"))'
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
When `$()` resolves that selector, UiAutomator2 scrolls the list until the element is found — no
|
|
294
|
+
separate `scroll to` needed. If the screen **isn't scrollable** (content fits the viewport) and an
|
|
295
|
+
element is merely occluded by a floating bottom bar, neither scroll nor `element.click()` works → tag
|
|
296
|
+
that scenario `@manual` until visible-point tapping lands.
|
|
297
|
+
|
|
298
|
+
## Flutter / React-Native note
|
|
299
|
+
|
|
300
|
+
These render to a canvas, but expose a **Semantics tree** to Appium as `content-desc` once a real screen
|
|
301
|
+
is drawn — so UiAutomator2 + `accessibility-id` usually works (no flutter-driver needed) **provided** the
|
|
302
|
+
app sets semantic labels. If `generate_locators` returns almost nothing on a populated screen, the app
|
|
303
|
+
likely lacks semantics → escalate: ask devs to add `Semantics`/`Key`, or consider `appium-flutter-driver`
|
|
304
|
+
(needs a debug/profile build). A blank result on a *loading* screen just means "wait for render".
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Attempt Budget Summary
|
|
309
|
+
|
|
310
|
+
| Phase | What runs | Max fix attempts |
|
|
311
|
+
|---|---|---|
|
|
312
|
+
| 0. Pre-gen | Appium MCP `generate_locators` → selectors.yaml | 1 scan |
|
|
313
|
+
| 1. Smoke | one screen spec via wdio | 2 |
|
|
314
|
+
| 2. Priority | screen/feature suite | 2 |
|
|
315
|
+
| 3. Full | `npm run test:mobile` | 1 |
|
|
316
|
+
| 4. Regression | final `npm run test:mobile` | 0 |
|
|
@@ -9,6 +9,8 @@ user-invocable: false
|
|
|
9
9
|
- **Write incrementally — never emit the whole suite in one response.** Build the `.feature` in batches via successive `Write`/`Edit` (≈10–15 scenarios per call). For **Full coverage**, write tier-by-tier: `Write` Tier 1 → `Edit` append Tier 2 → `Edit` append Tier 3.
|
|
10
10
|
→ One huge `Write` can exceed the model's output-token cap → `API Error: Claude's response exceeded the N output token maximum`. Single-pass full coverage only fits when `CLAUDE_CODE_MAX_OUTPUT_TOKENS ≥ 64000`; otherwise batch. Batching also lets the audit/reviewer run per batch — higher quality.
|
|
11
11
|
|
|
12
|
+
- **Generate group-by-group (sequential here).** Copilot has no sub-agents, so generate one viewpoint group/theme at a time, tier-by-tier, keeping each `VP-` theme in its own id prefix. (The Claude Code variant fans these out as parallel `sungen-generator` shards and merges — same output shape, just no speedup. Keep each theme self-contained so it would merge cleanly either way.)
|
|
13
|
+
|
|
12
14
|
- `spec_figma.md` exists → read file only, **NEVER** call `mcp__figma__*`
|
|
13
15
|
→ PAT auth flow already done by `sungen-capture` (mode figma-pat); re-calling fails or duplicates work.
|
|
14
16
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# .env.appium — env for `npm run test:mobile` (loaded by wdio.conf.ts). Copy to `.env.appium`
|
|
2
|
+
# (gitignored) and fill. Shell exports win over this file. Most settings auto-detect / default in
|
|
3
|
+
# wdio.conf — only the lines marked REQUIRED must be set. Full guide: docs → Mobile → Appium Setup.
|
|
4
|
+
|
|
5
|
+
# ════════════ Android (default platform) ════════════
|
|
6
|
+
# REQUIRED — local SDK + JDK (the uiautomator2 driver reads these). SDK_ROOT = same path as HOME:
|
|
7
|
+
ANDROID_HOME=/Users/<you>/Library/Android/sdk
|
|
8
|
+
ANDROID_SDK_ROOT=/Users/<you>/Library/Android/sdk
|
|
9
|
+
JAVA_HOME=/opt/homebrew/opt/openjdk@21
|
|
10
|
+
# App + device (override the defaults from `sungen init`):
|
|
11
|
+
# APP_PACKAGE=com.example.app # the app to test (launch activity is auto-detected)
|
|
12
|
+
# ANDROID_UDID=<serial> # a REAL phone (`adb devices`); emulator default = emulator-5554
|
|
13
|
+
# APP_APK=/abs/path/app.apk # alternative: install + launch from an .apk
|
|
14
|
+
|
|
15
|
+
# ════════════ iOS — Simulator (quick path, no Apple account) ════════════
|
|
16
|
+
# Set MOBILE_PLATFORM=ios and boot a sim — udid/version/name auto-detect.
|
|
17
|
+
# IOS_APP=/abs/path/MyApp.app # optional: install a Simulator .app build first
|
|
18
|
+
|
|
19
|
+
# ════════════ iOS — real iPhone (only for a physical device) ════════════
|
|
20
|
+
# A real iPhone must code-sign Appium's WebDriverAgent → IOS_TEAM_ID is REQUIRED (it signs WDA, not
|
|
21
|
+
# your app — needed even when the app is already installed). Device prep + how-to: Appium Setup §3C.
|
|
22
|
+
# MOBILE_PLATFORM=ios # REQUIRED — turn iOS on (or 'both' = Android + iOS)
|
|
23
|
+
# IOS_UDID=<device-udid> # REQUIRED — physical device (`xcrun xctrace list devices`), not a sim
|
|
24
|
+
# IOS_BUNDLE_ID=<ios bundle id> # REQUIRED — the iOS bundle (⚠ NOT the Android package)
|
|
25
|
+
# IOS_TEAM_ID=<Apple Team ID> # REQUIRED — 10-char id (Xcode → Settings → Accounts)
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Join a datasource base URL with a catalog path. Concatenate rather than rely on Playwright's
|
|
3
|
+
* baseURL resolution: an absolute path (`/user/1`) resolves against the base ORIGIN and would drop
|
|
4
|
+
* a base path component (`/api/v3`). Most APIs are mounted under such a prefix, so the full URL must
|
|
5
|
+
* be built explicitly.
|
|
6
|
+
*/
|
|
7
|
+
export declare function joinApiUrl(base: string, urlPath: string): string;
|
|
1
8
|
declare class ApiClient {
|
|
2
9
|
private configs;
|
|
3
10
|
private cfg;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"specs-api.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-api.ts"],"names":[],"mappings":"AAmDA,cAAM,SAAS;IACb,OAAO,CAAC,OAAO,CAA8C;IAE7D,OAAO,CAAC,GAAG;IAWX;;;;;OAKG;IACG,IAAI,CACR,KAAK,EAAE,MAAM,EACb,GAAG,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,WAAW,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,EACtJ,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,EAChC,IAAI,GAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAO,GACnC,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,GAAG,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;IA6CvF;;;;;;OAMG;IACG,KAAK,CACT,KAAK,EAAE,MAAM,EACb,GAAG,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,WAAW,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,EACtJ,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,EAChC,CAAC,SAAI,EACL,IAAI,GAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAO,GACnC,OAAO,CAAC;QACT,SAAS,EAAE,KAAK,CAAC;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,OAAO,CAAC;YAAC,IAAI,EAAE,GAAG,CAAC;YAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;SAAE,CAAC,CAAC;QAC9F,QAAQ,EAAE,MAAM,CAAC;QACjB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACtC,QAAQ,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;CAYH;AAED,eAAO,MAAM,GAAG,WAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"specs-api.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-api.ts"],"names":[],"mappings":"AAmDA;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAGhE;AAED,cAAM,SAAS;IACb,OAAO,CAAC,OAAO,CAA8C;IAE7D,OAAO,CAAC,GAAG;IAWX;;;;;OAKG;IACG,IAAI,CACR,KAAK,EAAE,MAAM,EACb,GAAG,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,WAAW,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,EACtJ,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,EAChC,IAAI,GAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAO,GACnC,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,GAAG,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;IA6CvF;;;;;;OAMG;IACG,KAAK,CACT,KAAK,EAAE,MAAM,EACb,GAAG,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,WAAW,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,EACtJ,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,EAChC,CAAC,SAAI,EACL,IAAI,GAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAO,GACnC,OAAO,CAAC;QACT,SAAS,EAAE,KAAK,CAAC;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,OAAO,CAAC;YAAC,IAAI,EAAE,GAAG,CAAC;YAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;SAAE,CAAC,CAAC;QAC9F,QAAQ,EAAE,MAAM,CAAC;QACjB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACtC,QAAQ,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;CAYH;AAED,eAAO,MAAM,GAAG,WAAkB,CAAC"}
|
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.api = void 0;
|
|
37
|
+
exports.joinApiUrl = joinApiUrl;
|
|
37
38
|
/* eslint-disable */
|
|
38
39
|
/**
|
|
39
40
|
* Sungen API Driver — runtime helper (auto-generated into specs/api.ts).
|
|
@@ -74,6 +75,16 @@ function loadConfig() {
|
|
|
74
75
|
function substitute(text, params) {
|
|
75
76
|
return text.replace(/:([A-Za-z_][A-Za-z0-9_]*)/g, (_m, p) => encodeURIComponent(String(params[p] ?? '')));
|
|
76
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Join a datasource base URL with a catalog path. Concatenate rather than rely on Playwright's
|
|
80
|
+
* baseURL resolution: an absolute path (`/user/1`) resolves against the base ORIGIN and would drop
|
|
81
|
+
* a base path component (`/api/v3`). Most APIs are mounted under such a prefix, so the full URL must
|
|
82
|
+
* be built explicitly.
|
|
83
|
+
*/
|
|
84
|
+
function joinApiUrl(base, urlPath) {
|
|
85
|
+
const b = base.replace(/\/$/, '');
|
|
86
|
+
return urlPath.startsWith('/') ? b + urlPath : `${b}/${urlPath}`;
|
|
87
|
+
}
|
|
77
88
|
class ApiClient {
|
|
78
89
|
constructor() {
|
|
79
90
|
this.configs = null;
|
|
@@ -127,13 +138,13 @@ class ApiClient {
|
|
|
127
138
|
// `storageState` (the @auth role's saved session) so the request shares the browser's
|
|
128
139
|
// authenticated cookies. Disposed per call so no request context lingers and hangs the process.
|
|
129
140
|
const ctx = await test_1.request.newContext({
|
|
130
|
-
baseURL: base,
|
|
131
141
|
extraHTTPHeaders: headers,
|
|
132
142
|
timeout: conf.timeout_ms ?? 15000,
|
|
133
143
|
...(opts.storageState ? { storageState: opts.storageState } : {}),
|
|
134
144
|
});
|
|
135
145
|
try {
|
|
136
|
-
|
|
146
|
+
// Full URL (not a baseURL-relative path) so a base path component like /api/v3 is preserved.
|
|
147
|
+
const res = await ctx.fetch(joinApiUrl(base, urlPath), { method: req.method, ...bodyOpt });
|
|
137
148
|
const text = await res.text();
|
|
138
149
|
let parsed = text;
|
|
139
150
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"specs-api.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-api.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"specs-api.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -49,6 +49,17 @@ function substitute(text: string, params: Record<string, any>): string {
|
|
|
49
49
|
return text.replace(/:([A-Za-z_][A-Za-z0-9_]*)/g, (_m, p) => encodeURIComponent(String(params[p] ?? '')));
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Join a datasource base URL with a catalog path. Concatenate rather than rely on Playwright's
|
|
54
|
+
* baseURL resolution: an absolute path (`/user/1`) resolves against the base ORIGIN and would drop
|
|
55
|
+
* a base path component (`/api/v3`). Most APIs are mounted under such a prefix, so the full URL must
|
|
56
|
+
* be built explicitly.
|
|
57
|
+
*/
|
|
58
|
+
export function joinApiUrl(base: string, urlPath: string): string {
|
|
59
|
+
const b = base.replace(/\/$/, '');
|
|
60
|
+
return urlPath.startsWith('/') ? b + urlPath : `${b}/${urlPath}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
52
63
|
class ApiClient {
|
|
53
64
|
private configs: Record<string, ApiDataSource> | null = null;
|
|
54
65
|
|
|
@@ -103,13 +114,13 @@ class ApiClient {
|
|
|
103
114
|
// `storageState` (the @auth role's saved session) so the request shares the browser's
|
|
104
115
|
// authenticated cookies. Disposed per call so no request context lingers and hangs the process.
|
|
105
116
|
const ctx: APIRequestContext = await request.newContext({
|
|
106
|
-
baseURL: base,
|
|
107
117
|
extraHTTPHeaders: headers,
|
|
108
118
|
timeout: conf.timeout_ms ?? 15000,
|
|
109
119
|
...(opts.storageState ? { storageState: opts.storageState } : {}),
|
|
110
120
|
});
|
|
111
121
|
try {
|
|
112
|
-
|
|
122
|
+
// Full URL (not a baseURL-relative path) so a base path component like /api/v3 is preserved.
|
|
123
|
+
const res = await ctx.fetch(joinApiUrl(base, urlPath), { method: req.method, ...bodyOpt });
|
|
113
124
|
const text = await res.text();
|
|
114
125
|
let parsed: any = text;
|
|
115
126
|
try { parsed = text ? JSON.parse(text) : null; } catch { /* non-JSON → keep text */ }
|