@sun-asterisk/sungen 3.2.24 → 3.2.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capabilities/context-router.d.ts.map +1 -1
- package/dist/capabilities/context-router.js +15 -7
- package/dist/capabilities/context-router.js.map +1 -1
- package/dist/cli/commands/capability.d.ts.map +1 -1
- package/dist/cli/commands/capability.js +6 -48
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/changed.d.ts +14 -0
- package/dist/cli/commands/changed.d.ts.map +1 -0
- package/dist/cli/commands/changed.js +128 -0
- package/dist/cli/commands/changed.js.map +1 -0
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +25 -1
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/commands/generate.js +1 -1
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/results-merge.d.ts +19 -0
- package/dist/cli/commands/results-merge.d.ts.map +1 -0
- package/dist/cli/commands/results-merge.js +191 -0
- package/dist/cli/commands/results-merge.js.map +1 -0
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +100 -12
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/index.js +4 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/exporters/api-catalog-loader.d.ts.map +1 -1
- package/dist/exporters/api-catalog-loader.js +10 -3
- package/dist/exporters/api-catalog-loader.js.map +1 -1
- package/dist/exporters/api-testcase-formatter.d.ts +9 -0
- package/dist/exporters/api-testcase-formatter.d.ts.map +1 -1
- package/dist/exporters/api-testcase-formatter.js +55 -3
- package/dist/exporters/api-testcase-formatter.js.map +1 -1
- package/dist/exporters/csv-exporter.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.js +10 -1
- package/dist/exporters/csv-exporter.js.map +1 -1
- package/dist/exporters/json-exporter.d.ts.map +1 -1
- package/dist/exporters/json-exporter.js +6 -1
- package/dist/exporters/json-exporter.js.map +1 -1
- package/dist/exporters/matrix/build.d.ts.map +1 -1
- package/dist/exporters/matrix/build.js +10 -0
- package/dist/exporters/matrix/build.js.map +1 -1
- package/dist/exporters/matrix/export.js +1 -1
- package/dist/exporters/matrix/export.js.map +1 -1
- package/dist/exporters/matrix/render-csv.d.ts.map +1 -1
- package/dist/exporters/matrix/render-csv.js +5 -1
- package/dist/exporters/matrix/render-csv.js.map +1 -1
- package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
- package/dist/exporters/matrix/render-xlsx.js +4 -1
- package/dist/exporters/matrix/render-xlsx.js.map +1 -1
- package/dist/exporters/matrix/types.d.ts +7 -0
- package/dist/exporters/matrix/types.d.ts.map +1 -1
- package/dist/exporters/matrix/types.js.map +1 -1
- package/dist/exporters/playwright-report-parser.d.ts +17 -2
- package/dist/exporters/playwright-report-parser.d.ts.map +1 -1
- package/dist/exporters/playwright-report-parser.js +68 -8
- package/dist/exporters/playwright-report-parser.js.map +1 -1
- package/dist/exporters/results-merge.d.ts +46 -0
- package/dist/exporters/results-merge.d.ts.map +1 -0
- package/dist/exporters/results-merge.js +148 -0
- package/dist/exporters/results-merge.js.map +1 -0
- package/dist/exporters/selector-key-resolver.d.ts +4 -0
- package/dist/exporters/selector-key-resolver.d.ts.map +1 -1
- package/dist/exporters/selector-key-resolver.js +101 -1
- package/dist/exporters/selector-key-resolver.js.map +1 -1
- package/dist/exporters/types.d.ts +8 -1
- package/dist/exporters/types.d.ts.map +1 -1
- package/dist/exporters/xlsx-report-builder.d.ts.map +1 -1
- package/dist/exporters/xlsx-report-builder.js +5 -4
- package/dist/exporters/xlsx-report-builder.js.map +1 -1
- package/dist/generators/gherkin-parser/index.d.ts +2 -0
- package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
- package/dist/generators/gherkin-parser/index.js +52 -1
- package/dist/generators/gherkin-parser/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts +20 -10
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts +2 -11
- package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/appium/appium-adapter.js.map +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/imports.hbs +3 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/date-picker-unsupported.hbs +4 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/lifecycle-missing-app-id.hbs +5 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/matches-format-assertion.hbs +15 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +5 -2
- package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +185 -26
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +2 -11
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs +17 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/attribute-assertion.hbs +6 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/have-value-assertion.hbs +18 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-filter-assertion.hbs +5 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/matches-format-assertion.hbs +5 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +4 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-for-element-with-text.hbs +4 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-expect-text.hbs +1 -1
- package/dist/generators/test-generator/code-generator.d.ts +6 -0
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +80 -7
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/diagnostics.d.ts +60 -6
- package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
- package/dist/generators/test-generator/diagnostics.js +59 -5
- package/dist/generators/test-generator/diagnostics.js.map +1 -1
- package/dist/generators/test-generator/patterns/expect-patterns.d.ts +7 -0
- package/dist/generators/test-generator/patterns/expect-patterns.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/expect-patterns.js +36 -2
- package/dist/generators/test-generator/patterns/expect-patterns.js.map +1 -1
- package/dist/generators/test-generator/patterns/index.d.ts +19 -12
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +58 -38
- package/dist/generators/test-generator/patterns/index.js.map +1 -1
- package/dist/generators/test-generator/patterns/types.d.ts +23 -0
- package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/unsupported-step-code.d.ts +18 -0
- package/dist/generators/test-generator/patterns/unsupported-step-code.d.ts.map +1 -0
- package/dist/generators/test-generator/patterns/unsupported-step-code.js +23 -0
- package/dist/generators/test-generator/patterns/unsupported-step-code.js.map +1 -0
- package/dist/generators/test-generator/recipe-expander.js +9 -1
- package/dist/generators/test-generator/recipe-expander.js.map +1 -1
- package/dist/generators/test-generator/reset-tier.d.ts +68 -0
- package/dist/generators/test-generator/reset-tier.d.ts.map +1 -0
- package/dist/generators/test-generator/reset-tier.js +94 -0
- package/dist/generators/test-generator/reset-tier.js.map +1 -0
- package/dist/generators/test-generator/step-mapper.d.ts +39 -0
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +144 -5
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/template-engine.d.ts +2 -10
- package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
- package/dist/generators/test-generator/template-engine.js +16 -2
- package/dist/generators/test-generator/template-engine.js.map +1 -1
- package/dist/generators/test-generator/utils/format-pattern.d.ts +35 -0
- package/dist/generators/test-generator/utils/format-pattern.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/format-pattern.js +82 -0
- package/dist/generators/test-generator/utils/format-pattern.js.map +1 -0
- package/dist/generators/test-generator/utils/js-literal-scanner.d.ts +42 -0
- package/dist/generators/test-generator/utils/js-literal-scanner.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/js-literal-scanner.js +142 -0
- package/dist/generators/test-generator/utils/js-literal-scanner.js.map +1 -0
- package/dist/generators/test-generator/utils/mask-refs-for-matching.d.ts +18 -0
- package/dist/generators/test-generator/utils/mask-refs-for-matching.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/mask-refs-for-matching.js +25 -0
- package/dist/generators/test-generator/utils/mask-refs-for-matching.js.map +1 -0
- package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts +9 -1
- package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/runtime-data-transformer.js +124 -63
- package/dist/generators/test-generator/utils/runtime-data-transformer.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +20 -1
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/generators/test-generator/utils/spec-syntax-check.d.ts +14 -0
- package/dist/generators/test-generator/utils/spec-syntax-check.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/spec-syntax-check.js +26 -0
- package/dist/generators/test-generator/utils/spec-syntax-check.js.map +1 -0
- package/dist/harness/annotation-overrides.d.ts +0 -12
- package/dist/harness/annotation-overrides.d.ts.map +1 -1
- package/dist/harness/annotation-overrides.js +9 -5
- package/dist/harness/annotation-overrides.js.map +1 -1
- package/dist/harness/audit.js +4 -4
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/capability-plan.d.ts +2 -1
- package/dist/harness/capability-plan.d.ts.map +1 -1
- package/dist/harness/capability-plan.js +8 -3
- package/dist/harness/capability-plan.js.map +1 -1
- package/dist/harness/capability.d.ts +11 -0
- package/dist/harness/capability.d.ts.map +1 -1
- package/dist/harness/capability.js +8 -0
- package/dist/harness/capability.js.map +1 -1
- package/dist/harness/catalog/drivers.yaml +5 -0
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +5 -1
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/run-manifest.d.ts +58 -0
- package/dist/harness/run-manifest.d.ts.map +1 -0
- package/dist/harness/run-manifest.js +264 -0
- package/dist/harness/run-manifest.js.map +1 -0
- package/dist/harness/viewpoint-ledger.d.ts +1 -1
- package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
- package/dist/harness/viewpoint-ledger.js +25 -4
- package/dist/harness/viewpoint-ledger.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/orchestrator/lockstep-version.d.ts +22 -0
- package/dist/orchestrator/lockstep-version.d.ts.map +1 -0
- package/dist/orchestrator/lockstep-version.js +58 -0
- package/dist/orchestrator/lockstep-version.js.map +1 -0
- package/dist/orchestrator/project-deps-sync.d.ts +39 -0
- package/dist/orchestrator/project-deps-sync.d.ts.map +1 -0
- package/dist/orchestrator/project-deps-sync.js +175 -0
- package/dist/orchestrator/project-deps-sync.js.map +1 -0
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +5 -2
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/run-test.md +109 -0
- package/dist/orchestrator/templates/ai-src/config/claude.md +1 -1
- package/dist/orchestrator/templates/ai-src/config/copilot.md +1 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-api-design/SKILL.md +49 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +14 -3
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +110 -6
- package/dist/orchestrator/templates/ai-src/skills/sungen-locale/SKILL.md +1 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-mobile-gestures/SKILL.md +8 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +1 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +9 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +15 -0
- package/dist/orchestrator/templates/env.appium.example +10 -0
- package/dist/orchestrator/templates/specs-api.d.ts +4 -2
- package/dist/orchestrator/templates/specs-api.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-api.js +233 -10
- package/dist/orchestrator/templates/specs-api.js.map +1 -1
- package/dist/orchestrator/templates/specs-api.ts +214 -11
- package/dist/orchestrator/templates/specs-regex-escape.d.ts +13 -0
- package/dist/orchestrator/templates/specs-regex-escape.d.ts.map +1 -0
- package/dist/orchestrator/templates/specs-regex-escape.js +19 -0
- package/dist/orchestrator/templates/specs-regex-escape.js.map +1 -0
- package/dist/orchestrator/templates/specs-regex-escape.ts +16 -0
- package/dist/orchestrator/templates/specs-test-data.ts +53 -6
- package/dist/orchestrator/templates/wdio.conf.ts +28 -9
- package/package.json +8 -6
- package/src/capabilities/context-router.ts +13 -5
- package/src/cli/commands/capability.ts +4 -38
- package/src/cli/commands/changed.ts +104 -0
- package/src/cli/commands/delivery.ts +27 -2
- package/src/cli/commands/generate.ts +1 -1
- package/src/cli/commands/results-merge.ts +153 -0
- package/src/cli/commands/update.ts +72 -13
- package/src/cli/index.ts +4 -0
- package/src/exporters/api-catalog-loader.ts +10 -2
- package/src/exporters/api-testcase-formatter.ts +59 -3
- package/src/exporters/csv-exporter.ts +11 -2
- package/src/exporters/json-exporter.ts +7 -2
- package/src/exporters/matrix/build.ts +9 -0
- package/src/exporters/matrix/export.ts +1 -1
- package/src/exporters/matrix/render-csv.ts +5 -1
- package/src/exporters/matrix/render-xlsx.ts +4 -1
- package/src/exporters/matrix/types.ts +7 -0
- package/src/exporters/playwright-report-parser.ts +68 -9
- package/src/exporters/results-merge.ts +150 -0
- package/src/exporters/selector-key-resolver.ts +81 -1
- package/src/exporters/types.ts +8 -1
- package/src/exporters/xlsx-report-builder.ts +5 -4
- package/src/generators/gherkin-parser/index.ts +55 -1
- package/src/generators/test-generator/adapters/adapter-interface.ts +23 -1
- package/src/generators/test-generator/adapters/appium/appium-adapter.ts +2 -2
- package/src/generators/test-generator/adapters/appium/templates/imports.hbs +3 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/date-picker-unsupported.hbs +4 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/lifecycle-missing-app-id.hbs +5 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/matches-format-assertion.hbs +15 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +5 -2
- package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +185 -26
- package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +2 -2
- package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs +17 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/attribute-assertion.hbs +6 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/have-value-assertion.hbs +18 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-filter-assertion.hbs +5 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/matches-format-assertion.hbs +5 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +4 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-for-element-with-text.hbs +4 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-expect-text.hbs +1 -1
- package/src/generators/test-generator/code-generator.ts +86 -7
- package/src/generators/test-generator/diagnostics.ts +60 -6
- package/src/generators/test-generator/patterns/expect-patterns.ts +37 -2
- package/src/generators/test-generator/patterns/index.ts +58 -33
- package/src/generators/test-generator/patterns/types.ts +36 -0
- package/src/generators/test-generator/patterns/unsupported-step-code.ts +19 -0
- package/src/generators/test-generator/recipe-expander.ts +9 -1
- package/src/generators/test-generator/reset-tier.ts +93 -0
- package/src/generators/test-generator/step-mapper.ts +149 -6
- package/src/generators/test-generator/template-engine.ts +18 -3
- package/src/generators/test-generator/utils/format-pattern.ts +79 -0
- package/src/generators/test-generator/utils/js-literal-scanner.ts +132 -0
- package/src/generators/test-generator/utils/mask-refs-for-matching.ts +21 -0
- package/src/generators/test-generator/utils/runtime-data-transformer.ts +126 -76
- package/src/generators/test-generator/utils/selector-resolver.ts +22 -1
- package/src/generators/test-generator/utils/spec-syntax-check.ts +22 -0
- package/src/harness/annotation-overrides.ts +10 -5
- package/src/harness/audit.ts +4 -4
- package/src/harness/capability-plan.ts +10 -5
- package/src/harness/capability.ts +16 -0
- package/src/harness/catalog/drivers.yaml +5 -0
- package/src/harness/parse.ts +5 -1
- package/src/harness/run-manifest.ts +254 -0
- package/src/harness/viewpoint-ledger.ts +26 -5
- package/src/index.ts +1 -0
- package/src/orchestrator/lockstep-version.ts +46 -0
- package/src/orchestrator/project-deps-sync.ts +175 -0
- package/src/orchestrator/project-initializer.ts +5 -2
- package/src/orchestrator/templates/ai-src/commands/run-test.md +109 -0
- package/src/orchestrator/templates/ai-src/config/claude.md +1 -1
- package/src/orchestrator/templates/ai-src/config/copilot.md +1 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-api-design/SKILL.md +49 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +14 -3
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +110 -6
- package/src/orchestrator/templates/ai-src/skills/sungen-locale/SKILL.md +1 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-mobile-gestures/SKILL.md +8 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +1 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +9 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +15 -0
- package/src/orchestrator/templates/env.appium.example +10 -0
- package/src/orchestrator/templates/specs-api.ts +214 -11
- package/src/orchestrator/templates/specs-regex-escape.ts +16 -0
- package/src/orchestrator/templates/specs-test-data.ts +53 -6
- package/src/orchestrator/templates/wdio.conf.ts +28 -9
|
@@ -80,7 +80,7 @@ qa/deliverables/<name>-testcases.xlsx # Styled workbook for client hand-off
|
|
|
80
80
|
|
|
81
81
|
## Test Data
|
|
82
82
|
|
|
83
|
-
`{{variable}}` references in `.feature` map to keys in `test-data/<screen>.yaml`. Data is loaded **at runtime** — the same generated `.spec.ts` works across environments without recompiling.
|
|
83
|
+
`{{variable}}` references in `.feature` map to keys in `test-data/<screen>.yaml`. They belong in slots that take a value (`with {{v}}`, `as {{v}}`, `contains {{v}}`) — never inside a `[Target]`, which is a selector key matched literally. Data is loaded **at runtime** — the same generated `.spec.ts` works across environments without recompiling.
|
|
84
84
|
|
|
85
85
|
**Environment overrides**: `SUNGEN_ENV=staging npx playwright test` merges `<screen>.staging.yaml` on top of `<screen>.yaml`. Create `<screen>.<env>.yaml` for environment-specific values (different credentials, URLs, test users).
|
|
86
86
|
|
|
@@ -84,6 +84,41 @@ A flow (`create → login → delete`) is a **Functional integration** test, **n
|
|
|
84
84
|
|
|
85
85
|
Fixture path resolves cwd-relative/absolute first, else `qa/fixtures/<path>` (drop sample files there, reference by name from `test-data`). An empty resolved file param omits the part → use for missing-file `@cases` error rows. `files:`/`bodyFile:` are mutually exclusive. A **`GET`/`HEAD` entry may carry no body at all** (no `body:`/`files:`/`bodyFile:`) — HTTP forbids it and the catalog lint refuses it; put the inputs in the path or `query`, or use `POST`. **Automate the upload success case with `@api`** — don't defer it to `@manual`.
|
|
86
86
|
|
|
87
|
+
## XML APIs (`encoding: xml`)
|
|
88
|
+
|
|
89
|
+
For services that accept/return XML (OTA/SOAP-style backends). Works with ANY XML shape — attributes, namespaces, CDATA, nesting.
|
|
90
|
+
|
|
91
|
+
**Send** — the body is a raw string template (YAML block scalar); declared `params:` bind as `:name` tokens:
|
|
92
|
+
```yaml
|
|
93
|
+
stock_check:
|
|
94
|
+
method: POST
|
|
95
|
+
path: /agent-api
|
|
96
|
+
encoding: xml
|
|
97
|
+
params: [agent_code, signature, item_code]
|
|
98
|
+
body: |
|
|
99
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
|
100
|
+
<request>
|
|
101
|
+
<method>StockCheck</method>
|
|
102
|
+
<agent_code>:agent_code</agent_code>
|
|
103
|
+
<signature>:signature</signature>
|
|
104
|
+
<item_code>:item_code</item_code>
|
|
105
|
+
</request>
|
|
106
|
+
```
|
|
107
|
+
Only DECLARED params bind (namespace prefixes like `soapenv:Envelope` are never touched). Bound values are XML-escaped (`& < > " '`) so data can't corrupt the document; wrap a token as `<![CDATA[:param]]>` to bind raw. Content-Type defaults to `application/xml` (override via `headers:`).
|
|
108
|
+
|
|
109
|
+
**Read** — a response whose Content-Type contains `xml` (never `html`) parses to an object for `{{name.body.<path>}}`; a missing/octet-stream Content-Type is sniffed only when the body starts with `<?xml`. Charset comes from the Content-Type, else the XML declaration, else UTF-8 (Shift_JIS responses decode correctly). Mapping:
|
|
110
|
+
|
|
111
|
+
| XML | Bound value |
|
|
112
|
+
|---|---|
|
|
113
|
+
| `<error_code>01000001</error_code>` | `{{r.body.response.error_code}}` = `"01000001"` (always strings — no numeric coercion, leading zeros survive) |
|
|
114
|
+
| `<smok></smok>` / `<smok/>` | `""` |
|
|
115
|
+
| `<shop id="7">` | `{{r.body.response.shop.@_id}}` = `"7"` (attributes keep an `@_` prefix) |
|
|
116
|
+
| `<soapenv:Body>` | key stays `soapenv:Body` (`{{r.body.soapenv:Envelope.soapenv:Body...}}`) |
|
|
117
|
+
| `<memo><![CDATA[x & y]]></memo>` | plain text `x & y`; `&`-style entities decode (the 5 predefined only) |
|
|
118
|
+
| repeated `<item>` | declare `arrayPaths: [r.items.item]` (dot path INCLUDING the root element) — then 1 item and N items are BOTH arrays and `[0]` access never breaks. Without it, one item is an object and two are an array. |
|
|
119
|
+
|
|
120
|
+
Caveats: an element literally named `count`/`first`/`last`/`length` under an array parent is shadowed by the path helper's own accessors; parse failure keeps the raw string body (same as any non-JSON response); the 5 predefined entities decode even inside response CDATA sections (a deliberate deviation from strict XML — assert the decoded form). Field metadata / `matches schema` / OpenAPI import for XML are not supported yet. The project needs the `fast-xml-parser` dev dependency (new projects get it from `sungen init`; older ones: `npm install --save-dev fast-xml-parser` — generate warns when it's missing).
|
|
121
|
+
|
|
87
122
|
## Per-endpoint knobs & auth patterns
|
|
88
123
|
- **Timeout** — a slow endpoint can override the datasource default (15s) with `timeout_ms: 30000` on its catalog entry (else the datasource `timeout_ms` applies).
|
|
89
124
|
- **OAuth2 (pure API, no browser)** — no new grammar; it's `@api` chaining. Call the token endpoint, bind the token, thread it into later calls' `Bearer :token` header:
|
|
@@ -95,6 +130,20 @@ Fixture path resolves cwd-relative/absolute first, else `qa/fixtures/<path>` (dr
|
|
|
95
130
|
```
|
|
96
131
|
The token endpoint is a normal catalog entry (`POST /oauth/token`, form/JSON body); `list_orders` carries `headers: { authorization: "Bearer :token" }`. For a browser-authorized session instead, use `@hybrid` + `@auth:<role>`.
|
|
97
132
|
- **Pagination** — use query params (`?page=:page&limit=:limit`, imported into `params`) + assert the page shape with plain field access (`{{list.body.next_page_token}}`, `{{list.body.items.length}}`); no special primitive needed.
|
|
133
|
+
- **Download an absolute URL from a response** (export/report endpoints returning a `body.url` on blob storage): declare a catalog entry whose `path:` is EXACTLY one token, and thread the URL as its param — this is the only supported syntax:
|
|
134
|
+
```yaml
|
|
135
|
+
download_export:
|
|
136
|
+
method: GET
|
|
137
|
+
path: ":url"
|
|
138
|
+
params: [url]
|
|
139
|
+
```
|
|
140
|
+
```gherkin
|
|
141
|
+
@api:export_report @api:download_export(url={{export_report.body.url}})
|
|
142
|
+
Scenario: VP-API-FLOW exported file is downloadable
|
|
143
|
+
Then expect {{export_report.status}} is 200
|
|
144
|
+
And expect {{download_export.status}} is 200
|
|
145
|
+
```
|
|
146
|
+
The bound value must be an ABSOLUTE http(s) URL — an empty string, a relative path, or any other scheme (`file:`, `data:`, `mailto:`) fails the run loudly, so a broken export returning `{"url": ""}` can never false-pass. Presigned query escapes survive verbatim; a non-JSON body (CSV/text) is kept as text for `expect {{download_export.body}}` assertions. A catalog entry may also declare a literal absolute `path: "https://…"` (its `:params` bind with normal encoding). **Security:** when the REQUEST URL's origin differs from the datasource `base_url`, none of the datasource headers are sent (auth/tenant defaults must not leak to another origin) — a cross-origin download that needs a header must declare it in the entry's own `headers:`. This boundary applies to the initial request only: a same-origin request that REDIRECTS to another origin still carries the datasource headers (fetch auto-strips only `authorization`/`cookie`/`proxy-authorization` cross-origin) — prefer binding the final URL over following an API-origin redirect. The 15s default timeout may be short for large files (`timeout_ms:` on the entry).
|
|
98
147
|
|
|
99
148
|
## Rules
|
|
100
149
|
- **No HTTP, no selectors** — only `.feature` + the reviewed `apis.yaml` + `test-data`.
|
|
@@ -77,6 +77,7 @@ needs any of these, it is a **finding for QA** — surface it in the run summary
|
|
|
77
77
|
|---|---|---|
|
|
78
78
|
| strict mode violation | Multiple elements match | Add `nth: 0`, `exact: true`, or more specific `name` |
|
|
79
79
|
| strict mode violation on `see`/`hover [X] item\|option\|cell with {{v}}` (dynamic list) | Older builds didn't filter the locator by the value | Since 3.2.13 `see`/`hover ... with {{v}}` on list-member roles filters by `hasText` like `click` (existence/hover the matching item) — keep the step data-driven, don't add per-item selectors. Update sungen if still unfiltered |
|
|
80
|
+
| strict mode violation on a filtered list step whose value is a PREFIX of sibling texts (e.g. `1:00` also inside `11:00`) | The `hasText` filter matches by SUBSTRING by default (case-insensitive, like Playwright), so several members match | Add `exact: true` to the selector YAML entry — it anchors the filter to the full text and is honored for `type: locator` entries too |
|
|
80
81
|
| Timeout / not found | Element doesn't exist or name wrong | Re-snapshot → copy exact accessible name. Check iframe/dialog scope |
|
|
81
82
|
| Element is not an input | Wrong element type targeted | Change `type` or `value` to match actual element |
|
|
82
83
|
| not a select | Custom dropdown, not native `<select>` | Set the widget variant: `variant: radix-select`/`antd-select` (built-in recipes), a custom recipe name from `qa/app.yaml`, or project-wide `widgets.select` in `qa/app.yaml` |
|
|
@@ -86,7 +87,7 @@ needs any of these, it is a **finding for QA** — surface it in the run summary
|
|
|
86
87
|
|
|
87
88
|
| Code | Meaning | Fix |
|
|
88
89
|
|---|---|---|
|
|
89
|
-
| `SG-W001` | Step matched no pattern —
|
|
90
|
+
| `SG-W001` | Step matched no pattern — sungen has no step form for the sentence. Compiles to `throw new Error("[sungen] …")` naming the step (it emitted a no-op `// TODO` before the unsupported-step gate, which let the scenario PASS asserting nothing) | Rephrase to a documented pattern (`sungen-gherkin-syntax`) or tag `@manual` with a reason. There is no silent fallback — the test goes red until one of those two is done |
|
|
90
91
|
| `SG-W002` | A step's `{{value}}` was parsed but never used in the generated code | The step form isn't the one you think — check the pattern's exact syntax |
|
|
91
92
|
| `SG-W003` | `{ name }` filter defaulted from the Gherkin reference on a name-from-content role (toast/alert/status) | Add explicit `name:` or opt out with `name: null` |
|
|
92
93
|
| `SG-W005` | `qa/app.yaml` unknown key / unknown preset / unparseable | Fix the key, or `extends: native\|radix\|antd` |
|
|
@@ -95,14 +96,23 @@ needs any of these, it is a **finding for QA** — surface it in the run summary
|
|
|
95
96
|
| `SG-W008` | `@manual` scenario incomplete in either direction: (a) HAS Gherkin steps but NO `Then` — a dangling `When` is invalid Gherkin, comments can NOT substitute; (b) documentation-style (NO steps) but its comments lack numbered action lines and/or a labeled oracle line — prose paragraphs / rationale-only comments export EMPTY Steps + Expected cells | Two valid shapes only: complete the Gherkin with a `Then`, or write the FULL procedure in the `# Tester verifies:` block as NUMBERED lines — action steps first (`1. <action>`), then labeled `Observable:`/`Oracle:`/`Expected:` lines (the number is what fills Steps, the label is what fills Expected results) |
|
|
96
97
|
| `SG-W009` | test-data references `${QA_<NAME>}` (a SECRET env ref) but the name is declared in neither `.env.qa` nor `.env.qa.example` — the test throws "environment variable not set" the moment the key is used. (Only the `${QA_*}` shape is checked; any other `${...}` is literal data and never flagged.) | Add `QA_<NAME>=<value>` to `.env.qa` (gitignored) and list the NAME in `.env.qa.example` (the committed checklist). See `qa/TEST-DATA-GUIDE.md` |
|
|
97
98
|
| `SG-W010` | A row step's `[Ref]` looks POSITIONAL (`First…`/`Last…`/`Top…`/`nth`) but has no selectors.yaml entry — the label is display-only, so the step checks existence ANYWHERE in the table; an ordering/sort-stability rule would pass even when the row moved | Declare the row in selectors.yaml (a `type: locator` value RELATIVE to the table, e.g. `first contact row:` → `value: 'tbody tr:first-child'`) — the step then asserts `toContainText` on that exact row. This is a selectors.yaml-only fix — allowed inside the run-test auto-fix loop |
|
|
98
|
-
| `SG-W011` | A storage assertion written as `expect … in local/session storage` — `expect` steps only read `{{response}}` refs, so this
|
|
99
|
+
| `SG-W011` | A storage assertion written as `expect … in local/session storage` — `expect` steps only read `{{response}}` refs, so this matches no pattern. Compiles to a `throw` (was a silent no-op) | Rewrite using the supported grammar: `User see [KEY] in local storage exists/…` |
|
|
99
100
|
| `SG-W012` | A mock-install step written AFTER a navigation step in the same block — `page.route()` registered after `goto()` misses every request fired during page load | Move the mock-install step before the navigation, or into `Background` |
|
|
100
101
|
| `SG-W013` | A page assertion (`see [X] page` / `is on [X] page`) whose `[Ref]` has no `type: page` selector entry (or collides with a non-page entry) — the step falls back to the feature's own path (or `/<ref>/`) instead of `X`'s real URL, so the anchored assertion can never pass | Declare a `type: page` entry for `[Ref]` with its real URL; if the key collides with another type, disambiguate with a `--type` suffix (`sungen-selector-keys` § Collision rule) |
|
|
101
102
|
| `SG-W014` | `[X] page with {{v}}` where `{{v}}`'s base test-data value carries no query and no fragment — the step checks the PATH only, asserting less than it reads as | Informational — pass a value like `?q=…` if you meant to assert a query, or drop `with {{v}}` for a bare page |
|
|
103
|
+
| `SG-W015` | `see [X] card/item with {{n}}` where `{{n}}`'s test-data value is a **bare number** — this compiles to a TEXT assertion (`toHaveText('3')`), NOT a count. An author reaching for "3 cards are shown" gets an assertion that can only pass if the card's TEXT is literally the digit: a wrong test with no error. Scoped to container-ish element types (`card`/`item`/`option` + plurals); a text element asserting a numeric readout (`[Row Quantity] text with 4`) stays silent | For a COUNT use a form that has count semantics: `see [Ref] row with {{n}}` / `see [T] table with {{n}}`, or a list `should have count`. If the number really is the element's text, assert it on a **text/label** element instead |
|
|
102
104
|
| `SG-E020` | A step matched a pattern, but the **active adapter ships no template** for it (e.g. a web-only step compiled under `platform: mobile`). The feature file still generates — that one step compiles to `throw new Error("[sungen] …")` naming the step, feature, pattern, template and adapter, so the failure is loud and traceable rather than a crashed build | Rephrase to a step the target adapter actually ships (see `sungen-gherkin-syntax` Platform Support section / `sungen-mobile-gestures`), or tag the scenario `@manual` with the platform reason |
|
|
105
|
+
| `SG-E021` | The step carries a trailing **state clause** (`… is hidden`) that the pattern which matched it does not assert — the clause would be SILENTLY DROPPED. Worst shape in this table: it does not weaken an assertion, it can INVERT one. `see [X] page is hidden` matched `page-assertion` (priority 13) ahead of `is-hidden` (11), so the step compiled to `toHaveURL(...)` — asserting the page IS displayed, and passing. Also fires on `wait for [X] is checked` (a wait honours only `hidden`/`visible`/`disabled`/`enabled`; everything else silently fell back to `visible`) and on `row`/`column in [T] table … is <state>` for any state but `hidden` | Move the state onto the element that has it: `Then User see [X] checkbox is checked`. For a page, assert the page you expect (`Then User see [Other] page`) or a marker element (`Then User see [Banner] is hidden`). If sungen cannot express the check, tag the scenario `@manual`. The diagnostic names the pattern that swallowed the clause — that name tells you which form you actually hit |
|
|
103
106
|
| `SG-W020` | The matched pattern **declares `platforms`** (today: `@mock`) and the active platform isn't among them. Caught before template lookup, so the diagnostic can name the native alternative directly | Drop `@mock` from a mobile unit (there is no Mock Driver on Appium) — use `@api`/`@query` or a `@manual` note instead |
|
|
104
107
|
| `SG-W021` | `use dialog` / `User is on [X] dialog` scope under the **mobile** adapter — the scope is recorded but no Appium template reads `inDialog`, so every following step resolves against the whole screen, not just the dialog | Don't rely on `scope: dialog`/`use dialog` for disambiguation on mobile — give the element inside the dialog its own unique accessibility-id/testid instead |
|
|
105
108
|
| `SG-W022` | `Then User see [X] page` under the **mobile** adapter — a native app has no URL, so the step compiles to a bare COMMENT: it reads as an assertion, checks nothing, and the scenario passes whatever is on screen. Worse than a hard failure, because nothing ever goes red. (Its `is on [X] page` twin throws via `route-assertion`; `Given User is on [X] page` is the app-LAUNCH directive and correctly emits nothing) | Assert something actually on the screen — `Then User see [Some Header] text` / a screen-marker accessibility-id — instead of a page/URL check, or tag the scenario `@manual` |
|
|
109
|
+
| `SG-W023` | A mobile **setup** step that emitted NO executable code, so a declared PRECONDITION is never established. `Given the user is logged out` matches `user-not-logged-in` → the `clear-auth` template, which on Appium is a bare COMMENT: mobile auth lives in the keychain/prefs, not in cookies, so no per-step action can clear it. The scenario then runs against whatever the PREVIOUS scenario left signed in and passes regardless — the same false-pass class as `SG-W022`, arriving through setup instead of assertion. Deliberately NOT a `throw` (an unestablished precondition weakens setup, it does not INVERT an assertion) | Declare a feature-level reset tier — `@reset:data` on the Feature — so each scenario starts from a wiped data layer; app state is reset in the `beforeEach` hook, not mid-scenario by a step. For BACKEND state (`database is empty`) the remedy is different: reset through a test API/script, or bind a `@query:` precondition, the same way the web adapter has to |
|
|
110
|
+
| `SG-W024` | Two or more scenarios in one feature **share a name** and at least ONE carries a `@reset:` tag. The generated per-scenario tier lookup is keyed on the scenario NAME (a `@cases` title is built at runtime, so no compile-time key can match the rendered title), so every same-named scenario resolves to the SAME tier — including one that declared none, which then silently runs at its namesake's tier: a `@reset:none` chain broken, or a reinstall nobody asked for. Counted over EVERY scenario rather than the tagged subset, because the untagged twin is the one that gets the wrong tier and has no tag to be counted by | **Rename** the scenarios so each title is unique. Reported rather than resolved by precedence on purpose — two tests genuinely sharing one title cannot be told apart by a name-keyed lookup, so a rename is the only honest fix (silently picking a winner would be the same silent-discard shape as `SG-W021`) |
|
|
111
|
+
| `SG-E022` | The **generated spec file itself** is not parseable JS/TS, so NOTHING in it runs — Playwright reports one `SyntaxError` and every scenario in the unit is lost. A compiler self-check (`@babel/parser`) on the emitted code, added after a field report where the runtime-data marker post-processor mis-paired quote delimiters across two literals and the user only found out at `npx playwright test`. Reported, never thrown — the file is still written so you keep the artifact to inspect. **Unlike every other code in this table this one blames the COMPILER, not your `.feature`** | Nothing to fix in the Gherkin — this should be impossible. **Report it** with the `.feature` (and the step) the file was generated from, attaching the unparseable `.spec.ts`. Check first that you are running the project-local compiler (`npx sungen`), not an older global one |
|
|
112
|
+
| `SG-E023` | The step carries an **assertion clause** (`matches "…"`, `contains`, `has text`) but the generated code is a BARE VISIBILITY check — the clause was swallowed by a catch-all and the test would pass whatever the element says. The sibling of `SG-E021`, for the clauses that are not states: `SG-E021` closed the state case declaratively, but a dropped `matches`/`contains`/`has text` still slid through (reproduced live: a `matches "<format>"` step compiled by an older sungen lost the assertion entirely, emitted no warning, and left a `toBeVisible()` that passed whatever the label said). Four separate defects arrived this way, so the step now compiles to a runtime `throw`. Deliberately narrow — fires only on the bare-visibility shape, so a pattern that genuinely consumed the clause (`is-hidden`, `contains`, `matches-format`, the state family, or a `filter({ hasText })` on a list member) is untouched | Either the phrasing is not the supported one for that clause — check the exact form in `sungen-gherkin-syntax` — or this sungen predates the step: verify you are running the project-local compiler (`npx sungen`), not an older global one |
|
|
113
|
+
| `SG-E024` | A mobile **app-lifecycle** step (`User restart the app` / `User reinstall the app`) in a feature that declares no `Path:` app id. The lifecycle templates resolve the target app through `__resolveAppId()`, a helper the appium test-file template emits ONLY when the id is present, so the spec used to compile clean and then die at runtime with `ReferenceError: __resolveAppId is not defined` — taking the WHOLE spec down, not the one step, because the failure lands in a before hook. (Field case: a mobile unit scaffolded with the nav recipe in `--path` instead of `--reach`, so `Path` never carried an app id.) Now refused at compile time as a `throw` naming the missing line | Add the app id under the `Feature` line: `Path: com.example.app/.MainActivity \| com.example.app`. For a mobile unit `--path` carries the **APP ID** and `--reach` carries the navigation recipe — if the nav recipe ended up in `--path`, that is the mistake |
|
|
114
|
+
|
|
115
|
+
A diagnostic never changes generation's own exit code — `sungen generate --strict` turns any collected diagnostic into a non-zero exit for CI gates. They are not all inert, though: **`SG-W001`, `SG-W011`, `SG-E020`, `SG-E021`, `SG-W020`, `SG-E023` and `SG-E024` additionally compile the offending step to a runtime `throw`** — a step that reads as a check and silently checks nothing is worse than one that fails loudly. Generation always COMPLETES: the rest of the scenario and the rest of the file still compile. `SG-E022` is the odd one out — it blames the compiler, not your `.feature`, and throws nothing (the file is written so you can inspect it).
|
|
106
116
|
|
|
107
117
|
### Runtime error → `Test data "<key>" references ${QA_*} but the environment variable is not set`
|
|
108
118
|
|
|
@@ -123,7 +133,8 @@ The scenario uses a SECRET test-data key whose `QA_*` variable is missing in thi
|
|
|
123
133
|
### Assertion type rule
|
|
124
134
|
|
|
125
135
|
Sungen picks assertion based on element type:
|
|
126
|
-
- **Input** (`field`, `textarea`, `search`, `
|
|
136
|
+
- **Input** (`field`, `textarea`, `search`, `slider`) → `toHaveValue()`
|
|
137
|
+
- **Dropdown** (`dropdown`, `select`, `combobox`) → runtime tag probe: `toHaveValue()` on a native `<select>`/input, `toHaveText()` on a `<button role="combobox">` trigger (Radix/shadcn, MUI). Pin one branch with `variant: native` or an `assertValue` recipe in `qa/app.yaml`.
|
|
127
138
|
- **Text** (everything else: `message`, `heading`, `label`, `row`) → `toHaveText()`
|
|
128
139
|
- **Partial** (`contains` keyword) → `toContainText()`
|
|
129
140
|
|
|
@@ -36,7 +36,7 @@ AND → inherits from preceding keyword
|
|
|
36
36
|
|
|
37
37
|
```
|
|
38
38
|
User is on [T] page | page with {{v}} | dialog
|
|
39
|
-
User fill [T] field | textarea | search | slider | date-picker with {{v}}
|
|
39
|
+
User fill [T] field | textarea | search | slider | date-picker with {{v}} # date-picker: web only unless qa/app.yaml declares widgets.datepicker — appium refuses it
|
|
40
40
|
User fill [T] uploader with {{f}} [web]
|
|
41
41
|
User clear [T] field
|
|
42
42
|
User check [T] checkbox | toggle | radio
|
|
@@ -104,13 +104,23 @@ two content-filtered queries can hit different rows if the table re-renders in b
|
|
|
104
104
|
# 1. Visibility: User see [T] type (NEVER add "is visible") | is hidden
|
|
105
105
|
# 2. Text (toHaveText): User see [T] message | header | label with {{v}}
|
|
106
106
|
# 3. Partial (toContainText): User see [T] text contains {{v}}
|
|
107
|
-
#
|
|
107
|
+
# 3b. Format (shape, not value): User see [T] label matches "yyyy/mm/dd ~ yyyy/mm/dd"
|
|
108
|
+
# 4. Input (toHaveValue): User see [T] field | date-picker | search | slider with {{v}}
|
|
109
|
+
# dropdown | select | combobox → tag probed at runtime (toHaveValue native, toHaveText custom trigger)
|
|
108
110
|
# 5. State: User see [T] button is disabled | checkbox is checked | dialog with {{v}} is hidden
|
|
109
111
|
# 6. Attribute (toHaveAttribute): User see [T] image | link with {{v}}
|
|
110
112
|
# 7. Count: User see [T] row with {{count}}
|
|
111
113
|
# 8. Page: User see [T] page | page with {{url}} ← `with` also asserts the query string
|
|
114
|
+
# NEVER a state: `page is hidden` / `page is empty` … are NOT step forms (see Pattern 8 below)
|
|
112
115
|
```
|
|
113
116
|
|
|
117
|
+
> **`page` takes NO state clause.** The only two forms are `see [T] page` and `see [T] page with
|
|
118
|
+
> {{url}}`. `see [X] page is hidden` is not a supported step: `page` compiles to a URL assertion, so
|
|
119
|
+
> the clause is dropped and the step asserts the page IS displayed — the opposite of what it reads
|
|
120
|
+
> as. The compiler now rejects it (`SG-E021`) instead of generating that. To say "we left page X",
|
|
121
|
+
> assert the page you expect (`Then User see [Other] page`); to say "this went away", assert a marker
|
|
122
|
+
> element (`Then User see [Some Element] is hidden`).
|
|
123
|
+
|
|
114
124
|
**Pattern 8 — the page assertion judges pathname AND query together.** Both `see [T] page` and
|
|
115
125
|
`is on [T] page` (Then-side) compile to ONE web-first predicate — `toHaveURL(urlMatches({…}))`,
|
|
116
126
|
which retries like any other assertion — comparing the **exact** pathname (anchored, trailing slash
|
|
@@ -197,7 +207,31 @@ Then User see key matching "PATTERN" in local storage # regex over key name
|
|
|
197
207
|
# same five forms with `session storage`
|
|
198
208
|
```
|
|
199
209
|
|
|
200
|
-
`[KEY]` is a **storage key, not a selector** — never add it to selectors.yaml. It may embed `{{vars}}`: `[{{exclusive_code}}_ACCESS_TOKEN]`. The check runs inside the browser and returns only a boolean, so a failure message never contains the stored value (safe for tokens). There is deliberately no `equals {{expected}}` form. ⚠️ `expect [KEY] in local storage …` is NOT valid (`expect` reads `{{response}}` refs only) — the compiler warns SG-W011.
|
|
210
|
+
`[KEY]` is a **storage key, not a selector** — never add it to selectors.yaml. It may embed `{{vars}}`: `[{{exclusive_code}}_ACCESS_TOKEN]`. **This is the ONLY bracket that accepts a `{{var}}`.** An element `[Reference]` is a selectors.yaml key matched **literally** — it is never interpolated, so `[{{var}}]` for an element is invalid and the compiler rejects it. To target text that varies (locale, `@cases` column), keep the reference static and put the variable in the selector entry's `name:`/`value:` field, or pick by text with `with {{var}}` (`User click [Answer List] item with {{confirm_value}}`). The check runs inside the browser and returns only a boolean, so a failure message never contains the stored value (safe for tokens). There is deliberately no `equals {{expected}}` form. ⚠️ `expect [KEY] in local storage …` is NOT valid (`expect` reads `{{response}}` refs only) — the compiler warns SG-W011.
|
|
211
|
+
|
|
212
|
+
### Format assertion `see [T] type matches "<format>"`
|
|
213
|
+
|
|
214
|
+
A spec often states a label's contract as a **shape**, not a value — "Format: yyyy/mm/dd ~ yyyy/mm/dd",
|
|
215
|
+
"Format: 残りX個". Write it verbatim; no regex belongs in a `.feature`:
|
|
216
|
+
|
|
217
|
+
```gherkin
|
|
218
|
+
Then User see [Application Period] label matches "yyyy/mm/dd ~ yyyy/mm/dd"
|
|
219
|
+
Then User see [Remaining Stock] label matches "残りX個"
|
|
220
|
+
Then User see [Total Plans] label matches "*X件"
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
| Token | Matches | | Token | Matches |
|
|
224
|
+
|---|---|---|---|---|
|
|
225
|
+
| `yyyy` | a 4-digit year | | `X` | an integer, `1,280` allowed |
|
|
226
|
+
| `mm` `dd` `hh` `ss` | a zero-padded 2-digit part | | `*` | any run of characters |
|
|
227
|
+
|
|
228
|
+
Everything else is a **literal**. The match is **anchored** — `"X件"` is not satisfied by `12件です`;
|
|
229
|
+
use `*` where the label legitimately carries more (a mobile node that fuses a prefix and its count,
|
|
230
|
+
`開催中の企画\n2件` → `"*X件"`). A token glued to an ASCII letter stays literal, so `"Comment: X件"`
|
|
231
|
+
keeps its `mm`.
|
|
232
|
+
|
|
233
|
+
Use this instead of `with {{v}}` when the VALUE varies but the SHAPE is the requirement: an exact
|
|
234
|
+
value pins data that changes daily (red while the app is right), and `contains` passes on anything.
|
|
201
235
|
|
|
202
236
|
### Tab order `[web]`
|
|
203
237
|
|
|
@@ -307,8 +341,13 @@ Full-shape contract check: `expect {{name.body}} matches schema [Ref]` validates
|
|
|
307
341
|
### Platform Support — web-only / mobile-only / divergences
|
|
308
342
|
|
|
309
343
|
Every claim below is checked against a shipped `.hbs` under
|
|
310
|
-
`adapters/{playwright,appium}/templates/steps/` or a shipped diagnostic
|
|
311
|
-
|
|
344
|
+
`adapters/{playwright,appium}/templates/steps/` or a shipped diagnostic. The authoritative
|
|
345
|
+
per-template answer is **generated from the adapters themselves** — `docs/spec/step-support-matrix.md`
|
|
346
|
+
(regenerate: `npm run gen:step-matrix`; `DIVERGENCES` lives in `tools/step-support-matrix/sources.ts`). Read
|
|
347
|
+
it when this prose and the compiler seem to disagree: the matrix is derived, this section is authored.
|
|
348
|
+
|
|
349
|
+
The matrix maps **templates**, not phrasings — a phrasing no pattern requests is absent from it
|
|
350
|
+
entirely (a *pattern* gap, not a template gap), which is what the catalogue below is for.
|
|
312
351
|
|
|
313
352
|
**Web-only `[web]`** — the Appium template throws, naming the reason: `fill [T] uploader with
|
|
314
353
|
{{f}}`, Positional table rows, the whole Table section, Browser storage, Tab order, `@mock`,
|
|
@@ -355,6 +394,10 @@ zero-match assertion failure, not a platform gap.
|
|
|
355
394
|
| Group | Types |
|
|
356
395
|
|---|---|
|
|
357
396
|
| **Context** | `page` `dialog` `modal` `drawer` `tab` `alert` `overlay` `step` |
|
|
397
|
+
|
|
398
|
+
> `page` is the one Context type with **no state**: `dialog`/`modal`/`drawer`/`tab` etc. all take
|
|
399
|
+
> `is hidden` / `is visible`, `page` never does. It asserts a URL. See Pattern 8.
|
|
400
|
+
|
|
358
401
|
| **Input** | `field` `textarea` `search` `dropdown` `option` `checkbox` `radio` `toggle` `uploader` `slider` `date-picker` |
|
|
359
402
|
| **Trigger** | `button` `link` `icon` `menuitem` `tag` |
|
|
360
403
|
| **Data** | `table` `row` `column` `cell` `list` `item` `card` `section` |
|
|
@@ -443,12 +486,68 @@ on mobile a dialog-scoped ref still resolves against the whole screen (`SG-W021`
|
|
|
443
486
|
| `@afterEach` | Hook: runs after each test → `test.afterEach()` (custom cleanup) |
|
|
444
487
|
| `@afterAll` | Hook: runs once after all tests → `test.afterAll()` |
|
|
445
488
|
| `@flow` | Mark feature as E2E flow (cross-screen testing) |
|
|
489
|
+
| `@reset:none\|soft\|data\|full` | **`[mobile]`** How much state each scenario starts from. Default `soft`. Feature-level, or on a Scenario to RAISE (never lower) that one scenario's tier. See "Reset tiers" below |
|
|
446
490
|
| `@cases:dataset` | Data-driven: run the scenario once per row of the `dataset` LIST in test-data → one `test()` per row |
|
|
447
491
|
| `@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) |
|
|
448
492
|
| `@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) |
|
|
449
493
|
| `@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) |
|
|
450
494
|
| `@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) |
|
|
451
495
|
|
|
496
|
+
### Reset tiers (`@reset`) `[mobile]`
|
|
497
|
+
|
|
498
|
+
Web gets a clean slate for free — a URL reload is effectively fresh state. Mobile does not: to save
|
|
499
|
+
time the app stays alive across the whole session, so the end state of one scenario becomes the start
|
|
500
|
+
state of the next. Left uncontrolled that breaks test independence (scenarios can't run in isolation
|
|
501
|
+
or in parallel, a leftover "nearly right" state makes the FOLLOWING scenario fail, and a scenario that
|
|
502
|
+
dies mid-flow poisons every one after it). Nobody just reinstalls between scenarios because a cold
|
|
503
|
+
start is 5–30s — hence a ladder rather than one all-or-nothing switch.
|
|
504
|
+
|
|
505
|
+
| Tier | Mechanism | Cost | Clears |
|
|
506
|
+
|---|---|---|---|
|
|
507
|
+
| `none` | nothing — only the NATIVE-context + portrait guards | 0s | — state carries over BY DESIGN |
|
|
508
|
+
| `soft` | `terminateApp` + `activateApp` — **the default** | ~1–3s | in-memory state, navigation stack |
|
|
509
|
+
| `data` | `soft` + wipe the app's data layer | ~2–5s | + local DB, cache, prefs, keychain |
|
|
510
|
+
| `full` | `removeApp` + `installApp` | ~5–30s | + permissions, install-time state |
|
|
511
|
+
|
|
512
|
+
```gherkin
|
|
513
|
+
@platform:mobile @reset:data
|
|
514
|
+
Feature: Onboarding
|
|
515
|
+
Path: com.example.app/.MainActivity | com.example.app
|
|
516
|
+
|
|
517
|
+
Scenario: VP-001 First-run tutorial shows for a brand-new user
|
|
518
|
+
Then User see [Tutorial Slide 1] section
|
|
519
|
+
|
|
520
|
+
@reset:full
|
|
521
|
+
Scenario: VP-002 Needs a clean install, unlike its neighbours
|
|
522
|
+
Then User see [Permission Prompt] dialog
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
Rules that matter when generating:
|
|
526
|
+
|
|
527
|
+
- **`soft` is the default** — omit the tag unless the scenario actually needs more. An untagged
|
|
528
|
+
feature behaves exactly as it always did.
|
|
529
|
+
- **Pick `data` for first-run / onboarding / empty-state oracles.** A plain relaunch leaves a
|
|
530
|
+
completed tutorial completed, so the scenario passes on run 1 and fails on run 2.
|
|
531
|
+
- **Pick `full` only for permissions or install-time state.** It costs a cold start every scenario.
|
|
532
|
+
- **Pick `none` only for a deliberate carryover chain** (a cart accumulating across scenarios). Order
|
|
533
|
+
becomes load-bearing, and the scenarios can no longer run independently — that is the trade.
|
|
534
|
+
- **A scenario tier RAISES the feature tier, never lowers it.** A `@reset:full` feature is not weakened
|
|
535
|
+
by a `@reset:soft` scenario; use a separate feature if you need a weaker one.
|
|
536
|
+
- **A failed scenario escalates the NEXT one by one rung** (`soft`→`data`→`full`), because a process
|
|
537
|
+
relaunch does not undo a half-written local DB. One rung, one scenario, and never from `none`.
|
|
538
|
+
A **retry** of the failed scenario is NOT escalated — it re-runs at its declared tier, so a
|
|
539
|
+
state-dependent failure fails both attempts and is reported honestly instead of passing on attempt 2.
|
|
540
|
+
- **`SUNGEN_RESET_TIER=data` raises the floor for a whole run** without regenerating specs — CI can run
|
|
541
|
+
stricter than local dev. It never lowers a declared tier.
|
|
542
|
+
- **`data`/`full` on iOS reinstall the app**, because `mobile: clearApp` is Android-only (UiAutomator2).
|
|
543
|
+
That needs `IOS_APP` (or `APP_APK` on Android) so the `appium:app` capability is present, or the
|
|
544
|
+
reset throws naming it.
|
|
545
|
+
- **`data` and `full` wipe app data on the target device.** Emulator / dedicated test device only.
|
|
546
|
+
|
|
547
|
+
Mobile app-state preconditions have no per-step equivalent: `Given the user is logged out` compiles to
|
|
548
|
+
a COMMENT on Appium (auth lives in the keychain, not in cookies) and reports **SG-W023**. Declare
|
|
549
|
+
`@reset:data` on the Feature instead of writing a step that establishes nothing.
|
|
550
|
+
|
|
452
551
|
### Data-driven scenarios (`@cases`)
|
|
453
552
|
|
|
454
553
|
For one test case × many inputs (email/format/boundary validation, decision tables), tag the
|
|
@@ -575,7 +674,7 @@ dashboard:
|
|
|
575
674
|
|
|
576
675
|
- Tool executes **only Given→When** of `@steps` scenario (skips Then)
|
|
577
676
|
- The `Given` in `@extend` scenario is the **entry assertion** (confirms state after base steps)
|
|
578
|
-
- **Entry assertion MUST use `Given User is on [X]
|
|
677
|
+
- **Entry assertion MUST use `Given User is on [X] page`** (or `[X] dialog`) — NEVER `Given User see [X] type`. `is on` takes ONLY `page` (`screen` on mobile) and `dialog`: it names a navigation target. For a state inside the current page (a wizard step, a panel) assert it with `Then User see [X] section` and reach it via the flow's When steps or an `@steps`/`@extend` base — the compiler rejects `is on [X] <other type>`.
|
|
579
678
|
- `Given` keyword ONLY allows `is on` action. `see` = `Then` only.
|
|
580
679
|
- If `@steps` scenario fails, `@extend` scenario is **skipped**
|
|
581
680
|
- Name format: `snake_case` or `kebab-case` with module prefix: `@steps:kudos__open_modal`
|
|
@@ -593,6 +692,11 @@ dashboard:
|
|
|
593
692
|
| Hardcode data | `with {{admin@mail.com}}` | `with {{invalid_email}}` |
|
|
594
693
|
| Missing `is` for state | `with {{text}} hidden` | `with {{text}} is hidden` |
|
|
595
694
|
| State as value | `with {{disabled}}` | `is disabled` |
|
|
695
|
+
| State on a `page` | `Then User see [Home] page is hidden` | `Then User see [Other] page` — or assert a marker element: `Then User see [Home Banner] is hidden`. `page` asserts a URL; the clause is dropped and the assertion INVERTS. Rejected by `SG-E021` |
|
|
696
|
+
| State the step form cannot assert | `wait for [X] spinner is checked` · `see [R] row in [T] table with {{v}} is disabled` | Assert the state on the element itself: `Then User see [X] checkbox is checked`. A `wait` only honours `hidden`/`visible`/`disabled`/`enabled`; a row/column step only honours `is hidden`. Anything else is dropped → `SG-E021` |
|
|
697
|
+
| Invented step form | any sentence with no matching pattern | Use a form from this reference. There is no silent fallback any more — an unmatched step compiles to a runtime `throw` (`SG-W001`). If the check cannot be automated, tag the scenario `@manual` |
|
|
698
|
+
| Assertion clause the pattern drops | `Then User see [Code] text matches "<format>"` compiling to a bare `toBeVisible()` | Use the exact supported phrasing for the clause (see the `matches` / `contains` / `has text` forms above). A clause that gets swallowed leaves an assertion that passes whatever the element says, so the step is rejected with `SG-E023` instead. If the step still compiles bare, you are on an older global sungen — run `npx sungen` |
|
|
699
|
+
| Mobile lifecycle step with no app id | `When User restart the app` in a feature whose `Path:` carries only a route | Declare the app id on the Feature: `Path: com.example.app/.MainActivity \| com.example.app`. The lifecycle steps resolve the app from `Path`; without it the whole spec would die at runtime, so the step is rejected with `SG-E024`. For a mobile unit `--path` carries the APP ID, `--reach` carries the navigation recipe |
|
|
596
700
|
| Missing target type | `fill [email] with {{v}}` | `fill [email] field with {{v}}` |
|
|
597
701
|
| Background with scope | `Background: ... And User is on [X] dialog` | Use `@steps` + `@extend` for scope-dependent flows |
|
|
598
702
|
| `is on` after When | `When ... And User is on [X] dialog` | `And User see [X] dialog` or separate Given |
|
|
@@ -575,7 +575,7 @@ If the screen has multiple `.feature` files (e.g. `home.feature` + `home-modal.f
|
|
|
575
575
|
|
|
576
576
|
## What NOT to do
|
|
577
577
|
|
|
578
|
-
- Do not edit `.feature` files.
|
|
578
|
+
- Do not edit `.feature` files. Their `[Target]` labels are locale-stable keys — they are matched literally and must NOT be turned into `[{{var}}]`. Localised text lives in the selector entry's `name:`/`value:`, so only selectors + test-data need surgery.
|
|
579
579
|
{{#cap parallel-subagents}}
|
|
580
580
|
- Do not write a separate selectors file per locale (`home.en.yaml`). One selectors file with `{{var}}` works across all locales — that's the whole point.
|
|
581
581
|
- Do not delete keys from the base `test-data/<feature>.yaml`. Always append. Existing tests rely on base values being complete.
|
|
@@ -43,6 +43,14 @@ patterns (`click`, `hover`, `fill`) don't cover.
|
|
|
43
43
|
> - **`reinstall the app`** → remove → install from the session's build file → launch. **Requires
|
|
44
44
|
> the session to have installed via `APP_APK` / `IOS_APP`** (the `appium:app` cap) — attaching by
|
|
45
45
|
> package only makes the step fail loud with that exact fix in the message.
|
|
46
|
+
>
|
|
47
|
+
> **These two are IN-scenario steps. Reset BETWEEN scenarios is a tag, not a step** — `@reset:` on the
|
|
48
|
+
> Feature (`none` · `soft` = default · `data` · `full`), which runs in the `beforeEach` hook. Reach for
|
|
49
|
+
> the tag when the whole suite needs a cleaner starting state, and for these steps when one scenario's
|
|
50
|
+
> ORACLE is "does this survive a restart / a reinstall". Writing a step to establish app state does not
|
|
51
|
+
> work on mobile: `Given the user is logged out` compiles to a comment (auth lives in the keychain, not
|
|
52
|
+
> in cookies) and reports **SG-W023** — declare `@reset:data` instead. Full tier table + trade-offs:
|
|
53
|
+
> `sungen-gherkin-syntax` → "Reset tiers".
|
|
46
54
|
> - **`turn airplane mode|wifi|mobile data on|off`** → `mobile: setConnectivity`. **Android-only** —
|
|
47
55
|
> XCUITest cannot toggle connectivity; on iOS the step throws loud. Keep such scenarios
|
|
48
56
|
> `@platform:android`. Do NOT author cleanup steps to restore connectivity "in case the test
|
|
@@ -57,7 +57,7 @@ When running Phase 0 for a **flow** (`qa/flows/<name>/`), check existing screen
|
|
|
57
57
|
2. **Collect references**: parse the `.feature` file for every `[Reference]` element + its type (e.g. `[Submit] button`, `[Email] field`). Deduplicate.
|
|
58
58
|
|
|
59
59
|
⛔ **Non-selector bracket refs — never hunt the DOM for these, never write a selectors.yaml entry:**
|
|
60
|
-
- `[KEY] in local storage …` / `[KEY] in session storage …` — a **storage key**, checked via `page.evaluate`, not an element (the key may embed `{{vars}}`, e.g. `[{{code}}_ACCESS_TOKEN]`).
|
|
60
|
+
- `[KEY] in local storage …` / `[KEY] in session storage …` — a **storage key**, checked via `page.evaluate`, not an element (the key may embed `{{vars}}`, e.g. `[{{code}}_ACCESS_TOKEN]` — this is the ONLY bracket that accepts one; an element `[Reference]` is matched literally and `[{{var}}]` is rejected by the compiler).
|
|
61
61
|
- Bracket refs consumed by other non-DOM step families (`@mock` route refs) follow the same rule: if the step's grammar does not target a UI element, the bracket content is NOT a selector.
|
|
62
62
|
Writing a fabricated entry for these breaks the real step (the pattern parses the bracket itself) and pollutes selectors.yaml.
|
|
63
63
|
✅ **Tab-order table cells ARE selector refs** — each `| [Ref] |` row under `Then User see tab order:` resolves through selectors.yaml like any element (verify them once; they are the same refs the scenario's other steps use — don't double-count them as new elements).
|
|
@@ -449,6 +449,15 @@ Cross-pattern: `@high` by default.
|
|
|
449
449
|
|
|
450
450
|
---
|
|
451
451
|
|
|
452
|
+
## Format over value
|
|
453
|
+
|
|
454
|
+
When the spec states a label's contract as a **shape** ("Format: yyyy/mm/dd ~ yyyy/mm/dd", "Format: X件", "Format: 残りX個"), generate `matches "<format>"` — never `with {{v}}`. An exact value pins data that changes daily (red while the app is right); `contains` proves nothing. Tokens: `yyyy mm dd hh ss X *`. Anchored — use `*` when a node fuses a prefix with the value. See `sungen-gherkin-syntax` → Format assertion.
|
|
455
|
+
|
|
456
|
+
```gherkin
|
|
457
|
+
Then User see [Application Period] label matches "yyyy/mm/dd ~ yyyy/mm/dd"
|
|
458
|
+
Then User see [Total Plans] label matches "*X件"
|
|
459
|
+
```
|
|
460
|
+
|
|
452
461
|
## Priority Tags
|
|
453
462
|
|
|
454
463
|
Every scenario **MUST** have exactly one priority tag. Assign by user impact per the **"Assign priority by user impact"** table in `sungen-gherkin-syntax`; override only when context differs.
|
|
@@ -46,7 +46,12 @@ Hard-fail gate. Any ✗ is a syntax error — must be fixed; do not score furthe
|
|
|
46
46
|
- [ ] `Given` → only `is on` · `When` → action (click/fill/select/press/clear/check/uncheck/hover/wait) · `Then` → only `see` · `And` inherits the preceding keyword
|
|
47
47
|
- [ ] NO `Given User see…` (→ `Then`, or entry assertion `Given User is on [X] page`)
|
|
48
48
|
- [ ] NO `When … And User is on [X] dialog` (→ `And User see` or a separate `Given`)
|
|
49
|
+
- [ ] `dismiss [X]` is SETUP, never proof — it is best-effort and never fails, so a scenario that
|
|
50
|
+
claims an overlay closed must assert it: `Then User see [X] is hidden` (mobile: tap the
|
|
51
|
+
close/scrim node explicitly, then assert)
|
|
52
|
+
- [ ] `is on` takes ONLY `[X] page` (`[X] screen` on mobile) / `[X] dialog` — `is on [X] step|section|card` ✗ (it compiles to a real navigation, or to a URL assertion that cannot fail). A state inside the page → `Then User see [X] section`.
|
|
49
53
|
- [ ] NO literal-URL navigation (`navigate to "/x"`) → `User is on [X] page` + declare in `selectors.yaml`
|
|
54
|
+
- [ ] NO `{{var}}` inside a `[Target]` (`[{{confirm_value}}]` ✗) — a target is a selector key matched literally. For text that varies (locale, `@cases` column) keep the target static and move the variable into the selector entry's `name:`/`value:`, or pick by text with `with {{var}}`. Only a browser-storage key may embed one.
|
|
50
55
|
|
|
51
56
|
### A2. Action → Element type
|
|
52
57
|
|
|
@@ -67,6 +72,16 @@ Hard-fail gate. Any ✗ is a syntax error — must be fixed; do not score furthe
|
|
|
67
72
|
### A4. Assertion (8 patterns → determines the Playwright assertion)
|
|
68
73
|
|
|
69
74
|
- [ ] Visibility `see [T] type` → NO redundant `is visible`; hidden uses `is hidden`
|
|
75
|
+
- [ ] **No state clause on `[T] page`** — `page is hidden`/`is empty`/… are not step forms. `page`
|
|
76
|
+
asserts a URL, so the clause is dropped and the assertion inverts (`SG-E021`). Assert the
|
|
77
|
+
expected page, or a marker element
|
|
78
|
+
- [ ] **The state is one the step form can actually assert** — a `wait for` honours only
|
|
79
|
+
`hidden`/`visible`/`disabled`/`enabled`; `row in [T] table` / `column in [T] table` honour
|
|
80
|
+
only `is hidden`. Anything else is dropped (`SG-E021`) — move the state onto the element
|
|
81
|
+
- [ ] **No invented step forms** — every step matches a pattern in `sungen-gherkin-syntax`. An
|
|
82
|
+
unmatched step compiles to a runtime `throw` (`SG-W001`), never a silent pass; park a
|
|
83
|
+
genuinely unautomatable check as `@manual`
|
|
84
|
+
- [ ] A spec `Format:` line → `matches "<format>"`, NOT `with {{v}}` (an exact value pins data that changes daily; `contains` proves nothing)
|
|
70
85
|
- [ ] Correct pattern: text(`message/header/label with {{v}}`) · partial(`text contains {{v}}`) · input(`field/dropdown with {{v}}`) · state(`is …`) · attribute(`image/link with {{v}}`) · count(`row with {{count}}`) · page(`[T] page`)
|
|
71
86
|
- [ ] Table: `[Col] column in [Table] table`, `[Ref] row in [Table] table with {{v}}`, `table with {{count}}`/`is empty`, `table match data:`; row scope used correctly
|
|
72
87
|
|
|
@@ -24,6 +24,16 @@ JAVA_HOME=/opt/homebrew/opt/openjdk@21
|
|
|
24
24
|
# IOS_BUNDLE_ID=<ios bundle id> # REQUIRED — the iOS bundle (⚠ NOT the Android package)
|
|
25
25
|
# IOS_TEAM_ID=<Apple Team ID> # REQUIRED — 10-char id (Xcode → Settings → Accounts)
|
|
26
26
|
|
|
27
|
+
# ════════════ State reset between scenarios ════════════
|
|
28
|
+
# Raises the reset floor for a WHOLE run without regenerating any spec — CI can run stricter than
|
|
29
|
+
# local dev. It never LOWERS a tier a feature declared with @reset:. Tiers, weakest → strongest:
|
|
30
|
+
# none nothing reset (state carries over by design) 0s
|
|
31
|
+
# soft terminate + relaunch — THE DEFAULT ~1–3s
|
|
32
|
+
# data + wipe local DB / cache / prefs / keychain ~2–5s (iOS: reinstalls, needs IOS_APP)
|
|
33
|
+
# full + remove and reinstall (permissions too) ~5–30s (needs APP_APK / IOS_APP)
|
|
34
|
+
# ⚠ data and full WIPE APP DATA on the target device — emulator / dedicated test device only.
|
|
35
|
+
# SUNGEN_RESET_TIER=data
|
|
36
|
+
|
|
27
37
|
# ════════════ Appium server security (opt-in) ════════════
|
|
28
38
|
# Allow `mobile: shell` (raw adb passthrough) for device-level oracles (e.g. pulling an app's
|
|
29
39
|
# SQLite DB via run-as). Starts the server with --allow-insecure=uiautomator2:adb_shell.
|
|
@@ -40,12 +40,13 @@ declare class ApiClient {
|
|
|
40
40
|
method: string;
|
|
41
41
|
path: string;
|
|
42
42
|
body?: unknown;
|
|
43
|
-
encoding?: 'json' | 'form' | 'multipart';
|
|
43
|
+
encoding?: 'json' | 'form' | 'multipart' | 'xml';
|
|
44
44
|
files?: Record<string, FileSpec | FileSpec[]>;
|
|
45
45
|
bodyFile?: FileSpec;
|
|
46
46
|
headers?: Record<string, string>;
|
|
47
47
|
timeout?: number;
|
|
48
48
|
datasource?: string;
|
|
49
|
+
arrayPaths?: string[];
|
|
49
50
|
}, params?: Record<string, any>, opts?: {
|
|
50
51
|
storageState?: string;
|
|
51
52
|
}): Promise<{
|
|
@@ -65,12 +66,13 @@ declare class ApiClient {
|
|
|
65
66
|
method: string;
|
|
66
67
|
path: string;
|
|
67
68
|
body?: unknown;
|
|
68
|
-
encoding?: 'json' | 'form' | 'multipart';
|
|
69
|
+
encoding?: 'json' | 'form' | 'multipart' | 'xml';
|
|
69
70
|
files?: Record<string, FileSpec | FileSpec[]>;
|
|
70
71
|
bodyFile?: FileSpec;
|
|
71
72
|
headers?: Record<string, string>;
|
|
72
73
|
timeout?: number;
|
|
73
74
|
datasource?: string;
|
|
75
|
+
arrayPaths?: string[];
|
|
74
76
|
}, params?: Record<string, any>, n?: number, opts?: {
|
|
75
77
|
storageState?: string;
|
|
76
78
|
}): Promise<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"specs-api.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-api.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"specs-api.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-api.ts"],"names":[],"mappings":"AAwOA,yFAAyF;AACzF,KAAK,QAAQ,GAAG,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAO/F,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AACD,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAM/D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,EAAE,CAAC,EAC5C,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,KAAK,EAAE,MAAM,GACZ,QAAQ,CAsBV;AAED;;;;;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,GAAG,KAAK,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,EAAE,CAAC,CAAC;QAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,EAC3Q,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;IAkLvF;;;;;;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,GAAG,KAAK,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,EAAE,CAAC,CAAC;QAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,EAC3Q,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;IAaF;;;;;OAKG;IACH,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;CAGlF;AA2CD,eAAO,MAAM,GAAG,WAAkB,CAAC"}
|