@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
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sungen-viewpoint
|
|
3
|
-
description: '
|
|
4
|
-
generation and review. Auto-loaded by create-test and review commands.'
|
|
3
|
+
description: '32 UI patterns (17 web + 15 mobile-native) x 4 viewpoints — structured
|
|
4
|
+
checklist for test case generation and review. Auto-loaded by create-test and review commands.'
|
|
5
5
|
user-invocable: false
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
## How to use this skill
|
|
9
9
|
|
|
10
|
-
This skill is a **router**. The detailed checklists live in
|
|
10
|
+
This skill is a **router**. The detailed checklists live in 6 group files — load only the ones relevant to the screen under test. Groups A–E are the 17 platform-agnostic patterns; Group F is the mobile-native layer, read IN ADDITION on a `@platform:mobile/android/ios` unit.
|
|
11
11
|
|
|
12
12
|
1. Read the **4 Viewpoints** and **Shared Checks** below (always).
|
|
13
13
|
2. Identify which UI patterns the screen contains, resolve any overlap via **Pattern selection** below, then read **only** the matching group file(s) from the routing table.
|
|
@@ -36,6 +36,22 @@ This skill is a **router**. The detailed checklists live in 5 group files — lo
|
|
|
36
36
|
| Login form / logout button | 15. Login / Logout | `group-e-identity.md` |
|
|
37
37
|
| Sign-up form / SSO | 16. Register | `group-e-identity.md` |
|
|
38
38
|
| Forgot / reset / change password | 17. Password Management | `group-e-identity.md` |
|
|
39
|
+
| **Native app screen (`@platform:mobile/android/ios`) — read Group F IN ADDITION to the patterns above** | | |
|
|
40
|
+
| Sheet sliding up from the bottom edge | 18. Bottom Sheet | `group-f-mobile-native.md` |
|
|
41
|
+
| Persistent bar of 3–5 destinations at the bottom | 19. Tab Bar / Bottom Navigation | `group-f-mobile-native.md` |
|
|
42
|
+
| Horizontal swipe on a row reveals actions | 20. Swipe-to-Delete Row | `group-f-mobile-native.md` |
|
|
43
|
+
| Field opening an OS wheel / date / dropdown picker | 21. Native Picker | `group-f-mobile-native.md` |
|
|
44
|
+
| Runtime permission — camera, location, notifications | 22. Permission Dialog | `group-f-mobile-native.md` |
|
|
45
|
+
| Fingerprint / Face ID / app lock | 23. Biometric Prompt | `group-f-mobile-native.md` |
|
|
46
|
+
| Leaves the app for the system camera or photo library | 24. Camera / Gallery Picker | `group-f-mobile-native.md` |
|
|
47
|
+
| Push messages / notification routing | 25. Push Notification | `group-f-mobile-native.md` |
|
|
48
|
+
| External link, notification payload, or QR opens a screen | 26. Deep-link Entry | `group-f-mobile-native.md` |
|
|
49
|
+
| Offline banner, cached content, retry control | 27. Offline Banner | `group-f-mobile-native.md` |
|
|
50
|
+
| First-launch intro slides / tutorial | 28. Onboarding Carousel | `group-f-mobile-native.md` |
|
|
51
|
+
| Feed reloading by drag-down, paging, Load More | 29. Pull-to-Refresh List | `group-f-mobile-native.md` |
|
|
52
|
+
| Any text field — the soft keyboard covers the screen | 30. Soft-Keyboard Overlap | `group-f-mobile-native.md` |
|
|
53
|
+
| List of switches / preferences | 31. Settings Toggle List | `group-f-mobile-native.md` |
|
|
54
|
+
| Every mobile screen (background, rotate, relaunch) | 32. App Lifecycle & Orientation | `group-f-mobile-native.md` |
|
|
39
55
|
|
|
40
56
|
## Pattern selection (precedence & inheritance)
|
|
41
57
|
|
|
@@ -54,6 +70,8 @@ A screen often matches several patterns at once — a login screen is *both* a f
|
|
|
54
70
|
| Both a keyword box and filter controls | Apply **8. Search** *and* **9. Filter** (Search = free-text match; Filter = structured narrowing) + one combined AND-logic scenario |
|
|
55
71
|
| A form rendered inside an overlay | Apply the form's pattern (1/4/5/15…) for fields/submit **and** **12. Modal/Dialog** for open/close/focus-trap/backdrop |
|
|
56
72
|
|
|
73
|
+
5. **On mobile, Group F is ADDITIVE — never a replacement.** A native screen is still whatever it functionally is: a native login screen is **15**, a native product list is **10**, and the harness routes such a screen to `auth` / `ecommerce-list` deliberately, not to a `mobile-*` page-type. Route by function first (1–17), then add the Group F patterns for the native surfaces present, and generate each check **once** — a native form is **1. Form & Inputs** *and* **30. Soft-Keyboard Overlap**, with the field rules living only in pattern 1. `group-f-mobile-native.md` carries the theme↔catalog map and the precedence detail.
|
|
74
|
+
|
|
57
75
|
## 4 Viewpoints
|
|
58
76
|
|
|
59
77
|
| VP | Focus | Tag |
|
|
@@ -90,16 +108,32 @@ The 4 viewpoints above are the *generic* axes. On a domain screen, the `VP-<CAT>
|
|
|
90
108
|
Generate **once per screen**, do not repeat for each pattern.
|
|
91
109
|
Each pattern only points back with "Shared checks applied: [name]".
|
|
92
110
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
|
99
|
-
|
|
111
|
+
The `Platform` column decides which checks a screen owes. Applying the web three to a native
|
|
112
|
+
screen is wasted work — a `<script>` payload cannot execute in a native text view and there is no
|
|
113
|
+
URL to manipulate — while the native surfaces (deep-link params, clipboard, the recents thumbnail)
|
|
114
|
+
go unchecked unless they are named here.
|
|
115
|
+
|
|
116
|
+
| Check | Condition → Expected | VP | Priority | Platform |
|
|
117
|
+
|---|---|---|---|---|
|
|
118
|
+
| **Loading State** | Data fetch in progress → spinner/skeleton shown, user cannot interact | UI | @normal | both |
|
|
119
|
+
| **Empty State** | Query returns 0 records → clear message shown, layout does not break | UI | @normal | both |
|
|
120
|
+
| **XSS** | Script tag entered into a field → rendered as literal text, not executed | SEC | @high | web |
|
|
121
|
+
| **SQL injection** | SQL payload entered into a field → DB unaffected, no data exposed | SEC | @high | web |
|
|
122
|
+
| **URL Manipulation** | URL params wrong/missing/out-of-range → fallback to default, no 500 crash | SEC | @high | web |
|
|
123
|
+
| **Deep-link param injection** | An external link carries a traversal / script / oversized param → treated as data, no crash, no privileged screen reached | SEC | @high | mobile |
|
|
124
|
+
| **Clipboard leak** | A field holding sensitive data (password, token, card) → the value cannot be copied out, or is excluded from the clipboard | SEC | @high | mobile |
|
|
125
|
+
| **Recents / screenshot leak** | Background the app on a screen showing sensitive data → the app-switcher thumbnail is masked, screenshots blocked where the spec requires it | SEC | @high | mobile |
|
|
126
|
+
| **Root / jailbreak posture** | The app runs on a rooted/jailbroken device → the spec's response (warn, degrade, or block), never a silent full-trust run | SEC | @normal | mobile |
|
|
127
|
+
| **Certificate pinning** | Traffic through an intercepting proxy with a substituted certificate → the app refuses the connection rather than transmitting | SEC | @high | mobile |
|
|
128
|
+
| **Permission over-request** | Cold launch → only the permissions the current feature needs are requested, and each is asked in context | SEC | @normal | mobile |
|
|
100
129
|
|
|
101
130
|
> **SQL injection — 2 layers for search/LIKE fields**: (1) field-level: UI blocks special chars → `@high` automated; (2) API-level: if the field reaches a LIKE query (search, partial-match), send `1 OR 1=1` straight to the API endpoint (bypassing the UI) → verify a parameterized query is used → `@high @manual`. Missing layer 2 = a real attack vector is overlooked even when field validation is correct.
|
|
102
131
|
|
|
132
|
+
> **Two mobile edges the table simplifies.** A screen rendered inside a **WebView** is web for these
|
|
133
|
+
> purposes — apply XSS there. And a native app hits the same API as the web client, so the API-level
|
|
134
|
+
> SQL-injection layer above is still owed by the *system*; it is just not a screen-unit check on a
|
|
135
|
+
> mobile unit. Record it once at the API layer (`qa/api/`), not per mobile screen.
|
|
136
|
+
|
|
103
137
|
---
|
|
104
138
|
|
|
105
139
|
## Security Tag Rules
|
|
@@ -108,6 +142,8 @@ For VP-SEC scenarios testing **unauthorized access** (no login, wrong role, dire
|
|
|
108
142
|
- Use the **`@no-auth`** tag — runs without authentication to verify the redirect/block.
|
|
109
143
|
- Do NOT use `@manual` for these — they are automatable.
|
|
110
144
|
|
|
145
|
+
On **web** the redirect target is a URL, so asserting the page is a real check:
|
|
146
|
+
|
|
111
147
|
```gherkin
|
|
112
148
|
@high @no-auth
|
|
113
149
|
Scenario: VP-SEC-001 Unauthenticated user cannot access admin page
|
|
@@ -115,4 +151,17 @@ Scenario: VP-SEC-001 Unauthenticated user cannot access admin page
|
|
|
115
151
|
Then User see [Login] page
|
|
116
152
|
```
|
|
117
153
|
|
|
154
|
+
On **mobile** that same shape is a false pass. A native app has no URL: the assertion compiles to an
|
|
155
|
+
empty comment and reports **SG-W022**, so the scenario goes green having proven nothing. Assert a
|
|
156
|
+
marker element that only exists on the login screen, plus the protected screen's own marker going
|
|
157
|
+
`is hidden`:
|
|
158
|
+
|
|
159
|
+
```gherkin
|
|
160
|
+
@high @no-auth @platform:mobile
|
|
161
|
+
Scenario: VP-SEC-001 An unauthenticated launch stops at the login screen
|
|
162
|
+
Given User is on [Login] screen
|
|
163
|
+
Then User see [Login Submit] button
|
|
164
|
+
And User see [Account Name] is hidden
|
|
165
|
+
```
|
|
166
|
+
|
|
118
167
|
Use `@manual` only when the environment truly cannot be set up automatically.
|
|
@@ -147,6 +147,7 @@ See `SKILL.md` for the 4 Viewpoints, Shared Checks, and Security Tag Rules.
|
|
|
147
147
|
|
|
148
148
|
- A menu item for a restricted page is shown but the user has no permission → click → a 403 page; or the item is hidden from the menu (verify the DOM has no link and the API returns no data)
|
|
149
149
|
- Direct URL access to a restricted page while not logged in → redirect to Login, the URL is preserved so post-login redirects correctly
|
|
150
|
+
- **The oracle for "role X can open screen Y" is rendered content, not the URL.** A client-side router shows the destination URL before the permission check answers; assert a heading/landmark of the screen (or give its page selector an `anchor:`) so a late 403 bounce fails the case
|
|
150
151
|
|
|
151
152
|
---
|
|
152
153
|
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
# GROUP F: MOBILE NATIVE
|
|
2
|
+
|
|
3
|
+
> The user holds a device. The app is already launched, there is no URL, the OS interrupts, and a
|
|
4
|
+
> finger is the only pointer. These are the concerns the 17 web patterns have no concept of.
|
|
5
|
+
|
|
6
|
+
Patterns: 18. Bottom Sheet · 19. Tab Bar / Bottom Navigation · 20. Swipe-to-Delete Row · 21. Native Picker · 22. Permission Dialog · 23. Biometric Prompt · 24. Camera / Gallery Picker · 25. Push Notification · 26. Deep-link Entry · 27. Offline Banner · 28. Onboarding Carousel · 29. Pull-to-Refresh List · 30. Soft-Keyboard Overlap · 31. Settings Toggle List · 32. App Lifecycle & Orientation
|
|
7
|
+
|
|
8
|
+
See `SKILL.md` for the 4 Viewpoints, the platform-split Shared Checks, and the Security Tag Rules.
|
|
9
|
+
**Step syntax is NOT repeated here** — the step forms live in `sungen-gherkin-syntax` (Platform
|
|
10
|
+
Support + divergence table) and `sungen-mobile-gestures` (the gesture and device vocabulary). This
|
|
11
|
+
file answers only *what to check*. Every step quoted below compiles on the Appium adapter; check
|
|
12
|
+
`docs/spec/step-support-matrix.md` before inventing one.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Read this first: mobile is web patterns PLUS Group F, never instead of
|
|
17
|
+
|
|
18
|
+
A mobile screen is still whatever it functionally is. A native login screen is **15. Login** and a
|
|
19
|
+
native product list is **10. List / Card View** — those patterns carry the business rules, and the
|
|
20
|
+
harness deliberately routes such a screen to `auth` / `ecommerce-list`, not to a `mobile-*`
|
|
21
|
+
page-type. Group F adds the **platform** concerns on top and never restates the functional ones.
|
|
22
|
+
|
|
23
|
+
Precedence, in order:
|
|
24
|
+
|
|
25
|
+
1. **The functional pattern wins.** Route the screen by what it does (1–17), exactly as on web.
|
|
26
|
+
2. **Then add the Group F patterns for the native surfaces present.** A native form screen is
|
|
27
|
+
**1. Form & Inputs** *and* **30. Soft-Keyboard Overlap** — the field rules are generated once, in
|
|
28
|
+
pattern 1; pattern 30 adds only the keyboard concern.
|
|
29
|
+
3. **Generate each check once.** If a Group F row would repeat a check the functional pattern
|
|
30
|
+
already produced, drop the row — a duplicate scenario costs review time and scores nothing.
|
|
31
|
+
4. **`is on [X] screen` is the app-launch anchor, not navigation.** It compiles to an empty comment
|
|
32
|
+
*by design* (the wdio capabilities launched the app). Change screens with a real tap or gesture;
|
|
33
|
+
an active `open [X] page` / `navigate to` on mobile reports **SG-W026** because it promises a
|
|
34
|
+
navigation it does not perform.
|
|
35
|
+
5. **Reset between scenarios is a tag, not a step** — `@reset:none|soft|data|full` on the Feature.
|
|
36
|
+
A step that tries to establish app state (`Given the user is logged out`) reports **SG-W023**.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Theme coverage map — the contract with the harness
|
|
41
|
+
|
|
42
|
+
`sungen audit` scores a mobile screen against `universal-viewpoints.yaml`. This table is the
|
|
43
|
+
authoritative link between what that catalog scores and what generates it; a
|
|
44
|
+
`tests/ai-skills/mobile-viewpoint-catalog-parity.run.ts` gate fails if the two ever drift apart.
|
|
45
|
+
|
|
46
|
+
Read it in the direction you need: **generating** → find the themes of your page-type and go to the
|
|
47
|
+
pattern named; **reviewing** → any theme with no scenario is a scored gap.
|
|
48
|
+
|
|
49
|
+
| Theme | Page-type | Oracle | Generated by |
|
|
50
|
+
|---|---|---|---|
|
|
51
|
+
| `theme:content-visibility` | `mobile-home` | sighting | **19** Tab Bar / Bottom Navigation |
|
|
52
|
+
| `theme:navigation-correctness` | `mobile-home` | sighting | **19** Tab Bar / Bottom Navigation |
|
|
53
|
+
| `theme:list-data` | `mobile-list` | **data** | **29** Pull-to-Refresh List |
|
|
54
|
+
| `theme:refresh-correctness` | `mobile-list` | **data** | **29** Pull-to-Refresh List |
|
|
55
|
+
| `theme:scroll-reachability` | `mobile-list` | **data** | **29** Pull-to-Refresh List · **20** Swipe-to-Delete Row |
|
|
56
|
+
| `theme:detail-data` | `mobile-detail` | **data** | `group-c` **10. List / Card View** — a native detail screen's data oracle is generic; Group F adds no native concern |
|
|
57
|
+
| `theme:list-detail-consistency` | `mobile-detail` | **data, cross-screen** | a **flow**: `User remember [List Item] text as {{v}} … User see [Detail Title] text contains {{v}}`. On a single screen this is honestly `@manual` deferred-to-flow, never a shallow sighting |
|
|
58
|
+
| `theme:field-validation` | `mobile-form` | **data** | `group-a` **1. Form & Inputs** · **21** Native Picker for picker-backed fields |
|
|
59
|
+
| `theme:keyboard-overlap` | `mobile-form` | sighting | **30** Soft-Keyboard Overlap |
|
|
60
|
+
| `theme:submit-correctness` | `mobile-form` | **data** | `group-a`/`group-b` **1** / **4** / **5** |
|
|
61
|
+
| `theme:credential-validation` | `mobile-auth` | **data** | `group-e` **15. Login / Logout** · **23** Biometric Prompt |
|
|
62
|
+
| `theme:session-persistence` | `mobile-auth` | **data** | **32** App Lifecycle & Orientation |
|
|
63
|
+
| `theme:toggle-state-correctness` | `mobile-settings` | **data** | **31** Settings Toggle List |
|
|
64
|
+
| `theme:setting-persistence` | `mobile-settings` | **data** | **31** Settings Toggle List |
|
|
65
|
+
| `theme:orientation-stability` | *universal* | reminder | **32** App Lifecycle & Orientation |
|
|
66
|
+
| `theme:connectivity-degraded` | *universal* | reminder | **27** Offline Banner |
|
|
67
|
+
| `theme:app-lifecycle-resume` | *universal* | reminder | **32** App Lifecycle & Orientation |
|
|
68
|
+
| `theme:permission-prompt` | *universal* | reminder | **22** Permission Dialog |
|
|
69
|
+
| `theme:gesture-reachability` | *universal* | reminder | **20** Swipe-to-Delete Row · **29** Pull-to-Refresh List |
|
|
70
|
+
| `theme:deep-link-entry` | *universal* | reminder | **26** Deep-link Entry |
|
|
71
|
+
|
|
72
|
+
**Oracle column — this is the whole point of the phase-06 catalog.** A theme marked **data** is not
|
|
73
|
+
covered by seeing the screen: it needs a value assertion (`text contains {{v}}`, `message contains
|
|
74
|
+
{{v}}`, `field with {{v}}`). A suite that only asserts presence scores as *thin* even with every
|
|
75
|
+
theme "covered". A **reminder** theme is real but unscored — it applies to some mobile screens and
|
|
76
|
+
not others, so generate it when the screen owes it, not to satisfy a gate.
|
|
77
|
+
|
|
78
|
+
**Patterns 18, 21, 24, 25, 28 carry no scored theme.** They are native surfaces the catalog does not
|
|
79
|
+
score, because not every mobile screen has them. Generate them for real coverage; do not expect a
|
|
80
|
+
score change.
|
|
81
|
+
|
|
82
|
+
### Being on mobile does not select a `mobile-*` page-type {#page-type-selection}
|
|
83
|
+
|
|
84
|
+
A mobile target considers the mobile page-types **and the web ones**, and the winner is decided by
|
|
85
|
+
how many `detect_keywords` your scenarios and viewpoints actually hit — at least **2** distinct
|
|
86
|
+
keywords, and at least **1** more than the runner-up. Nothing about the target being mobile tips the
|
|
87
|
+
scale.
|
|
88
|
+
|
|
89
|
+
The consequence catches people out on the two most ordinary screens:
|
|
90
|
+
|
|
91
|
+
| You wrote | Selected type | Because |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| a login screen using *login · password · sign in · credential* | `auth` (web) | those are `auth`'s keywords; `mobile-auth` matches on *biometric · fingerprint · face id · touch id · app lock* |
|
|
94
|
+
| a form using *form · submit · field · input · validation* | `form` (web) | `mobile-form` matches on *soft keyboard · keyboard overlap · hide keyboard · on-screen keyboard · keyboard covers* |
|
|
95
|
+
|
|
96
|
+
So a mobile login screen is scored against the web `auth` themes, and `theme:session-persistence` /
|
|
97
|
+
`theme:credential-validation` are never demanded of it. That is the catalog's stated philosophy — a
|
|
98
|
+
native page-type is selected by native vocabulary, not by the platform flag — but it means **writing
|
|
99
|
+
a biometric scenario is how you opt in**, not a bonus on top.
|
|
100
|
+
|
|
101
|
+
**Opting into `mobile-form` costs an `@platform:android` tag.** The vocabulary that selects it is
|
|
102
|
+
keyboard vocabulary, and `hide the keyboard` is Android-only: on iOS the step is guarded out with
|
|
103
|
+
**no throw**, so a scenario written to reach this page-type would pass on iOS having done nothing.
|
|
104
|
+
Tag such a feature `@platform:android` — see **30. Soft-Keyboard Overlap**. `mobile-auth` has no
|
|
105
|
+
equivalent cost; biometric vocabulary runs on both.
|
|
106
|
+
|
|
107
|
+
Fewer than 2 hits, or a tie, and the page-type is **undetermined**: no themes are required at all and
|
|
108
|
+
the gate passes leniently. A near-miss therefore reads as a pass, not as a gap.
|
|
109
|
+
|
|
110
|
+
**To settle it deliberately, declare it.** `page-type: mobile-auth` in `test-viewpoint.md` overrides
|
|
111
|
+
detection outright — no keyword counting, no ties. Use that when the screen genuinely owes the native
|
|
112
|
+
themes and the prose does not happen to say the magic words.
|
|
113
|
+
|
|
114
|
+
*(Keywords above are copied from `universal-viewpoints.yaml`; that file is the source of truth if the
|
|
115
|
+
two ever disagree.)*
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 18. Bottom Sheet
|
|
120
|
+
|
|
121
|
+
**Apply when**: content slides up from the bottom edge over the current screen — a share sheet, a
|
|
122
|
+
filter sheet, an action sheet.
|
|
123
|
+
|
|
124
|
+
**Shared checks applied**: Loading State
|
|
125
|
+
|
|
126
|
+
| Check | Condition → Expected | VP | Priority | Theme |
|
|
127
|
+
|---|---|---|---|---|
|
|
128
|
+
| Sheet opens | Tap the trigger → the sheet's own title/content is visible | UI | @high | — |
|
|
129
|
+
| Sheet closes, **proven** | Swipe down on the sheet → `Then User see [Sheet] is hidden`. `dismiss [X]` is best-effort and never fails, so it can only be SETUP — a closure claim needs the hidden assertion | LOGIC | @high | — |
|
|
130
|
+
| Scrim tap | Tap outside the sheet → it closes (or is explicitly modal and does not) | LOGIC | @normal | — |
|
|
131
|
+
| Content behind is inert | While the sheet is up, the underlying control does not react to a tap | LOGIC | @normal | — |
|
|
132
|
+
| Half → full expansion | Swipe up on a half-height sheet → the previously clipped row becomes visible | UI | @low | — |
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## 19. Tab Bar / Bottom Navigation
|
|
137
|
+
|
|
138
|
+
**Apply when**: a persistent bar of 3–5 destinations sits at the bottom (or top) of the app.
|
|
139
|
+
|
|
140
|
+
**Shared checks applied**: Loading State · Empty State
|
|
141
|
+
|
|
142
|
+
| Check | Condition → Expected | VP | Priority | Theme |
|
|
143
|
+
|---|---|---|---|---|
|
|
144
|
+
| Landing content | The launch screen shows its own content, not an empty shell | UI | @high | `theme:content-visibility` |
|
|
145
|
+
| Tab switches screen | Tap another tab → a marker unique to the destination is visible **and** the origin's own marker `is hidden` | LOGIC | @high | `theme:navigation-correctness` |
|
|
146
|
+
| Active indicator | After switching → the tapped tab reads selected, the previous one does not | UI | @normal | `theme:content-visibility` |
|
|
147
|
+
| State is kept per tab | Scroll a list, leave the tab, come back → the same position/filters, not a reset | LOGIC | @normal | `theme:navigation-correctness` |
|
|
148
|
+
| The bar occludes content | The last list row sits under the floating bar → `User tap top of [Last Row] row`. **`scroll to` cannot fix this** — `scrollIntoView` is a no-op on an element already inside the viewport, occluded or not | LOGIC | @high | `theme:navigation-correctness` |
|
|
149
|
+
|
|
150
|
+
> Two markers, not one. "Left Home" asserted only by seeing the destination passes when the app
|
|
151
|
+
> never moved and both screens share an element. Assert the destination marker AND the origin
|
|
152
|
+
> marker going `is hidden`.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 20. Swipe-to-Delete Row
|
|
157
|
+
|
|
158
|
+
**Apply when**: a horizontal swipe on a list row reveals actions (delete, archive, pin).
|
|
159
|
+
|
|
160
|
+
**Shared checks applied**: Empty State
|
|
161
|
+
|
|
162
|
+
| Check | Condition → Expected | VP | Priority | Theme |
|
|
163
|
+
|---|---|---|---|---|
|
|
164
|
+
| Swipe reveals the action | `User swipe left on [List Item] row` → the action button is visible | UI | @high | `theme:gesture-reachability` |
|
|
165
|
+
| Delete removes the row's DATA | Confirm the delete → the removed value is gone from the list: `User see all [List Item] text contains {{remaining_label}}`, not merely "the row is hidden" | LOGIC | @high | `theme:scroll-reachability` |
|
|
166
|
+
| Swipe back cancels | Swipe the row the other way → the action hides, nothing was deleted | LOGIC | @normal | `theme:gesture-reachability` |
|
|
167
|
+
| Undo | An undo affordance appears → tapping it restores the row's value | LOGIC | @normal | `theme:scroll-reachability` |
|
|
168
|
+
| Off-screen row | The target row is below the fold → `User scroll to [Last List Item] row` first, then swipe | LOGIC | @normal | `theme:gesture-reachability` |
|
|
169
|
+
| Last row deleted | Delete the only row → the empty state shows, the layout does not break | UI | @normal | — |
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## 21. Native Picker
|
|
174
|
+
|
|
175
|
+
**Apply when**: a field opens an OS picker — a wheel/spinner, a date picker, a dropdown sheet.
|
|
176
|
+
|
|
177
|
+
**Shared checks applied**: (no default shared check)
|
|
178
|
+
|
|
179
|
+
| Check | Condition → Expected | VP | Priority | Theme |
|
|
180
|
+
|---|---|---|---|---|
|
|
181
|
+
| Selection lands on the field | `User select {{country}} in [Country Picker] dropdown` → `Then User see [Country Picker] text contains {{country}}` | VAL | @high | `theme:field-validation` |
|
|
182
|
+
| Cancel keeps the old value | Open the picker, cancel → the field still holds its previous value | LOGIC | @normal | `theme:field-validation` |
|
|
183
|
+
| Constrained range | A date picker with a min/max → an out-of-range value cannot be selected, or is rejected with the spec's message | VAL | @high | `theme:field-validation` |
|
|
184
|
+
| Dependent picker | Choosing a parent value re-populates the child picker and clears a now-invalid child selection | LOGIC | @high | `theme:field-validation` |
|
|
185
|
+
|
|
186
|
+
> There is no `<select>` in a native tree. Which template a `select` step reaches is decided by the
|
|
187
|
+
> **selector's role**, not the step text: `role: combobox|listbox` → the native picker path,
|
|
188
|
+
> no role → a custom-dropdown two-tap. Both emit the same open-then-tap shape on mobile, so declare
|
|
189
|
+
> the role in `selectors.yaml` and assert the field afterwards rather than assuming.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## 22. Permission Dialog
|
|
194
|
+
|
|
195
|
+
**Apply when**: the screen needs a runtime permission — camera, location, notifications, contacts.
|
|
196
|
+
|
|
197
|
+
**Shared checks applied**: Permission over-request
|
|
198
|
+
|
|
199
|
+
| Check | Condition → Expected | VP | Priority | Theme |
|
|
200
|
+
|---|---|---|---|---|
|
|
201
|
+
| Granted path | `User grant camera permission` → the gated feature works and its content is visible | LOGIC | @high | `theme:permission-prompt` |
|
|
202
|
+
| Asked only in context | The prompt appears when the feature is used, not on cold launch | UI | @normal | `theme:permission-prompt` |
|
|
203
|
+
| Denied path | Deny → an in-app explanation with a route to Settings, **never a crash or a silent dead button**. No step denies a permission, so this is `@manual:M10` unless the project ships a selector for the OS dialog | LOGIC | @high | `theme:permission-prompt` |
|
|
204
|
+
| Never-ask-again | Deny twice, relaunch → the app must not loop the request; shows the Settings route instead. `@manual:M10` | LOGIC | @normal | `theme:permission-prompt` |
|
|
205
|
+
| Revoked while running | Revoke in Settings and return → the app re-checks rather than using a stale grant. `@manual:M10` | LOGIC | @normal | `theme:permission-prompt` |
|
|
206
|
+
|
|
207
|
+
> `grant [X] permission` is implemented on **both** OSes (Android `changePermissions`, iOS
|
|
208
|
+
> Simulator `setPermission`) — iOS additionally needs the `applesimutils` binary or the driver
|
|
209
|
+
> fails loud with the install hint. Only the *granted* cell is automatable today; the other three
|
|
210
|
+
> carry `@manual:M10` (device/OS capability), which is a reason code, not an excuse.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 23. Biometric Prompt
|
|
215
|
+
|
|
216
|
+
**Apply when**: the app authenticates with fingerprint / Face ID, or locks behind a biometric gate.
|
|
217
|
+
|
|
218
|
+
**Shared checks applied**: (no default shared check)
|
|
219
|
+
|
|
220
|
+
| Check | Condition → Expected | VP | Priority | Theme |
|
|
221
|
+
|---|---|---|---|---|
|
|
222
|
+
| Match unlocks | `User pass biometric authentication` → the protected screen's own data is visible | LOGIC | @high | `theme:credential-validation` |
|
|
223
|
+
| Non-match is refused | `User fail biometric authentication` → the app stays locked and shows the spec's error, and the passcode fallback is offered | VAL | @high | `theme:credential-validation` |
|
|
224
|
+
| Fallback path | Decline biometrics → the password form is reachable and works | LOGIC | @high | `theme:credential-validation` |
|
|
225
|
+
| Not enrolled | No enrolled biometric on the device → the app falls back silently, no error dialog | LOGIC | @normal | `theme:credential-validation` |
|
|
226
|
+
| Lock-out | Repeated non-matches → the OS lock-out is handled with the passcode route, not a stuck screen. `@manual:M11` | LOGIC | @normal | `theme:credential-validation` |
|
|
227
|
+
|
|
228
|
+
> Emulator/Simulator ONLY (Android `mobile: fingerprint` needs finger 1 enrolled; iOS
|
|
229
|
+
> `sendBiometricMatch`). A real device rejects the command and the step fails loud — on a real-device
|
|
230
|
+
> lane these are `@manual:M11`.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 24. Camera / Gallery Picker
|
|
235
|
+
|
|
236
|
+
**Apply when**: the flow leaves the app for the system camera or photo library and returns with a file.
|
|
237
|
+
|
|
238
|
+
**Shared checks applied**: Permission over-request
|
|
239
|
+
|
|
240
|
+
| Check | Condition → Expected | VP | Priority | Theme |
|
|
241
|
+
|---|---|---|---|---|
|
|
242
|
+
| Return with a file | Pick an image → the app shows the thumbnail/filename and the upload control enables | LOGIC | @high | — |
|
|
243
|
+
| Return with nothing | Cancel the picker → the app returns to the same state, no spinner left running | LOGIC | @high | — |
|
|
244
|
+
| Permission refused | Library/camera access denied → the in-app explanation shows, the picker is not launched | LOGIC | @high | — |
|
|
245
|
+
| Oversized / wrong type | A file past the spec's limit or of a rejected type → the spec's error, no upload | VAL | @high | — |
|
|
246
|
+
| Killed while away | The OS reclaims the app while the camera is open → returning re-enters a valid state, not a blank screen. `@manual:M10` | LOGIC | @normal | — |
|
|
247
|
+
|
|
248
|
+
> The system camera and library are **other apps**. Automating them means driving a foreign UI, so
|
|
249
|
+
> most of this pattern is honestly `@manual:M11` (real camera) / `@manual:M10` (OS picker variance).
|
|
250
|
+
> Write the checks anyway — a manual case with a real oracle beats a missing one.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## 25. Push Notification
|
|
255
|
+
|
|
256
|
+
**Apply when**: the app receives push messages, or a notification deep-links into a screen.
|
|
257
|
+
|
|
258
|
+
**Shared checks applied**: (no default shared check)
|
|
259
|
+
|
|
260
|
+
| Check | Condition → Expected | VP | Priority | Theme |
|
|
261
|
+
|---|---|---|---|---|
|
|
262
|
+
| Shade shows the message | `User open notifications` → the notification's title/body matches the spec. **Android only** → tag the scenario `@platform:android` | UI | @high | — |
|
|
263
|
+
| Tap routes correctly | Tap the notification → the app opens the screen the payload names, with that item's data | LOGIC | @high | — |
|
|
264
|
+
| Foreground arrival | A push arrives while the app is open → in-app banner per spec, no OS shade duplicate | UI | @normal | — |
|
|
265
|
+
| Badge / count | Reading the item clears the badge and the unread count | LOGIC | @normal | — |
|
|
266
|
+
| Real token | Delivery from the real push service needs a real device token → `@manual:M11` | LOGIC | @high | — |
|
|
267
|
+
|
|
268
|
+
> `open notifications` is the **notification shade** and XCUITest has no shade automation — the step
|
|
269
|
+
> throws loud on iOS. Keep any scenario that uses it `@platform:android`; iOS coverage of the same
|
|
270
|
+
> concern is `@manual:M10`.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## 26. Deep-link Entry
|
|
275
|
+
|
|
276
|
+
**Apply when**: an external link, a notification payload, or a QR code opens a specific screen.
|
|
277
|
+
|
|
278
|
+
**Shared checks applied**: Deep-link param injection
|
|
279
|
+
|
|
280
|
+
| Check | Condition → Expected | VP | Priority | Theme |
|
|
281
|
+
|---|---|---|---|---|
|
|
282
|
+
| Cold-start entry | `User open deep link {{item_url}}` from a killed app → the target screen shows **that item's** data, not the default screen | LOGIC | @high | `theme:deep-link-entry` |
|
|
283
|
+
| Warm entry | The same link while the app is running → routes without losing the current stack in a way the spec forbids | LOGIC | @high | `theme:deep-link-entry` |
|
|
284
|
+
| Auth-gated target | A deep link to a protected screen while signed out → the login screen, and after signing in the app lands on the original target | SEC | @high | `theme:deep-link-entry` |
|
|
285
|
+
| Unknown / stale target | A link to a deleted or non-existent id → the spec's not-found state, never a crash or a blank screen | LOGIC | @high | `theme:deep-link-entry` |
|
|
286
|
+
| Malicious param | A path-traversal / script / oversized param in the link → treated as data, no crash, no privileged screen reached | SEC | @high | `theme:deep-link-entry` |
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## 27. Offline Banner
|
|
291
|
+
|
|
292
|
+
**Apply when**: the screen reacts to connectivity — an offline banner, cached content, a retry button.
|
|
293
|
+
|
|
294
|
+
**Shared checks applied**: Loading State · Empty State
|
|
295
|
+
|
|
296
|
+
All scenarios here need `@platform:android`: connectivity is toggled with `mobile: setConnectivity`
|
|
297
|
+
and XCUITest cannot toggle it, so the step throws loud on iOS.
|
|
298
|
+
|
|
299
|
+
| Check | Condition → Expected | VP | Priority | Theme |
|
|
300
|
+
|---|---|---|---|---|
|
|
301
|
+
| Banner appears | `User turn wifi off` → the offline banner is visible with the spec's text | UI | @high | `theme:connectivity-degraded` |
|
|
302
|
+
| Cached content survives | Offline on a screen already loaded → the previously fetched values are still asserted, not a blank list | LOGIC | @high | `theme:connectivity-degraded` |
|
|
303
|
+
| Action while offline | Submit while offline → a clear failure message and the entered data is preserved, never a silent loss | LOGIC | @high | `theme:connectivity-degraded` |
|
|
304
|
+
| Recovery | Turn connectivity back on → the banner goes `is hidden` and the data refreshes without a manual reload | LOGIC | @high | `theme:connectivity-degraded` |
|
|
305
|
+
| Cold launch offline | Launch with no connection → the offline state, not an indefinite spinner | UI | @normal | `theme:connectivity-degraded` |
|
|
306
|
+
|
|
307
|
+
> Do **not** author a cleanup step to restore connectivity "in case the test dies". The generated
|
|
308
|
+
> `wdio.conf.ts` restores the baseline (airplane off, wifi/data/location on) in `onPrepare` /
|
|
309
|
+
> `onComplete`, so even a run whose Appium session died mid-toggle heals itself.
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## 28. Onboarding Carousel
|
|
314
|
+
|
|
315
|
+
**Apply when**: first launch shows swipeable intro slides, a tutorial, or a what's-new sheet.
|
|
316
|
+
|
|
317
|
+
**Shared checks applied**: (no default shared check)
|
|
318
|
+
|
|
319
|
+
| Check | Condition → Expected | VP | Priority | Theme |
|
|
320
|
+
|---|---|---|---|---|
|
|
321
|
+
| Slides advance | Swipe left on the carousel → slide 2's own text is visible and slide 1's `is hidden` | UI | @high | — |
|
|
322
|
+
| Page indicator | The dot/counter tracks the current slide | UI | @normal | — |
|
|
323
|
+
| Skip / Done | Skip or finish → the app lands on the real first screen with its content loaded | LOGIC | @high | — |
|
|
324
|
+
| Shown once | `User restart the app` after finishing → the onboarding `is hidden` and the app opens straight to the main screen | LOGIC | @high | — |
|
|
325
|
+
| Reappears after reinstall | `User reinstall the app` → onboarding shows again (it is install-time state, not session state) | LOGIC | @normal | — |
|
|
326
|
+
|
|
327
|
+
> "Shown once" is the classic false pass: asserting only that the main screen appears passes even
|
|
328
|
+
> when the carousel is on top of it. Assert the carousel's own marker going `is hidden`.
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## 29. Pull-to-Refresh List
|
|
333
|
+
|
|
334
|
+
**Apply when**: a feed or list reloads by dragging down, pages as it scrolls, or has a Load More control.
|
|
335
|
+
|
|
336
|
+
**Shared checks applied**: Loading State · Empty State
|
|
337
|
+
|
|
338
|
+
| Check | Condition → Expected | VP | Priority | Theme |
|
|
339
|
+
|---|---|---|---|---|
|
|
340
|
+
| Rows carry the expected DATA | `User see all [List Item] text contains {{expected_label}}` — a row count or a sighting is **not** this check | VAL | @high | `theme:list-data` |
|
|
341
|
+
| Refresh changes the data | `User pull to refresh [List] section` → `User see all [List Item] text contains {{expected_label}}` for the refreshed set, so a no-op refresh fails | LOGIC | @high | `theme:refresh-correctness` |
|
|
342
|
+
| Spinner resolves | The refresh indicator appears and then goes `is hidden` — a stuck spinner is a real defect | UI | @normal | `theme:refresh-correctness` |
|
|
343
|
+
| Off-screen item is reachable | `User scroll to [Last List Item] row` → `User see [Last List Item] text contains {{expected_last}}` | LOGIC | @high | `theme:scroll-reachability` |
|
|
344
|
+
| Next page appends | Reaching the end loads more and **keeps** the earlier rows rather than replacing them | LOGIC | @high | `theme:scroll-reachability` |
|
|
345
|
+
| Empty feed | No records → the empty state's own message, and pulling to refresh from it does not crash | UI | @normal | `theme:list-data` |
|
|
346
|
+
|
|
347
|
+
> `scroll to [X]` has two measured failure modes worth knowing before you rely on it: it throws when
|
|
348
|
+
> the screen has no `android.widget.ScrollView` node, and it is a **no-op** when the target is already
|
|
349
|
+
> inside the viewport but hidden behind a floating bar. For the occluded case use `tap top of [X]`.
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## 30. Soft-Keyboard Overlap
|
|
354
|
+
|
|
355
|
+
**Apply when**: the screen has any text field — the keyboard covers roughly half the screen.
|
|
356
|
+
|
|
357
|
+
**Shared checks applied**: (no default shared check)
|
|
358
|
+
|
|
359
|
+
| Check | Condition → Expected | VP | Priority | Theme |
|
|
360
|
+
|---|---|---|---|---|
|
|
361
|
+
| Focused field stays visible | Tap the lowest field → the field itself is still visible above the keyboard | UI | @high | `theme:keyboard-overlap` |
|
|
362
|
+
| The submit control is reachable | With the keyboard up, the primary action is either visible or reachable by scrolling — not permanently underneath | UI | @high | `theme:keyboard-overlap` |
|
|
363
|
+
| Dismissing reveals what was covered | `User hide keyboard` → the element the keyboard covered is visible again. Tag `@platform:android`: the iOS path is guarded out with **no** throw, so on iOS the step does nothing and this oracle would pass having proven nothing | LOGIC | @high | `theme:keyboard-overlap` |
|
|
364
|
+
| The error message is not hidden | A validation error on a bottom field → the message is visible with the keyboard up | VAL | @high | `theme:keyboard-overlap` |
|
|
365
|
+
| Return-key action | The keyboard's Next/Done key moves focus or submits per spec | LOGIC | @normal | `theme:keyboard-overlap` |
|
|
366
|
+
|
|
367
|
+
> This is the one Group F theme whose honest oracle is a **sighting**, not a value: after the
|
|
368
|
+
> keyboard is gone the field and its action are visible again. Do not invent a data assertion here.
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## 31. Settings Toggle List
|
|
373
|
+
|
|
374
|
+
**Apply when**: a list of switches, preferences, or a profile-settings screen.
|
|
375
|
+
|
|
376
|
+
**Shared checks applied**: (no default shared check)
|
|
377
|
+
|
|
378
|
+
| Check | Condition → Expected | VP | Priority | Theme |
|
|
379
|
+
|---|---|---|---|---|
|
|
380
|
+
| Toggle reflects the stored state | `User see [Notifications Toggle] text contains {{expected_state}}` — read the state, do not assume the default | VAL | @high | `theme:toggle-state-correctness` |
|
|
381
|
+
| Flipping takes effect | `User check [Notifications Toggle] checkbox` → the dependent UI appears/disappears **and** the toggle reads the new state | LOGIC | @high | `theme:toggle-state-correctness` |
|
|
382
|
+
| Setting survives a restart | `User restart the app` → `User see [Notifications Toggle] text contains {{expected_state}}` | LOGIC | @high | `theme:setting-persistence` |
|
|
383
|
+
| Failed save reverts | The save fails (offline / server error) → the toggle returns to its real state instead of lying | LOGIC | @high | `theme:setting-persistence` |
|
|
384
|
+
| Dependent settings | Turning a parent off disables its children and their stored values are unchanged when it is turned back on | LOGIC | @normal | `theme:toggle-state-correctness` |
|
|
385
|
+
|
|
386
|
+
> Use `check`/`uncheck`, never `click`, on a switch — those templates read the current state first
|
|
387
|
+
> and are idempotent, so a re-run does not silently invert the setting.
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## 32. App Lifecycle & Orientation
|
|
392
|
+
|
|
393
|
+
**Apply when**: always. Every mobile screen can be backgrounded, rotated, and relaunched.
|
|
394
|
+
|
|
395
|
+
**Shared checks applied**: Recents / screenshot leak
|
|
396
|
+
|
|
397
|
+
| Check | Condition → Expected | VP | Priority | Theme |
|
|
398
|
+
|---|---|---|---|---|
|
|
399
|
+
| Resume restores the screen | `User background the app for 5 seconds` → the same screen with its own data, not an empty shell or a re-login | LOGIC | @high | `theme:app-lifecycle-resume` |
|
|
400
|
+
| In-progress input survives | Background mid-form → returning keeps the typed values (or clears them deliberately per spec) | LOGIC | @high | `theme:app-lifecycle-resume` |
|
|
401
|
+
| Session survives a restart | `User restart the app` → `User see [Account Name] text contains {{user_name}}` | LOGIC | @high | `theme:session-persistence` |
|
|
402
|
+
| Rotation keeps state | `User rotate to landscape` → the content and any entered values survive; `User rotate to portrait` restores the layout | UI | @normal | `theme:orientation-stability` |
|
|
403
|
+
| Rotation does not duplicate work | Rotating mid-load does not fire a second request or double-submit | LOGIC | @normal | `theme:orientation-stability` |
|
|
404
|
+
| Process death | The OS kills a backgrounded app and the user returns → the screen is restored from saved state, not a crash. Not expressible as a step → `@manual:M10` | LOGIC | @high | `theme:app-lifecycle-resume` |
|
|
405
|
+
|
|
406
|
+
> `restart the app` (kill → relaunch) is THE persistence oracle — reach for it for session,
|
|
407
|
+
> setting, dismissal and show-once claims. `reinstall the app` clears install-time state instead,
|
|
408
|
+
> and requires the session to have installed via `APP_APK` / `IOS_APP`; attaching by package makes
|
|
409
|
+
> the step fail loud with that exact fix.
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## ⚡ Cross-pattern interactions
|
|
414
|
+
|
|
415
|
+
- **19 + 30**: The keyboard opens over a bottom tab bar → the bar must not float above the keyboard, and the focused field stays visible.
|
|
416
|
+
- **22 + 24**: Permission denied then granted inside one flow → the picker becomes usable without a relaunch.
|
|
417
|
+
- **26 + 15**: A deep link into a protected screen while signed out → login, then land on the original target, not the default home screen.
|
|
418
|
+
- **27 + 29**: Pull to refresh while offline → the offline message, and the previously loaded rows are still asserted rather than cleared.
|
|
419
|
+
- **32 + 31**: Change a setting, background the app, return → the change is still applied and still reads correctly.
|
|
@@ -150,7 +150,7 @@ Add to `.vscode/settings.json` to auto-load Gherkin syntax when editing `.featur
|
|
|
150
150
|
| Pattern | Example |
|
|
151
151
|
|---|---|
|
|
152
152
|
| Navigate | `User is on [login] page` |
|
|
153
|
-
| Navigate with data | `User is on [user detail] page with {{
|
|
153
|
+
| Navigate with data | `User is on [user detail] page with {{user_query}}` — `{{v}}` is a query/fragment (`?id=200`, `#tab`) appended to the page value, never a bare id |
|
|
154
154
|
| Click | `User click [Submit] button` |
|
|
155
155
|
| Fill | `User fill [Email] field with {{email}}` |
|
|
156
156
|
| Select | `User select [Country] dropdown with {{country}}` |
|
|
Binary file
|
|
@@ -64,4 +64,35 @@ export declare function joinPath(base: string, path: string, declared?: string |
|
|
|
64
64
|
* at all constrains the path only (the compiler warns: SG-W014).
|
|
65
65
|
*/
|
|
66
66
|
export declare function urlMatches({ pathname, declared, dataRef }: UrlExpectation): (u: URL) => boolean;
|
|
67
|
+
/**
|
|
68
|
+
* `toHaveURL` is a polling assertion: it passes on the FIRST sample that matches. A client-side
|
|
69
|
+
* router pushes the destination URL before the route guard or the API has answered, so a user
|
|
70
|
+
* with no permission is "on" `/items/detail` for a few hundred milliseconds — long enough for
|
|
71
|
+
* the assertion to sample it, pass, and end the test green — and is then bounced to `/forbidden`
|
|
72
|
+
* with no assertion left to see it. A real project shipped a permission matrix on that oracle;
|
|
73
|
+
* the 403s were found by hand (#662).
|
|
74
|
+
*
|
|
75
|
+
* `expectPage` is the page oracle `see [X] page` compiles to now. Arriving is not enough: the URL
|
|
76
|
+
* has to be STILL matching once the page has settled.
|
|
77
|
+
* 1. arrive — the same predicate `toHaveURL` always used;
|
|
78
|
+
* 2. settle — wait for network to go quiet (bounded: an app that polls forever must not hang
|
|
79
|
+
* the test, so a timeout here is not a failure);
|
|
80
|
+
* 3. hold — sample the URL over a settle window; the moment it stops matching, fail and
|
|
81
|
+
* NAME where it went, because that destination is the finding;
|
|
82
|
+
* 4. re-assert — the arrival predicate, once more, on the settled page.
|
|
83
|
+
*
|
|
84
|
+
* `SUNGEN_URL_SETTLE_MS` (default 1000) is the hold window; `SUNGEN_URL_NETWORKIDLE_MS` (default
|
|
85
|
+
* 3000) bounds step 2. Both are runtime knobs, so a slow environment is a config change, not a
|
|
86
|
+
* regenerate.
|
|
87
|
+
*/
|
|
88
|
+
export declare function expectPage(page: {
|
|
89
|
+
url(): string;
|
|
90
|
+
waitForLoadState(state: 'networkidle', opts?: {
|
|
91
|
+
timeout?: number;
|
|
92
|
+
}): Promise<void>;
|
|
93
|
+
}, expectation: UrlExpectation, opts?: {
|
|
94
|
+
settleMs?: number;
|
|
95
|
+
networkIdleMs?: number;
|
|
96
|
+
assertUrl?: (matches: (u: URL) => boolean) => Promise<void>;
|
|
97
|
+
}): Promise<void>;
|
|
67
98
|
//# sourceMappingURL=specs-url-assert.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"specs-url-assert.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-url-assert.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;GAiBG;
|
|
1
|
+
{"version":3,"file":"specs-url-assert.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-url-assert.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;GAiBG;AAIH,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6FAA6F;IAC7F,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAYrF;AAeD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,cAAc,GAAG,CAAC,CAAC,EAAE,GAAG,KAAK,OAAO,CAuC/F;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE;IAAE,GAAG,IAAI,MAAM,CAAC;IAAC,gBAAgB,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,EAC3G,WAAW,EAAE,cAAc,EAC3B,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAAO,GACpH,OAAO,CAAC,IAAI,CAAC,CAyBf"}
|