@sun-asterisk/sungen 3.2.27 → 3.2.29
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/discover.d.ts +29 -0
- package/dist/capabilities/discover.d.ts.map +1 -1
- package/dist/capabilities/discover.js +144 -10
- package/dist/capabilities/discover.js.map +1 -1
- package/dist/capabilities/registry.d.ts +18 -0
- package/dist/capabilities/registry.d.ts.map +1 -1
- package/dist/capabilities/registry.js +25 -0
- package/dist/capabilities/registry.js.map +1 -1
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +9 -0
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/capability.js +1 -1
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/dashboard/templates/index.html +1 -1
- package/dist/exporters/feature-parser.d.ts +1 -1
- package/dist/exporters/feature-parser.js +1 -1
- package/dist/exporters/matrix/build.d.ts.map +1 -1
- package/dist/exporters/matrix/build.js +14 -1
- package/dist/exporters/matrix/build.js.map +1 -1
- package/dist/exporters/matrix/item-labels.d.ts +35 -0
- package/dist/exporters/matrix/item-labels.d.ts.map +1 -0
- package/dist/exporters/matrix/item-labels.js +90 -0
- package/dist/exporters/matrix/item-labels.js.map +1 -0
- package/dist/exporters/matrix/render-coverage-sheet.d.ts +14 -0
- package/dist/exporters/matrix/render-coverage-sheet.d.ts.map +1 -0
- package/dist/exporters/matrix/render-coverage-sheet.js +160 -0
- package/dist/exporters/matrix/render-coverage-sheet.js.map +1 -0
- package/dist/exporters/matrix/render-overview-sheet.d.ts +20 -0
- package/dist/exporters/matrix/render-overview-sheet.d.ts.map +1 -0
- package/dist/exporters/matrix/render-overview-sheet.js +36 -0
- package/dist/exporters/matrix/render-overview-sheet.js.map +1 -0
- package/dist/exporters/matrix/render-testcase-sheet.d.ts +30 -0
- package/dist/exporters/matrix/render-testcase-sheet.d.ts.map +1 -0
- package/dist/exporters/matrix/render-testcase-sheet.js +156 -0
- package/dist/exporters/matrix/render-testcase-sheet.js.map +1 -0
- package/dist/exporters/matrix/render-xlsx.d.ts +41 -31
- package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
- package/dist/exporters/matrix/render-xlsx.js +72 -425
- package/dist/exporters/matrix/render-xlsx.js.map +1 -1
- package/dist/exporters/matrix/testcase-summary-band.d.ts +30 -0
- package/dist/exporters/matrix/testcase-summary-band.d.ts.map +1 -0
- package/dist/exporters/matrix/testcase-summary-band.js +75 -0
- package/dist/exporters/matrix/testcase-summary-band.js.map +1 -0
- package/dist/exporters/matrix/wording.d.ts.map +1 -1
- package/dist/exporters/matrix/wording.js +13 -0
- package/dist/exporters/matrix/wording.js.map +1 -1
- package/dist/exporters/matrix/xlsx-cells.d.ts +38 -0
- package/dist/exporters/matrix/xlsx-cells.d.ts.map +1 -0
- package/dist/exporters/matrix/xlsx-cells.js +52 -0
- package/dist/exporters/matrix/xlsx-cells.js.map +1 -0
- package/dist/exporters/xlsx-report-builder.d.ts +12 -1
- package/dist/exporters/xlsx-report-builder.d.ts.map +1 -1
- package/dist/exporters/xlsx-report-builder.js +10 -2
- package/dist/exporters/xlsx-report-builder.js.map +1 -1
- package/dist/generators/gherkin-parser/index.d.ts +7 -0
- package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
- package/dist/generators/gherkin-parser/index.js +3 -3
- package/dist/generators/gherkin-parser/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/appium/os-support.d.ts +53 -0
- package/dist/generators/test-generator/adapters/appium/os-support.d.ts.map +1 -0
- package/dist/generators/test-generator/adapters/appium/os-support.js +200 -0
- package/dist/generators/test-generator/adapters/appium/os-support.js.map +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/alert-fill-action.hbs +9 -2
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/capture-variable.hbs +24 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/hover-action.hbs +6 -2
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/hover-element-with-text.hbs +4 -3
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/unknown-element-action.hbs +9 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/have-value-assertion.hbs +34 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +14 -6
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/page-assertion.hbs +6 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/go-back-action.hbs +21 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/set-geolocation-action.hbs +16 -0
- package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +8 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row-nth.hbs +4 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +4 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/page-assertion.hbs +2 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +7 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +7 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-empty.hbs +4 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-match-data.hbs +4 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-count.hbs +4 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +12 -4
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +14 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +8 -8
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/id.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/testid.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +8 -8
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +6 -2
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +6 -2
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/diagnostics.d.ts +38 -2
- package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
- package/dist/generators/test-generator/diagnostics.js +37 -1
- package/dist/generators/test-generator/diagnostics.js.map +1 -1
- package/dist/generators/test-generator/patterns/index.d.ts +33 -6
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +71 -7
- package/dist/generators/test-generator/patterns/index.js.map +1 -1
- package/dist/generators/test-generator/patterns/types.d.ts +8 -0
- package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.d.ts +40 -1
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +104 -21
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts +24 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +82 -12
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/harness/audit.d.ts +3 -1
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +52 -29
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/capability-plan.d.ts +4 -0
- package/dist/harness/capability-plan.d.ts.map +1 -1
- package/dist/harness/capability-plan.js +32 -2
- package/dist/harness/capability-plan.js.map +1 -1
- package/dist/harness/catalog/drivers.yaml +1 -1
- package/dist/harness/catalog/universal-viewpoints.yaml +145 -0
- package/dist/harness/next-step.d.ts.map +1 -1
- package/dist/harness/next-step.js +1 -0
- package/dist/harness/next-step.js.map +1 -1
- package/dist/harness/parse.d.ts +6 -0
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +1 -1
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/repair.d.ts.map +1 -1
- package/dist/harness/repair.js +8 -1
- package/dist/harness/repair.js.map +1 -1
- package/dist/harness/sensors.d.ts +49 -0
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +51 -1
- package/dist/harness/sensors.js.map +1 -1
- package/dist/harness/viewpoint-ledger.d.ts +10 -0
- package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
- package/dist/harness/viewpoint-ledger.js +23 -2
- package/dist/harness/viewpoint-ledger.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/delivery.md +4 -3
- package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -5
- package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +48 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +39 -6
- package/dist/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +1 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-mobile-gestures/SKILL.md +16 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +11 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +14 -3
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +89 -3
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +58 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-test-design-techniques/SKILL.md +13 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +59 -10
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/group-d-display.md +1 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/group-f-mobile-native.md +419 -0
- package/dist/orchestrator/templates/readme.md +1 -1
- package/dist/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
- package/dist/orchestrator/templates/specs-url-assert.d.ts +31 -0
- package/dist/orchestrator/templates/specs-url-assert.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-url-assert.js +43 -0
- package/dist/orchestrator/templates/specs-url-assert.js.map +1 -1
- package/dist/orchestrator/templates/specs-url-assert.ts +54 -0
- package/dist/orchestrator/templates/wdio.conf.ts +105 -1
- package/package.json +3 -3
- package/src/capabilities/discover.ts +146 -10
- package/src/capabilities/registry.ts +30 -0
- package/src/cli/commands/audit.ts +9 -0
- package/src/cli/commands/capability.ts +1 -1
- package/src/dashboard/templates/index.html +1 -1
- package/src/exporters/feature-parser.ts +1 -1
- package/src/exporters/matrix/build.ts +13 -1
- package/src/exporters/matrix/item-labels.ts +78 -0
- package/src/exporters/matrix/render-coverage-sheet.ts +168 -0
- package/src/exporters/matrix/render-overview-sheet.ts +56 -0
- package/src/exporters/matrix/render-testcase-sheet.ts +178 -0
- package/src/exporters/matrix/render-xlsx.ts +67 -448
- package/src/exporters/matrix/testcase-summary-band.ts +79 -0
- package/src/exporters/matrix/wording.ts +13 -0
- package/src/exporters/matrix/xlsx-cells.ts +56 -0
- package/src/exporters/xlsx-report-builder.ts +16 -2
- package/src/generators/gherkin-parser/index.ts +1 -1
- package/src/generators/test-generator/adapters/appium/os-support.ts +176 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/alert-fill-action.hbs +9 -2
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/capture-variable.hbs +24 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/hover-action.hbs +6 -2
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/hover-element-with-text.hbs +4 -3
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/unknown-element-action.hbs +9 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/have-value-assertion.hbs +34 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +14 -6
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/page-assertion.hbs +6 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/go-back-action.hbs +21 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/set-geolocation-action.hbs +16 -0
- package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +8 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row-nth.hbs +4 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +4 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/page-assertion.hbs +2 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +7 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +7 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-empty.hbs +4 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-match-data.hbs +4 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-count.hbs +4 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +12 -4
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +14 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +8 -8
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/id.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/testid.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +8 -8
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +6 -2
- package/src/generators/test-generator/code-generator.ts +6 -2
- package/src/generators/test-generator/diagnostics.ts +39 -2
- package/src/generators/test-generator/patterns/index.ts +78 -8
- package/src/generators/test-generator/patterns/types.ts +8 -0
- package/src/generators/test-generator/step-mapper.ts +109 -19
- package/src/generators/test-generator/utils/selector-resolver.ts +98 -12
- package/src/harness/audit.ts +56 -31
- package/src/harness/capability-plan.ts +33 -1
- package/src/harness/catalog/drivers.yaml +1 -1
- package/src/harness/catalog/universal-viewpoints.yaml +145 -0
- package/src/harness/next-step.ts +1 -0
- package/src/harness/parse.ts +7 -1
- package/src/harness/repair.ts +8 -1
- package/src/harness/sensors.ts +100 -3
- package/src/harness/viewpoint-ledger.ts +26 -5
- package/src/orchestrator/templates/ai-src/commands/delivery.md +4 -3
- package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -5
- package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +48 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +39 -6
- package/src/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +1 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-mobile-gestures/SKILL.md +16 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +11 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +14 -3
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +89 -3
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +58 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-test-design-techniques/SKILL.md +13 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +59 -10
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/group-d-display.md +1 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/group-f-mobile-native.md +419 -0
- package/src/orchestrator/templates/readme.md +1 -1
- package/src/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
- package/src/orchestrator/templates/specs-url-assert.ts +54 -0
- package/src/orchestrator/templates/wdio.conf.ts +105 -1
|
@@ -63,7 +63,16 @@ instead; `--format both` writes both.
|
|
|
63
63
|
every unit it did not export with the reason, and exits 0 as long as it wrote something — a
|
|
64
64
|
scaffolded unit never blocks the ready ones.
|
|
65
65
|
|
|
66
|
-
**Workbook
|
|
66
|
+
**Workbook** (matrix mode): four sheets — `Overview` (live roll-up over the two below), `Auto`,
|
|
67
|
+
`Manual`, `Coverage`. Auto and Manual split **per variant**, so a **mixed item's parent row
|
|
68
|
+
appears on both**, each side carrying only its own variants and rolling up only what is on that
|
|
69
|
+
sheet — expect the same ID twice in the workbook, with two different numbers. Both detail sheets
|
|
70
|
+
always exist even when one is empty. Layout is BM-2-901-13: band on rows 6/7/8, `*: Mandatory`
|
|
71
|
+
legend on row 12, the 16 headers `A..P` on row 13, data from row 14, frozen at `A14`.
|
|
72
|
+
`Testcase type` (`K`) is variant-level (`Auto` · `Auto · Mocked` · `Manual (M6)`) and blank on a
|
|
73
|
+
parent row. Full detail: `docs/spec/delivery-coverage-matrix-spec.md` §8.
|
|
74
|
+
|
|
75
|
+
`Auto` / `Manual` — parent rows + outline-level-1 variant sub-rows for **every**
|
|
67
76
|
item (single-variant included: the sub-row carries the source VP-id, resolved data, and the
|
|
68
77
|
result/evidence entry). Sub-rows are **delta-only**: knowledge common to the whole item
|
|
69
78
|
(precondition, trigger) is written ONCE on the parent; a child repeats only what distinguishes it
|
|
@@ -80,9 +89,9 @@ never a concatenation of variant titles.
|
|
|
80
89
|
Variant Result cells hold exactly one of `Passed`/`Failed`/`Blocked`/`Pending`/`N/A` (dropdown) —
|
|
81
90
|
that vocabulary is the contract: the parent Result and the summary band are **live Excel formulas**
|
|
82
91
|
counting those words, so a parent label is always a composed `n/m Passed …` string (`All N/A` when
|
|
83
|
-
every variant is N/A) and never a bare state word. **`N/A` leaves the denominator.**
|
|
84
|
-
|
|
85
|
-
header rows.
|
|
92
|
+
every variant is N/A) and never a bare state word. **`N/A` leaves the denominator.** Note, Defect ID
|
|
93
|
+
and actual result share one column `P`; the header row is frozen; ISO dates; landscape print with
|
|
94
|
+
repeated header rows.
|
|
86
95
|
|
|
87
96
|
`Coverage` sheet — requirement coverage table (every FR/TR/NFR id with an explicit status),
|
|
88
97
|
target × category grid with explicit `—` gaps, dispositions, manifest. CSV mirrors the same model
|
|
@@ -201,7 +210,7 @@ The base sheet label comes from `base_locale:` (default **EN**). Undeclared env
|
|
|
201
210
|
|
|
202
211
|
## API delivery — extra worksheet
|
|
203
212
|
|
|
204
|
-
For **api-kind units** (`qa/api/<area>/`), the `.xlsx` gains a final worksheet **`API detail`** (appended after Overview/Auto/Manual). The main BM-2-901-13
|
|
213
|
+
For **api-kind units** (`qa/api/<area>/`), the `.xlsx` gains a final worksheet **`API detail`** (appended after Overview/Auto/Manual). The main BM-2-901-13 testcase-sheet layout is unchanged. The CSV is unchanged (16-column, no extra sheet).
|
|
205
214
|
|
|
206
215
|
### Required sources (API detail sheet only)
|
|
207
216
|
|
|
@@ -101,13 +101,15 @@ needs any of these, it is a **finding for QA** — surface it in the run summary
|
|
|
101
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) |
|
|
102
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
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 |
|
|
104
|
+
| `SG-W016` | A table step on a table located by CSS/testid whose value does not mention `table` and that declares no `rows:` — the table templates find rows by `getByRole('row')`; on a list built from `<div>`s that is 0 rows, so `is hidden`/`is empty` PASS without checking anything and counts/clicks time out (issue #688). A heuristic — the compiler cannot see the DOM — so it warns once per table | Declare the row shape on the TABLE entry (`rows: 'div.job-row'`, relative to the table) — it drives all seven table steps — or locate the table by `role: table` if it really is one. Do not put the shape on a row label's entry: that key positions ONE row |
|
|
104
105
|
| `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
106
|
| `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 |
|
|
106
|
-
| `SG-W020` | The matched pattern **declares `platforms`**
|
|
107
|
+
| `SG-W020` | The matched pattern **declares `platforms`** and the active platform isn't among them. Caught before template lookup, so the diagnostic names the alternative for the platform you are on. This is now the MAIN off-platform channel, not a corner case: the whole web-only set (`table-*`, `storage-*`, `route-assertion`, `tab-order`, `sorted`, `upload`, `alert-fill`) and the whole mobile-only set (gestures, device state, app lifecycle, device oracles) declare it, so the refusal arrives at compile time with a concrete substitute instead of at run time from inside a template. It still compiles to a `throw` — moving the message earlier never turns a refusal into a silent pass | Do what the message says: it carries the pattern's OWN `platformAlternative`, e.g. a table assertion on mobile → per-row accessibility-id assertions; `local storage` on mobile → assert the state via a UI element or `@api`/`@query`; a swipe on web → drive the control the swipe reveals. `@mock` on a mobile unit is the same shape (there is no Mock Driver on Appium) |
|
|
107
108
|
| `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 |
|
|
108
109
|
| `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
110
|
| `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
111
|
| `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`) |
|
|
112
|
+
| `SG-W026` | A mobile **active-navigation** step that emitted NO executable code, so a promised screen change never happens. `When User open [Settings] page` compiles to the one-line `navigation` template — a COMMENT noting the app was already launched by the wdio capabilities — so the step reads as "go to Settings" and goes nowhere; pair it with a `page` assertion (itself a no-op, `SG-W022`) and the scenario passes without ever touching the app. Scoped to the ACTIVE forms only: `Given User is on [X] page|screen` is the app-LAUNCH anchor and correctly emits nothing, so it is never reported. Deliberately NOT a `throw` — an unfulfilled navigation weakens the scenario rather than INVERTING an assertion, and throwing would break every mobile suite already carrying one | Change screens the way a user does — `When User tap [Settings] button` — then assert a marker on the new screen (`Then User see [Settings Header] text`). Keep `is on [X] screen` only as the feature's opening anchor. Note `generate --strict` fails on any diagnostic, so this code is blocking there from the day it lands |
|
|
111
113
|
| `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
114
|
| `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
115
|
| `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 |
|
|
@@ -202,6 +204,51 @@ If `toHaveText` fails on an input → the Gherkin step has the wrong target type
|
|
|
202
204
|
|
|
203
205
|
---
|
|
204
206
|
|
|
207
|
+
## Mobile Runtime Errors (Appium / WebdriverIO)
|
|
208
|
+
|
|
209
|
+
**The fixes are NOT written here.** They live once, as data, in `driver-mobile/src/mobile-repair.ts`,
|
|
210
|
+
and `sungen repair --screen <unit>` prints the matching one for the failure you actually got:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
sungen audit --screen <unit> # findings
|
|
214
|
+
npx playwright test # (or the wdio run) — leaves the result JSON
|
|
215
|
+
sungen repair --screen <unit> # → the concrete fix for each finding + each failure
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
This table is the INDEX into that catalog — symptom → rule id — so you can recognise a failure class
|
|
219
|
+
by eye and know a rule already covers it. Two copies of a fix drift; one copy plus an index does not.
|
|
220
|
+
`tests/ai-skills/error-mapping-repair-parity.run.ts` fails if this table and the catalog disagree in
|
|
221
|
+
either direction.
|
|
222
|
+
|
|
223
|
+
| What you see | Rule id | Class |
|
|
224
|
+
|---|---|---|
|
|
225
|
+
| `Element not found: ~key` / `no such element` | `mobile-element-absent` | selector |
|
|
226
|
+
| `iOS geometry oracle: rect={…0x0…}` | `mobile-ios-offscreen` | selector |
|
|
227
|
+
| `element click intercepted` / `is not clickable at point` | `mobile-tap-intercepted` | selector |
|
|
228
|
+
| `Default scrollable element '//android.widget.ScrollView' not found` | `mobile-scroll-no-scrollable` | selector |
|
|
229
|
+
| `A new session could not be created` | `mobile-session-not-created` | session |
|
|
230
|
+
| `Could not proxy command to the remote server` / `socket hang up` | `mobile-driver-proxy-lost` | session |
|
|
231
|
+
| `The instrumentation process cannot be initialized` | `mobile-instrumentation-failed` | session |
|
|
232
|
+
| `No Chromedriver found that can automate Chrome '…'` | `mobile-chromedriver-mismatch` | session |
|
|
233
|
+
| `@auth:<role> login did not complete` / `could not decide the login state` | `mobile-auth-session` | session |
|
|
234
|
+
| `device log (logcat) did not contain "…"` | `mobile-device-log-silent` | oracle |
|
|
235
|
+
| `[sungen] … web-only` / `not supported on the appium adapter` | `mobile-web-only-step` | compile (SG-W020/E020) |
|
|
236
|
+
| `[sungen] step "…" matched no pattern` | `mobile-step-unmatched` | compile (SG-W001) |
|
|
237
|
+
| `… needs the app id: the mobile lifecycle steps resolve …` | `mobile-lifecycle-no-app-id` | compile (SG-E024) |
|
|
238
|
+
| `… This feature declares @platform:ios` | `mobile-ios-unsupported-step` | compile (SG-E025) |
|
|
239
|
+
| `UNIVERSAL: missing theme(s): …` | `mobile-universal-theme` | audit |
|
|
240
|
+
| `GATE: critical theme "…" has NO covering scenario` | `mobile-gate-theme-uncovered` | audit |
|
|
241
|
+
| `GATE: critical theme "…" covered only by SHALLOW` | `mobile-gate-theme-shallow` | audit |
|
|
242
|
+
| `VIEWPOINT-GESTURE-SUBSTITUTED: …` | `mobile-gesture-substituted` | audit |
|
|
243
|
+
| `MANUAL-CODE-MISSING: …` | `mobile-manual-uncoded` | audit |
|
|
244
|
+
| `PAGE-TYPE-UNDETERMINED: …` | `mobile-page-type-undetermined` | audit |
|
|
245
|
+
|
|
246
|
+
A failure with no rule id is reported by `sungen repair` under **"No known rule (review manually)"**.
|
|
247
|
+
That list is deliberately not empty-by-construction: a rule broad enough to match everything would
|
|
248
|
+
hide the findings nobody has looked at yet.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
205
252
|
## Performance & Infrastructure Errors → Fix in `specs/base.ts`
|
|
206
253
|
|
|
207
254
|
All generated `.spec.ts` import from `specs/base.ts` — shared context caching, navigation, overlay cleanup. AI **can and should** tune `base.ts` to match the project.
|
|
@@ -35,7 +35,7 @@ AND → inherits from preceding keyword
|
|
|
35
35
|
### Setup / Form / Interaction
|
|
36
36
|
|
|
37
37
|
```
|
|
38
|
-
User is on [T] page | page with {{v}} | dialog
|
|
38
|
+
User is on [T] page | page with {{v}} | dialog # {{v}} = query/fragment ("?id=200", "#tab") APPENDED to the page value — a bare "200" onto "/x?id=199" gives "?id=199200"
|
|
39
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
|
|
@@ -103,6 +103,9 @@ two content-filtered queries can hit different rows if the table re-renders in b
|
|
|
103
103
|
```
|
|
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
|
+
# `label` forks on the entry: declared by IDENTITY (xpath/accessibility-id/testid/id/css)
|
|
107
|
+
# → asserts THAT element contains {{v}} (add `exact: true` for a strict match); no entry,
|
|
108
|
+
# or `type: text` → matches the caption+value text pair anywhere on screen
|
|
106
109
|
# 3. Partial (toContainText): User see [T] text contains {{v}}
|
|
107
110
|
# 3b. Format (shape, not value): User see [T] label matches "yyyy/mm/dd ~ yyyy/mm/dd"
|
|
108
111
|
# 4. Input (toHaveValue): User see [T] field | date-picker | search | slider with {{v}}
|
|
@@ -121,10 +124,28 @@ two content-filtered queries can hit different rows if the table re-renders in b
|
|
|
121
124
|
> assert the page you expect (`Then User see [Other] page`); to say "this went away", assert a marker
|
|
122
125
|
> element (`Then User see [Some Element] is hidden`).
|
|
123
126
|
|
|
127
|
+
**Pattern 8 is a URL oracle, and a URL is not a rendered page.** `see [T] page` proves the router
|
|
128
|
+
pushed a path — it does NOT prove the page rendered for this user. A client-side router pushes the
|
|
129
|
+
destination BEFORE the route guard or the permission API has answered, so a user with no permission
|
|
130
|
+
is "on" `/items/detail` for a few hundred milliseconds before being bounced to the 403 page. Since
|
|
131
|
+
3.2.28 the step compiles to `expectPage(page, {…})` — arrive, let the page settle, HOLD the URL, and
|
|
132
|
+
re-assert — so a late redirect fails and names where the user ended up. But the page having
|
|
133
|
+
rendered is still unproven: **for permission / auth-guard / "role X can open screen Y" scenarios,
|
|
134
|
+
give the page selector an `anchor:`** (a heading or landmark only the rendered page has) — every
|
|
135
|
+
`see [T] page` on that screen then asserts the anchor too — or follow the page step with a content
|
|
136
|
+
assertion. A scenario whose every `Then` is a page assertion is reported as `ORACLE-URL-ONLY`.
|
|
137
|
+
|
|
138
|
+
```yaml
|
|
139
|
+
detail:
|
|
140
|
+
type: 'page'
|
|
141
|
+
value: '/items/detail'
|
|
142
|
+
anchor: { type: 'role', value: 'heading', name: 'Item Detail' } # the rendered-content proof
|
|
143
|
+
```
|
|
144
|
+
|
|
124
145
|
**Pattern 8 — the page assertion judges pathname AND query together.** Both `see [T] page` and
|
|
125
|
-
`is on [T] page` (Then-side) compile to ONE web-first
|
|
126
|
-
|
|
127
|
-
normalised) plus the query:
|
|
146
|
+
`is on [T] page` (Then-side) compile to ONE web-first check — `expectPage(page, {…})`, built on
|
|
147
|
+
`toHaveURL(urlMatches({…}))` and retrying like any other assertion — comparing the **exact** pathname
|
|
148
|
+
(anchored, trailing slash normalised) plus the query:
|
|
128
149
|
|
|
129
150
|
| step | asserts |
|
|
130
151
|
|---|---|
|
|
@@ -135,6 +156,8 @@ normalised) plus the query:
|
|
|
135
156
|
| `… page with {{v}}`, `v` = `/vi/search` (no query) | **path only**, and the compiler warns (`SG-W014`): the step asserts less than it reads as |
|
|
136
157
|
| `… page with {{v}}`, `v` empty or key missing at run time | **throws, naming the key** — an unusable value is never a silent pass |
|
|
137
158
|
|
|
159
|
+
**Navigating** with data (`Given User is on [T] page with {{v}}`) appends `{{v}}` to the page selector's value **verbatim** — no merging, by design (the primary use is injecting malformed query input, which the compiler must not sanitize). So the page value carries the PATH only and `{{v}}` carries the whole query: `value: '/job/detail'` + `job_query: "?id=200"`. A page value that already holds `?id=199` plus `{{v}}` = `"200"` navigates to `?id=199200`. A rooted `{{v}}` (`/other/path`) replaces the page's path; an absolute URL replaces everything.
|
|
160
|
+
|
|
138
161
|
So pass `with {{v}}` whenever the URL carries state worth proving, and leave it off when the page
|
|
139
162
|
must be reached clean. A dynamic segment in the selector value (`/users/:id/profile`) wildcards to
|
|
140
163
|
`[^/]+` on its own, still anchored — `/users/42/profile/edit` does not match. Params compare
|
|
@@ -196,6 +219,15 @@ first contact row:
|
|
|
196
219
|
```
|
|
197
220
|
→ compiles to `expect(table.locator('tbody tr:first-child')).toContainText(v)` — the exact row must hold the value — and still enters row scope for `[Col] column` checks.
|
|
198
221
|
|
|
222
|
+
**A list built from `<div>`s is a table only if you tell sungen what a row is.** Every table step finds rows by `getByRole('row')`; a `<div>` list has none, so `is hidden` and `is empty` would pass on 0 rows (asserting nothing) and counts/clicks would time out. Declare the row SHAPE on the **table** entry — `rows:` is relative to the table and drives all seven table steps:
|
|
223
|
+
```yaml
|
|
224
|
+
job table:
|
|
225
|
+
type: locator
|
|
226
|
+
value: 'div.job-list'
|
|
227
|
+
rows: 'div.job-row' # what EVERY row matches — NOT a row label's entry
|
|
228
|
+
```
|
|
229
|
+
Two different keys, two meanings: `rows:` on the table = every row (the shape); a `type: locator` entry on a **row label** = ONE positioned row (`tbody tr:first-child`). A shape put on a row label fails at run time with a `toHaveCount(1)` message that points back here. A CSS-located table with no `rows:` gets `SG-W016`. On a shaped table `table row {{n}}` counts from the first data row (no header offset).
|
|
230
|
+
|
|
199
231
|
### Browser storage `[web]`
|
|
200
232
|
|
|
201
233
|
```
|
|
@@ -356,7 +388,7 @@ entirely (a *pattern* gap, not a template gap), which is what the catalogue belo
|
|
|
356
388
|
|
|
357
389
|
**Mobile-only `[mobile]`** — the gesture catalog (swipe, long-press, pinch-zoom, pull-to-refresh,
|
|
358
390
|
rotate, background/foreground, notifications, grant-permission, clipboard set, set-geolocation,
|
|
359
|
-
hide-keyboard, tap-top-of) has no web counterpart, and neither do the lifecycle / device-state /
|
|
391
|
+
hide-keyboard, tap-top-of, `go back`) has no web counterpart, and neither do the lifecycle / device-state /
|
|
360
392
|
timing steps (`restart the app`, `reinstall the app`, `turn airplane mode|wifi|mobile data on|off`,
|
|
361
393
|
`turn device location on|off` — the latter two Android-only, throw on iOS — the window
|
|
362
394
|
assertions `see [X] disappear within N seconds` / `still visible after N seconds`, and the
|
|
@@ -369,8 +401,9 @@ Full syntax → `sungen-mobile-gestures`.
|
|
|
369
401
|
| Step | Web | Mobile |
|
|
370
402
|
|---|---|---|
|
|
371
403
|
| `see [T] page` \| `page with {{v}}` | asserts path+query | **silent no-op** — asserts nothing; the scenario passes regardless. Assert a screen-marker element instead |
|
|
372
|
-
| `is on [T] page` \| `open [T] page` (Given/When) | navigates via URL | no-op —
|
|
404
|
+
| `is on [T] page` \| `open [T] page` (Given/When) | navigates via URL | `is on` = the app-launch anchor, correctly a no-op. `open`/`navigate to` = a no-op that **reports `SG-W026`** — it promises a screen change and performs none; use tap/gesture steps instead |
|
|
373
405
|
| `wait for [T] page` | waits for the URL | fixed `driver.pause(500)` settle — not a real wait condition |
|
|
406
|
+
| `go back` (OS/hardware back) | **not a web step** — reports `SG-W020`; re-enter with `is on [X] page` or tag `@manual` | Android: `KEYCODE_BACK`, the real hardware key. iOS: WDA **navigation**-back, *not* the user's edge swipe-back (iOS has no hardware back). Distinct from `tap [Back] button`: system back skips the screen's own handler unless the app wires it |
|
|
374
407
|
| `hover [T] icon \| row` | real hover | no-op — hover-revealed content is normally already visible on mobile; use `tap` |
|
|
375
408
|
| `fill [T] alert with {{v}}` | fills native `prompt()` | no-op (comment only) — app-specific, handle manually |
|
|
376
409
|
| `switch to [T] frame` | enters an `<iframe>` | switches a hybrid app's WebView context; no-op on a pure-native screen. **Needs `APPIUM_CHROMEDRIVER_AUTODOWNLOAD=1`** (.env.appium) or the switch fails with "No Chromedriver found". INSIDE the webview, selectors are **css/xpath only** — UiSelector-based types and the text-value asserts (`label with {{v}}`) don't apply there; assert element presence via css/xpath refs, then `switch to [main] frame` back |
|
|
@@ -34,6 +34,7 @@ user-invocable: false
|
|
|
34
34
|
| **DEPTH** | business-critical scenarios assert only visibility/navigation | Replace `Then User see [X] page/section` with **observable data assertions**: `Then User see [X] with {{value}}`, `Then User see [T] table match data:`. Capture real expected values into `test-data.yaml`. |
|
|
35
35
|
| **BALANCE** | secondary viewpoints (UI/validation/security) outweigh business-core | **Stop expanding** secondary viewpoints; generate the missing business-core scenarios first. Do not add more subscription/UI variants while core is thin. |
|
|
36
36
|
| **TRACE** | scenarios use ad-hoc `VP-<CAT>-NNN` codes not linked to the viewpoint-overview | Make each scenario map to a viewpoint-overview id (align category codes, or add a mapping comment). |
|
|
37
|
+
| **ORACLE-URL-ONLY** | every `Then` in the scenario is a page/URL assertion — it proves a path was pushed, not that the page rendered; on a permission/guard scenario a late 403 redirect turns it falsely green | Add `anchor:` to the page selector (one line per screen; every `see [X] page` on it becomes a content oracle) or a content assertion after the page step. Never delete the page step to silence it. |
|
|
37
38
|
| **UNIVERSAL** | a universal theme (error/empty-state, accessibility) is absent | Low priority — add if in scope; otherwise note as out-of-scope with reason. |
|
|
38
39
|
|
|
39
40
|
## P5 steps for deep cross-screen / list coverage
|
|
@@ -17,6 +17,21 @@ patterns (`click`, `hover`, `fill`) don't cover.
|
|
|
17
17
|
> - **`swipe <dir> on [X]`** → `mobile: swipeGesture`.
|
|
18
18
|
> - **`long-press [X] [for N seconds]`** → `mobile: longClickGesture`.
|
|
19
19
|
> - **`rotate to landscape|portrait`** → `driver.setOrientation(...)`.
|
|
20
|
+
> - **`go back`** (also `goes back` · `press the back button` · `hardware|system|device back` ·
|
|
21
|
+
> `back gesture` · `swipe back`) → Android `mobile: pressKey` with keycode 4 (KEYCODE_BACK — the
|
|
22
|
+
> real hardware key); iOS `driver.back()`, which is WebDriverAgent's **navigation** back and *not*
|
|
23
|
+
> the user's edge swipe-back gesture — iOS has no hardware back key. **This is NOT `tap [Back]`:**
|
|
24
|
+
> Android's system back does not run the screen's own handler unless the app wires it
|
|
25
|
+
> (`onBackPressed` / `BackHandler`), so a defect on the OS path is invisible to an in-app tap —
|
|
26
|
+
> which is why the viewpoint ledger refuses the in-app button as an answer to its "OS/hardware
|
|
27
|
+
> back gesture" theme. The step takes **no `[Ref]`**; that absence is what selects it. A phrasing
|
|
28
|
+
> that names a target (`go back to [List] screen`) is refused rather than reinterpreted, so pick
|
|
29
|
+
> one form: `go back`, or a tap.
|
|
30
|
+
> **`swipe back` is only a spelling of this step, not a swipe.** It compiles to the same
|
|
31
|
+
> `KEYCODE_BACK` / `driver.back()` as every other phrasing above, so on iOS — where the edge
|
|
32
|
+
> swipe-back *is* what a real user does — a scenario named for the gesture never performs it. If
|
|
33
|
+
> the edge swipe itself is under test, that needs a left-edge `mobile: swipe`, which no step
|
|
34
|
+
> currently emits: tag it `@manual` rather than letting the wording imply coverage it does not have.
|
|
20
35
|
> - **`pull-to-refresh on [X]`** → fast `mobile: swipeGesture` (direction down).
|
|
21
36
|
> - **`pinch-zoom in|out on [X]`** → `mobile: pinchOpenGesture` / `pinchCloseGesture`.
|
|
22
37
|
> - **`send app to background for N seconds`** → `driver.background(N)`.
|
|
@@ -108,7 +123,7 @@ from `appium_find_element`; screen gestures pass `direction` or coordinates.
|
|
|
108
123
|
| Scroll a list | `User scroll down on [Feed]` | `action=scroll, direction=down` |
|
|
109
124
|
| Scroll until visible | `User scroll to [Footer]` | `action=scroll_to_element, strategy, selector, direction` |
|
|
110
125
|
| Pinch zoom in/out | `User pinch-zoom in on [Map]` | `action=pinch_zoom, elementUUID, scale` (>1 in, <1 out) |
|
|
111
|
-
| System back | `User go back` | `action=back` |
|
|
126
|
+
| System back | `User go back` | `action=back` — **compiles** (see codegen status; per-OS) |
|
|
112
127
|
| Drag & drop | `User drag [A] onto [B]` | `appium_drag_and_drop` (separate tool) |
|
|
113
128
|
|
|
114
129
|
Other device-level actions (separate MCP tools for exploration; Gherkin now compiles — see codegen
|
|
@@ -428,6 +428,17 @@ users:
|
|
|
428
428
|
|
|
429
429
|
**How to build `columns`**: count column headers in snapshot (left to right, 0-indexed). Map each `[Col] column` reference from feature file to its index.
|
|
430
430
|
|
|
431
|
+
**A list built from `<div>`s (no `<table>`, no `role="row"` in the snapshot)** — the table steps still work, but you must declare the row SHAPE on the table entry, or every row step resolves 0 rows (`is hidden`/`is empty` pass vacuously — `SG-W016` warns about exactly this):
|
|
432
|
+
|
|
433
|
+
```yaml
|
|
434
|
+
job list:
|
|
435
|
+
type: 'locator'
|
|
436
|
+
value: 'div.job-list'
|
|
437
|
+
rows: 'div.job-row' # RELATIVE to the table: what every row matches
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
Pick `rows` from the snapshot as the repeating child that holds one record (a class like `.job-row`, `[data-testid="job-item"]`, or `> div`). Do **not** put it on a row label's entry — that key positions ONE row (`div.job-row:first-child`) and strict-mode-fails when it matches many.
|
|
441
|
+
|
|
431
442
|
---
|
|
432
443
|
|
|
433
444
|
## Detail Screens with Dynamic IDs
|
|
@@ -63,6 +63,10 @@ login:
|
|
|
63
63
|
awards:
|
|
64
64
|
type: 'page'
|
|
65
65
|
value: '/awards'
|
|
66
|
+
anchor: # optional — the element that proves the page RENDERED (#662).
|
|
67
|
+
type: 'role' # `see [Awards] page` asserts the URL, then this; without it the step is
|
|
68
|
+
value: 'heading' # a URL-only oracle a late 403 redirect can satisfy. One line per screen.
|
|
69
|
+
name: 'Awards'
|
|
66
70
|
|
|
67
71
|
"awards:submit":
|
|
68
72
|
type: 'role'
|
|
@@ -221,7 +225,7 @@ If no YAML key exists, the resolver infers from the Gherkin element type:
|
|
|
221
225
|
| `[X] text` / `message` / `label` | `getByText('X')` | Visible text = X (partial match) |
|
|
222
226
|
| `[X] logo/image/icon` | `getByRole('img', { name: 'X' })` | Image alt = X |
|
|
223
227
|
| `[X] search` | `getByRole('searchbox', { name: 'X' })` | Searchbox label = X |
|
|
224
|
-
| `[X] option` | `getByRole('option'
|
|
228
|
+
| `[X] option` | `getByRole('option')` — **no name** | web only (see the list-member note below) |
|
|
225
229
|
| `[X] slider` | `getByRole('slider', { name: 'X' })` | Slider label = X |
|
|
226
230
|
| `[X] toggle` | `getByRole('switch', { name: 'X' })` | Toggle label = X |
|
|
227
231
|
| `[X] tab` | `getByRole('tab', { name: 'X' })` | Tab text = X |
|
|
@@ -234,12 +238,19 @@ If no YAML key exists, the resolver infers from the Gherkin element type:
|
|
|
234
238
|
| `[X] progressbar` | `getByRole('progressbar', { name: 'X' })` | Progressbar label = X |
|
|
235
239
|
| `[X] section` | `getByRole('region', { name: 'X' })` | Section aria-label = X |
|
|
236
240
|
| `[X] card` | `getByRole('article', { name: 'X' })` | Card aria-label = X |
|
|
237
|
-
| `[X] item` | `getByRole('listitem'
|
|
238
|
-
| `[X] cell` | `getByRole('cell'
|
|
241
|
+
| `[X] item` | `getByRole('listitem')` — **no name** | web only (see the list-member note below) |
|
|
242
|
+
| `[X] cell` | `getByRole('cell')` — **no name** | web only (see the list-member note below) |
|
|
239
243
|
| `[X] spinner` | `getByRole('status', { name: 'X' })` | Spinner aria-label = X |
|
|
240
244
|
| `[X] breadcrumb` | `getByRole('navigation', { name: 'X' })` | Navigation aria-label = X |
|
|
241
245
|
| `[X] badge` / `tooltip` / `tag` | `getByText('X')` | Visible text = X |
|
|
242
246
|
|
|
247
|
+
**Special note on the list members `[X] item` / `[X] option` / `[X] cell`** — the only rows above that behave differently per platform:
|
|
248
|
+
|
|
249
|
+
- **Web**: they infer the role with **no `name`**, on purpose. A list member takes its accessible name from its own CONTENT, so a name defaulted from the `[Reference]` text matches zero elements — and `toBeHidden()` PASSES on an empty locator, so the scenario would go green having proved nothing. The role alone is the whole locator; `nth` or a count assertion narrows it. An explicit `name:` you write yourself is always honoured.
|
|
250
|
+
- **Mobile**: they do **not** auto-infer at all. A native a11y tree has no role concept, so with no name there is nothing to build an accessibility-id from — the old behaviour emitted the role token itself (`~listitem`), which no tree ever contains. `sungen generate` now fails naming the key to add, and selector generation (Phase 0) scans the live page for it instead of skipping. **Write an explicit entry**, e.g. `job list--item: { type: accessibility-id, value: 'job-row' }`.
|
|
251
|
+
|
|
252
|
+
**On mobile, `type: role` needs a `name:`.** The rule above is not about these three types — it is about a role with **no accessible name**, whichever way it got that way. On mobile such a role compiles to the role token (`~status`) and can never match, so it is refused at compile time. That includes `name: null`, which is the web opt-out for a content-named role: it has no mobile meaning, and `SG-W003` (which recommends it) does not fire on mobile for that reason. On a native app, prefer `type: accessibility-id`, `type: xpath` or `type: id` over `type: role` — the device exposes content-desc / accessibilityIdentifier, not ARIA roles.
|
|
253
|
+
|
|
243
254
|
**Special note on `[X] field`**: `getByPlaceholder('X')` only works when (1) the field has a placeholder attribute AND (2) the placeholder text equals X. For fields without placeholders (floating labels, aria-label), write explicit YAML: `type: label, value: "Actual label text"`.
|
|
244
255
|
|
|
245
256
|
**Only add a YAML entry when** auto-infer cannot work: DOM name differs from Gherkin label, need `testid`, need `nth`, need `exact: true`, need `name: null`, or the field type requires explicit config.
|
|
@@ -309,7 +309,7 @@ Security: [S1 – admin only]
|
|
|
309
309
|
**Depth is a GATE dimension (harness-roadmap P1) — self-raise, never silently go shallow:**
|
|
310
310
|
- For every data-correctness theme the catalog marks `depth.requires: data-assertion`, emit its `depth.template` shape by **default** — don't wait for the repair loop. `sungen audit` measures `businessDepth` (ratio of these scenarios that assert data) against an intent threshold (functional ≥ 0.70); below it the **gate FAILs**.
|
|
311
311
|
- **Verify depth deterministically before the gate:** run `sungen depth-lint --screen <name>`. It classifies every shallow business-critical scenario into **deepen-in-place** (add the theme's value assertion — the printed `template` is a hint, fit it to the actual claim) vs **cross-screen** (route to a flow / `@manual:Mx`). Clear the `deepen` list first — this is the mechanical way to hit `businessDepth` on the first pass instead of churning repair rounds. Never fake a value assertion onto a visibility/behavior scenario the lint over-counts; leave it and note the over-count.
|
|
312
|
-
- `depth.cross_screen: true` (cart / detail / filter / brand correctness) → write the deep capture/compare shape as an **automated flow scenario** (in the flow — do NOT leave a full-step `@manual` duplicate on the screen). `@manual` is **only** for genuine judgment (M6 visual/UX · M8 not-worth · M9 human) or a missing capability (M1–M5/M7), and it **must** carry a reason code (`@manual:Mx`, or a reason comment the planner can infer). A `@manual` scenario that still has full automatable steps (a data assertion, no visual/mock/a11y judgment) is now flagged by `sungen audit` as `MANUAL-AUTOMATABLE`, and business-critical scenarios you defer to `@manual` are reported as `DEPTH-DEFERRED` (they do NOT silently inflate `businessDepth`). Deferring automatable work to `@manual` lowers quality — automate it in the flow instead.
|
|
312
|
+
- `depth.cross_screen: true` (cart / detail / filter / brand correctness) → write the deep capture/compare shape as an **automated flow scenario** (in the flow — do NOT leave a full-step `@manual` duplicate on the screen). `@manual` is **only** for genuine judgment (M6 visual/UX · M8 not-worth · M9 human), a device/OS limit on mobile (M10 OS capability · M11 real-device only), or a missing capability (M1–M5/M7), and it **must** carry a reason code (`@manual:Mx`, or a reason comment the planner can infer). A `@manual` scenario that still has full automatable steps (a data assertion, no visual/mock/a11y judgment) is now flagged by `sungen audit` as `MANUAL-AUTOMATABLE`, and business-critical scenarios you defer to `@manual` are reported as `DEPTH-DEFERRED` (they do NOT silently inflate `businessDepth`). Deferring automatable work to `@manual` lowers quality — automate it in the flow instead.
|
|
313
313
|
- **Pick the right `@manual:Mx` code — it decides which driver can later automate the case** (`sungen audit` flags a code↔reason mismatch). Tag the code that matches the **oracle the reason describes**:
|
|
314
314
|
|
|
315
315
|
| The reason needs… | Code | Unblocked by |
|
|
@@ -321,9 +321,11 @@ Security: [S1 – admin only]
|
|
|
321
321
|
| an external dependency (email, payment gateway, download) | `M5` | mail-file / contract |
|
|
322
322
|
| visual / UX / responsive / a11y judgment | `M6` | — (keep manual) |
|
|
323
323
|
| not worth automating · true human judgment | `M8` / `M9` | — (keep manual) |
|
|
324
|
+
| **mobile** — what the OS/driver will not expose (notification shade, app switcher/recents, permission variance by OS version, call interruption) | `M10` | — (keep manual) |
|
|
325
|
+
| **mobile** — what an emulator/simulator cannot be (biometrics, real camera, NFC, a real push token) | `M11` | — (keep manual) |
|
|
324
326
|
|
|
325
327
|
e.g. "submit a payload then check the subscribers **table**" is an API+DB oracle → `@manual:M2` (NOT `M1`); "seed a DB with zero products" is a data state → `M1`; "throttle the network" → `M3`.
|
|
326
|
-
- **Prefer automation-ready `@requires:<cap>` over prose `@manual`.** When you *can* write the steps for a capability-manual case (an API/DB oracle, a seeded state), write it **automation-ready** — the real `@api`/`@query`/… steps tagged `@requires:<cap>` (e.g. `@requires:db @query:subscriber_row`) — instead of a prose `@manual:M2`. It compiles to a skipped-with-reason stub until `sungen capability add <cap>`, then runs as a real test with **no rewrite**. Reserve prose `@manual:Mx` for cases whose steps genuinely can't be expressed (M6/M8/M9 judgment, or a capability with no driver). `sungen audit` reports these as `AUTOMATION-READY-PENDING` (not a gap, not manual).
|
|
328
|
+
- **Prefer automation-ready `@requires:<cap>` over prose `@manual`.** When you *can* write the steps for a capability-manual case (an API/DB oracle, a seeded state), write it **automation-ready** — the real `@api`/`@query`/… steps tagged `@requires:<cap>` (e.g. `@requires:db @query:subscriber_row`) — instead of a prose `@manual:M2`. It compiles to a skipped-with-reason stub until `sungen capability add <cap>`, then runs as a real test with **no rewrite**. Reserve prose `@manual:Mx` for cases whose steps genuinely can't be expressed (M6/M8/M9 judgment, M10/M11 device-or-OS limits on mobile, or a capability with no driver). `sungen audit` reports these as `AUTOMATION-READY-PENDING` (not a gap, not manual).
|
|
327
329
|
- **If the spec lacks the concrete value** a deep assertion needs (exact message, price, count): still write the deep shape with a `{{var}}` placeholder and leave a `# SPEC-GAP: <field> value not in spec` comment — do **not** downgrade to `see [X] section`. A visible gap is better than a silent shallow pass.
|
|
328
330
|
- **Blind-Spot Memory:** before finishing, run `sungen blindspot list --prompt` (Bash) and make sure the suite satisfies each recorded pattern (e.g. "for any Add/Create action: check success + resulting data state + duplicate/double-submit"). These are gaps QA hit before — don't repeat them.
|
|
329
331
|
|
|
@@ -375,7 +377,7 @@ Security: [S1 – admin only]
|
|
|
375
377
|
| Validation rule | 1 exact-message TC per rule | `@high` |
|
|
376
378
|
| Business rule | 1 behavioral TC per rule | `@high` |
|
|
377
379
|
| **Secondary behavior / tiebreaker** | **1 TC per tiebreaker or fallback rule in `Secondary behaviors`** | **`@high`** |
|
|
378
|
-
| Auth / OAuth / permissions | 1 VP-SEC TC | `@high` |
|
|
380
|
+
| Auth / OAuth / permissions | 1 VP-SEC TC — **with a rendered-content oracle**: `see [X] page` alone is a URL check a late 403 redirect passes; give the page selector an `anchor:` or add a content assertion (`ORACLE-URL-ONLY`) | `@high` |
|
|
379
381
|
| Free-text input | 1 XSS TC **and** 1 SQL injection TC (separate) | `@high` |
|
|
380
382
|
| **Free-text LIKE / partial-match field** | **1 field-level SQL TC + 1 API-level SQL `@manual` TC** | **`@high`** |
|
|
381
383
|
| Lifecycle states | 1 key state transition TC | `@high` |
|
|
@@ -478,6 +480,90 @@ And User wait for [Page Title] heading is visible
|
|
|
478
480
|
|
|
479
481
|
---
|
|
480
482
|
|
|
483
|
+
## Mobile — the two axes web has no concept of
|
|
484
|
+
|
|
485
|
+
Applies to a `@platform:mobile/android/ios` unit. Read `sungen-viewpoint` →
|
|
486
|
+
`group-f-mobile-native.md` for *what to check* per native pattern (it carries the theme↔catalog
|
|
487
|
+
map the audit scores against); this section is the two **cross-cutting** axes that multiply every
|
|
488
|
+
other scenario on a device, and that a web-shaped coverage map has no row for.
|
|
489
|
+
|
|
490
|
+
### Axis 1 — the app lifecycle state machine
|
|
491
|
+
|
|
492
|
+
A browser tab is either open or closed. An app has five states, and a screen can be wrong in any of
|
|
493
|
+
them. Generate lifecycle scenarios from the transitions, not from the states.
|
|
494
|
+
|
|
495
|
+
```
|
|
496
|
+
launch (cold)
|
|
497
|
+
│
|
|
498
|
+
▼
|
|
499
|
+
┌──────────────► FOREGROUND ──────────────┐
|
|
500
|
+
│ │ │
|
|
501
|
+
│ background N seconds restart (kill)
|
|
502
|
+
│ ▼ ▼
|
|
503
|
+
│ BACKGROUNDED RELAUNCHED ── reinstall ──► FIRST-RUN
|
|
504
|
+
│ │ (install state cleared)
|
|
505
|
+
└──── resume ─────────┤
|
|
506
|
+
▼
|
|
507
|
+
PROCESS DEATH (the OS reclaims a backgrounded app)
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
| Transition | Step | Availability |
|
|
511
|
+
|---|---|---|
|
|
512
|
+
| foreground → backgrounded → resumed | `User background the app for 5 seconds` | both OSes |
|
|
513
|
+
| foreground → killed → relaunched | `User restart the app` | both OSes — **the** persistence oracle |
|
|
514
|
+
| → first-run | `User reinstall the app` | both OSes; needs the session to have installed via `APP_APK` / `IOS_APP` |
|
|
515
|
+
| a clean state *between* scenarios | `@reset:none\|soft\|data\|full` on the Feature | a tag, never a step — a step that tries reports **SG-W023** |
|
|
516
|
+
| backgrounded → process death → resumed | *no step* | `@manual:M10` (OS capability) |
|
|
517
|
+
|
|
518
|
+
**Every lifecycle scenario needs a DATA oracle.** "The screen is still there" passes on an empty
|
|
519
|
+
shell and on a silently re-logged-in session. Assert the value that must have survived:
|
|
520
|
+
|
|
521
|
+
```gherkin
|
|
522
|
+
@high @platform:mobile
|
|
523
|
+
Scenario: VP-LOGIC-001 The session survives a cold restart
|
|
524
|
+
Given User is on [Account] screen
|
|
525
|
+
When User restart the app
|
|
526
|
+
Then User see [Account Name] text contains {{user_name}}
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
Minimum for any mobile unit: **one resume scenario** (the screen owes its own data back) and, when
|
|
530
|
+
the screen persists anything — a session, a setting, a dismissal, a show-once card — **one restart
|
|
531
|
+
scenario per persisted thing**. Rotation belongs here too: `User rotate to landscape` must not lose
|
|
532
|
+
entered values or fire the request twice.
|
|
533
|
+
|
|
534
|
+
### Axis 2 — permission × network decision table
|
|
535
|
+
|
|
536
|
+
These two multiply: a permission-gated feature behaves differently offline, and the honest expected
|
|
537
|
+
result is often "which message wins". Build the table, then **test only the rows with distinct
|
|
538
|
+
outcomes** — the usual decision-table discipline, not all 12 combinations.
|
|
539
|
+
|
|
540
|
+
| # | Permission | Network | Expected | How |
|
|
541
|
+
|---|---|---|---|---|
|
|
542
|
+
| 1 | granted | online | the happy path, feature content asserted | `User grant camera permission` — both OSes (iOS Simulator needs `applesimutils`) |
|
|
543
|
+
| 2 | granted | offline | the spec's cached/queued behaviour + a clear message; entered data never silently lost | `User turn wifi off` → `@platform:android` (XCUITest cannot toggle connectivity); iOS is `@manual:M10` |
|
|
544
|
+
| 3 | granted | off → on | recovers and refreshes with no manual reload | `@platform:android` |
|
|
545
|
+
| 4 | denied | online | an in-app explanation with a route to Settings — never a crash or a dead button | `@manual:M10` — no step denies a permission |
|
|
546
|
+
| 5 | denied | offline | one message wins per spec; do NOT generate this row unless the spec says which | `@manual:M10` |
|
|
547
|
+
| 6 | never-ask-again | online | no request loop; the Settings route is shown | `@manual:M10` |
|
|
548
|
+
| 7 | revoked while running | online | the app re-checks instead of trusting a stale grant | `@manual:M10` |
|
|
549
|
+
|
|
550
|
+
Row 1 is `@high` and automated. Rows 2–3 are `@high` and automated **on Android only** — tag them,
|
|
551
|
+
do not write them platform-neutral and hope. Rows 4–7 carry `@manual:M10`, which is a reason code
|
|
552
|
+
and a real oracle, not a shrug: write the steps a human follows and the value they must see.
|
|
553
|
+
|
|
554
|
+
### Two mobile traps worth naming while generating
|
|
555
|
+
|
|
556
|
+
- **A `page` assertion proves nothing on mobile.** A native app has no URL, so `Then User see [Login]
|
|
557
|
+
page` compiles to an empty comment (**SG-W022**) and the scenario passes without looking. The
|
|
558
|
+
`screen` spelling is different and is what you want: it treats the target as a plain element and
|
|
559
|
+
emits a real visibility assertion — so name a marker only that screen has (`Then User see [Login
|
|
560
|
+
Submit] button`, or `Then User see [Login] screen` against a screen-root selector), and pair it
|
|
561
|
+
with the previous screen's marker going `is hidden`.
|
|
562
|
+
- **An `open [X] page` / `navigate to` step on mobile navigates nowhere** (**SG-W026**) — the app was
|
|
563
|
+
already launched by the capabilities. Change screens with a real tap or gesture.
|
|
564
|
+
|
|
565
|
+
---
|
|
566
|
+
|
|
481
567
|
## Cleanup & Hooks
|
|
482
568
|
|
|
483
569
|
Add cleanup tags per the `sungen-gherkin-syntax` Cleanup table. Key rules:
|
|
@@ -84,6 +84,7 @@ Hard-fail gate. Any ✗ is a syntax error — must be fixed; do not score furthe
|
|
|
84
84
|
- [ ] A spec `Format:` line → `matches "<format>"`, NOT `with {{v}}` (an exact value pins data that changes daily; `contains` proves nothing)
|
|
85
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`)
|
|
86
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
|
|
87
|
+
- [ ] Table on a `<div>` list (no `<table>` / `role="row"` in the app): the table entry declares `rows:` — without it `is hidden`/`is empty` pass on 0 rows (`SG-W016`). A row label's own `type: locator` entry is ONE positioned row, never the shape
|
|
87
88
|
|
|
88
89
|
### A5. Alert / Scope / Background
|
|
89
90
|
|
|
@@ -107,6 +108,30 @@ Hard-fail gate. Any ✗ is a syntax error — must be fixed; do not score furthe
|
|
|
107
108
|
- [ ] `@parallel` is REQUIRED when a feature mixes auth groups (`@auth:user` + `@no-auth`)
|
|
108
109
|
- [ ] `@flow`: `[Screen:Element]` namespace is consistent; YAML keys quoted with the colon (`"login:submit":`)
|
|
109
110
|
|
|
111
|
+
### A8. Mobile (`@platform:mobile/android/ios` units only)
|
|
112
|
+
|
|
113
|
+
Hard-fail rules — each one is a scenario that PASSES while proving nothing, which is worse than a
|
|
114
|
+
failing one. Skip this section entirely for a web unit.
|
|
115
|
+
|
|
116
|
+
- [ ] **`Then User see [X] page` → FAIL.** A native app has no URL: the step compiles to an empty
|
|
117
|
+
comment (**SG-W022**) and the scenario goes green without looking at the app. Require a marker
|
|
118
|
+
element (`see [X] screen` against a screen-root selector, or a button/text unique to the
|
|
119
|
+
screen) plus the previous screen's marker going `is hidden`.
|
|
120
|
+
- [ ] **`When User open [X] page` / `navigate to` → FAIL.** It navigates nowhere (**SG-W026**) — the
|
|
121
|
+
app was already launched by the wdio capabilities. Require a real tap or gesture.
|
|
122
|
+
`Given User is on [X] screen` is the legitimate app-launch anchor and is NOT a violation.
|
|
123
|
+
- [ ] **A step that establishes app state → FAIL.** `Given the user is logged out` compiles to a
|
|
124
|
+
comment (**SG-W023**); auth lives in the keychain, not in cookies. Require `@reset:data|full`
|
|
125
|
+
on the Feature instead.
|
|
126
|
+
- [ ] **Android-only vocabulary without `@platform:android` → FAIL.** `turn airplane mode|wifi|mobile
|
|
127
|
+
data …`, `open notifications` and `turn device location …` throw on iOS; `hide keyboard` is
|
|
128
|
+
worse — its iOS path is guarded out with **no** throw, so the scenario passes having done
|
|
129
|
+
nothing. Cross-check `docs/spec/step-support-matrix.md` (the `iOS` column) when unsure.
|
|
130
|
+
- [ ] **A web-only step on a mobile unit → FAIL.** Tables, browser storage, tab order, `@mock`, the
|
|
131
|
+
uploader and `hover` have no native equivalent — see `sungen-gherkin-syntax` → Platform Support.
|
|
132
|
+
- [ ] No `{{variable}}` used as a coordinate in `set location to …` — the numbers are resolved at
|
|
133
|
+
compile time and a variable silently becomes `0, 0`. Literal digits only.
|
|
134
|
+
|
|
110
135
|
---
|
|
111
136
|
|
|
112
137
|
## Layer B — Coverage Matrix (against Sun Common Checklist for QA)
|
|
@@ -128,6 +153,22 @@ Build a mapping table: for each applicable group, does the feature have a matchi
|
|
|
128
153
|
| **Security** | SQL Injection; XSS; Data Integrity (edit dropdown/remove `disabled` via DevTools → server rejects). SQL on LIKE/search fields → 2 TCs per the `sungen-viewpoint` SQL 2-layer rule. | VP-SEC | `with {{xss_*}}`/`{{sql_*}}`; data-integrity usually `@manual` |
|
|
129
154
|
| **Cross-surface outcomes** | Admin action → outcome on user-facing surface (portal, mobile, widget); spec defines display condition on another URL | VP-LOGIC-CS | `@high @manual` per surface per business rule — at minimum 1 per cross-surface rule; use `@auth:role` + `@extend` if surface reachable in same test run |
|
|
130
155
|
|
|
156
|
+
**Mobile units add four categories** (`@platform:mobile/android/ios` only). Each maps to a theme the
|
|
157
|
+
`sungen audit` mobile catalog scores, so a gap here is a scored gap, not a stylistic note — the
|
|
158
|
+
per-pattern checklists are in `sungen-viewpoint` → `group-f-mobile-native.md`:
|
|
159
|
+
|
|
160
|
+
| Category | Sub-viewpoint to check | VP map | Sungen representation |
|
|
161
|
+
|---|---|---|---|
|
|
162
|
+
| **App lifecycle** | Resume after background restores the screen's data; a restart preserves what the spec says persists (session, setting, dismissal, show-once); rotation loses nothing and does not double-submit | VP-LOGIC | `background the app for N seconds` · `restart the app` · `rotate to landscape\|portrait`, each with a **value** assertion |
|
|
163
|
+
| **Permission** | Granted path works; denied path explains and routes to Settings; never-ask-again does not loop; a revoked grant is re-checked | VP-LOGIC/SEC | `grant [X] permission` for the granted row; the other three are `@manual:M10` with a real oracle |
|
|
164
|
+
| **Gesture reachability** | The target is reachable — off-screen needs `scroll to`, occluded-by-a-floating-bar needs `tap top of` (scroll is a no-op there); swipe-revealed actions are asserted, not assumed | VP-LOGIC | `swipe <dir> on [X]` · `scroll to [X]` · `tap top of [X]` · `pull to refresh [X]` |
|
|
165
|
+
| **Connectivity** | Offline banner; cached content survives; an action while offline fails clearly without losing input; recovery refreshes without a manual reload | VP-LOGIC | `turn wifi off` → `@platform:android`; iOS `@manual:M10` |
|
|
166
|
+
|
|
167
|
+
**Deduct when a mobile suite has no lifecycle, permission, or gesture oracle at all** — those are the
|
|
168
|
+
three concerns a web-shaped test set always misses, and their absence is the single strongest signal
|
|
169
|
+
that the suite was written for a browser and re-tagged. A screen that genuinely owes none of them
|
|
170
|
+
(a static read-only screen with no permissions) is not penalised — say so in the notes.
|
|
171
|
+
|
|
131
172
|
**Tier-aware**: if the suite only has `@high` (Tier 1) → do NOT penalize missing pure VP-UI (deferred to Tier 2). Require full VP coverage only on a Full review.
|
|
132
173
|
|
|
133
174
|
### EP/BVA rules when mapping
|
|
@@ -180,6 +221,18 @@ Build a mapping table: for each applicable group, does the feature have a matchi
|
|
|
180
221
|
- **error (4xx)** — the status IS the assertion; also check the error-message field when the contract defines one.
|
|
181
222
|
- **anti-pattern (counts against "Avoids anti-patterns")** — re-asserting the input just sent instead of a server-derived value.
|
|
182
223
|
|
|
224
|
+
**Mobile scenarios — depth rubric:** the mobile catalog marks most themes as needing a **data**
|
|
225
|
+
oracle, so a sighting where a value is owed fails the "Specific" check:
|
|
226
|
+
- **a lifecycle/persistence claim asserted by a sighting = fail.** "The screen is still there" after
|
|
227
|
+
`restart the app` passes on an empty shell and on a silent re-login. Require the value that had to
|
|
228
|
+
survive (`text contains {{v}}`, `field with {{v}}`).
|
|
229
|
+
- **a list claim asserted by a count or a sighting = fail.** `see all [List Item] text contains {{v}}`
|
|
230
|
+
is the oracle; a row count passes on the wrong rows.
|
|
231
|
+
- **a navigation claim asserted by one marker = weak.** Require the destination's marker AND the
|
|
232
|
+
origin's marker going `is hidden` — one marker passes when the app never moved.
|
|
233
|
+
- **`keyboard-overlap` is the exception**: its honest oracle IS a sighting (the covered element is
|
|
234
|
+
visible again). Do not deduct for the absence of a value assertion there.
|
|
235
|
+
|
|
183
236
|
### Test Data (10)
|
|
184
237
|
|
|
185
238
|
| Check | Pts |
|
|
@@ -271,6 +324,10 @@ Do NOT deduct points when a `.feature` lacks the following viewpoints (Playwrigh
|
|
|
271
324
|
15. **Mixed auth groups missing `@parallel`** → add `@parallel`.
|
|
272
325
|
16. **Missing secondary behaviors** — spec defines tiebreaker, fallback rule, or secondary sort but no scenario tests it? Add 1 `@high` TC per rule.
|
|
273
326
|
17. **Missing concurrency scenarios** — spec or test-viewpoint mentions multi-tab, multi-user, or simultaneous actions but no `@manual` scenario exists? Add 1 `@manual` TC per risk (`@normal` by default; `@high` if data integrity at risk).
|
|
327
|
+
18. **Mobile: `see [X] page` oracle** → replace with a marker element on the destination screen + the origin's marker `is hidden` (SG-W022 — the original passes having proven nothing).
|
|
328
|
+
19. **Mobile: `open [X] page` / `navigate to`** → replace with the real tap/gesture that changes screens (SG-W026).
|
|
329
|
+
20. **Mobile: Android-only step without a platform tag** → add `@platform:android`, and add the iOS counterpart as `@manual:M10` if the spec owes iOS coverage.
|
|
330
|
+
21. **Mobile: no lifecycle scenario** → add 1 resume scenario (`background the app for N seconds` + a value assertion) and 1 restart scenario per persisted thing.
|
|
274
331
|
|
|
275
332
|
---
|
|
276
333
|
|
|
@@ -338,5 +395,5 @@ If `selectors/<name>.yaml` exists, count lines matching `@needs-live-verify` (pr
|
|
|
338
395
|
|
|
339
396
|
- **When NOT to apply**: exploratory charters, performance/load scripts, pure NFR specs — this rubric grades functional test cases.
|
|
340
397
|
- **Adapting weights**: a project may rebalance the 7 dimensions (e.g. security-critical service: Security→15, Maintainability→0) as long as the total stays 100 and the PASS/CONDITIONAL/FAIL thresholds are restated. Record any change in this section.
|
|
341
|
-
- **References**: detailed syntax → `sungen-gherkin-syntax`; selector keys → `sungen-selector-keys`; VP classification → `sungen-viewpoint`; generation workflow → `sungen-tc-generation`.
|
|
398
|
+
- **References**: detailed syntax → `sungen-gherkin-syntax`; selector keys → `sungen-selector-keys`; VP classification → `sungen-viewpoint` (mobile-native patterns → its `group-f-mobile-native.md`); mobile step vocabulary → `sungen-mobile-gestures`; per-OS support → `docs/spec/step-support-matrix.md`; generation workflow → `sungen-tc-generation`.
|
|
342
399
|
- **Owner / version**: Owner: `<QA Lead>` · Version: `2.0` · Last updated: `2026-06-03`.
|
|
@@ -116,3 +116,16 @@ Scenario: VP-VAL-003 Above maximum is rejected # value = 101
|
|
|
116
116
|
- `@normal` Invalid: Completed → Draft (blocked), Pending → Completed (skip approval)
|
|
117
117
|
|
|
118
118
|
**test-data:** Use named state keys (`order_in_draft`, `order_in_pending`).
|
|
119
|
+
|
|
120
|
+
**On mobile, three state machines exist that no spec draws** — apply this technique to them even
|
|
121
|
+
when `spec.md` has no diagram, because the states are imposed by the platform rather than the
|
|
122
|
+
product:
|
|
123
|
+
|
|
124
|
+
| Machine | States | Transition steps that compile |
|
|
125
|
+
|---|---|---|
|
|
126
|
+
| App lifecycle | foreground · backgrounded · relaunched · first-run · process-death | `background the app for N seconds` · `restart the app` · `reinstall the app`; process-death is `@manual:M10` |
|
|
127
|
+
| Screen entry / exit | entered by tap · entered by deep link · left by OS back · left by in-app control | `open deep link {{url}}` · `go back` (OS/hardware) · a tap on the in-app control — **the last two are different transitions**, since system back skips the screen's own handler unless the app wires it |
|
|
128
|
+
| Connectivity | online · offline · recovering | `turn wifi off` / back on → `@platform:android` (XCUITest cannot toggle connectivity) |
|
|
129
|
+
|
|
130
|
+
Full matrices and the permission axis → `sungen-tc-generation` → "Mobile — the two axes web has no
|
|
131
|
+
concept of". Per-pattern checklists → `sungen-viewpoint` → `group-f-mobile-native.md`.
|