@sun-asterisk/sungen 3.2.34-beta.1 → 3.2.35
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/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +3 -29
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/index.js +0 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/exporters/test-data-resolver.d.ts.map +1 -1
- package/dist/exporters/test-data-resolver.js +12 -2
- package/dist/exporters/test-data-resolver.js.map +1 -1
- package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
- package/dist/generators/gherkin-parser/index.js +1 -0
- package/dist/generators/gherkin-parser/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/contain-text-assertion.hbs +2 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/expanded-assertion.hbs +2 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/date-picker-unsupported.hbs +5 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/fill-action.hbs +13 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +9 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/expanded-assertion.hbs +12 -0
- package/dist/generators/test-generator/code-generator.js +4 -5
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/diagnostics.d.ts +9 -3
- package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
- package/dist/generators/test-generator/diagnostics.js +9 -3
- package/dist/generators/test-generator/diagnostics.js.map +1 -1
- package/dist/generators/test-generator/patterns/types.d.ts +1 -1
- package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.d.ts +8 -0
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +52 -8
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.d.ts +16 -0
- package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.js +59 -0
- package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +10 -2
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +2 -13
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/next-step.d.ts +0 -2
- package/dist/harness/next-step.d.ts.map +1 -1
- package/dist/harness/next-step.js +1 -15
- package/dist/harness/next-step.js.map +1 -1
- package/dist/harness/parse.d.ts +3 -6
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +4 -9
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +9 -11
- package/dist/harness/sensors.js.map +1 -1
- package/dist/harness/viewpoint-baseline.d.ts.map +1 -1
- package/dist/harness/viewpoint-baseline.js +1 -7
- package/dist/harness/viewpoint-baseline.js.map +1 -1
- package/dist/harness/viewpoint-ledger.d.ts +3 -13
- package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
- package/dist/harness/viewpoint-ledger.js +8 -38
- package/dist/harness/viewpoint-ledger.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/orchestrator/ai-skills/commands.d.ts.map +1 -1
- package/dist/orchestrator/ai-skills/commands.js +5 -3
- package/dist/orchestrator/ai-skills/commands.js.map +1 -1
- package/dist/orchestrator/ai-skills/golden-skills.test.js +29 -0
- package/dist/orchestrator/ai-skills/golden-skills.test.js.map +1 -1
- package/dist/orchestrator/flow-manager.d.ts +1 -0
- package/dist/orchestrator/flow-manager.d.ts.map +1 -1
- package/dist/orchestrator/flow-manager.js +52 -3
- package/dist/orchestrator/flow-manager.js.map +1 -1
- package/dist/orchestrator/screen-manager.d.ts.map +1 -1
- package/dist/orchestrator/screen-manager.js +52 -4
- package/dist/orchestrator/screen-manager.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/add-flow.md +26 -4
- package/dist/orchestrator/templates/ai-src/commands/create-test.md +22 -30
- package/dist/orchestrator/templates/ai-src/config/claude.md +20 -7
- package/dist/orchestrator/templates/ai-src/config/copilot.md +20 -7
- package/dist/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +1 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-capture-mobile/SKILL.md +3 -3
- package/dist/orchestrator/templates/ai-src/skills/sungen-data-factory/SKILL.md +9 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +2 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +5 -4
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +24 -8
- package/dist/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +1 -3
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +5 -4
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +0 -18
- package/dist/orchestrator/templates/specs-test-data.ts +22 -4
- package/dist/orchestrator/viewpoint-pattern-catalog.d.ts +1 -4
- package/dist/orchestrator/viewpoint-pattern-catalog.d.ts.map +1 -1
- package/dist/orchestrator/viewpoint-pattern-catalog.js +2 -5
- package/dist/orchestrator/viewpoint-pattern-catalog.js.map +1 -1
- package/package.json +3 -3
- package/src/cli/commands/audit.ts +3 -26
- package/src/cli/index.ts +0 -2
- package/src/exporters/test-data-resolver.ts +10 -2
- package/src/generators/gherkin-parser/index.ts +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/contain-text-assertion.hbs +2 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/expanded-assertion.hbs +2 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/date-picker-unsupported.hbs +5 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/fill-action.hbs +13 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +9 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/expanded-assertion.hbs +12 -0
- package/src/generators/test-generator/code-generator.ts +4 -4
- package/src/generators/test-generator/diagnostics.ts +9 -3
- package/src/generators/test-generator/patterns/types.ts +2 -0
- package/src/generators/test-generator/step-mapper.ts +56 -9
- package/src/generators/test-generator/utils/data-resolver.ts +53 -0
- package/src/generators/test-generator/utils/selector-resolver.ts +10 -2
- package/src/harness/audit.ts +2 -13
- package/src/harness/next-step.ts +1 -17
- package/src/harness/parse.ts +5 -13
- package/src/harness/sensors.ts +8 -9
- package/src/harness/viewpoint-baseline.ts +1 -7
- package/src/harness/viewpoint-ledger.ts +11 -52
- package/src/index.ts +2 -1
- package/src/orchestrator/ai-skills/commands.ts +5 -3
- package/src/orchestrator/ai-skills/golden-skills.test.ts +23 -0
- package/src/orchestrator/flow-manager.ts +53 -3
- package/src/orchestrator/screen-manager.ts +52 -4
- package/src/orchestrator/templates/ai-src/commands/add-flow.md +26 -4
- package/src/orchestrator/templates/ai-src/commands/create-test.md +22 -30
- package/src/orchestrator/templates/ai-src/config/claude.md +20 -7
- package/src/orchestrator/templates/ai-src/config/copilot.md +20 -7
- package/src/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +1 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-capture-mobile/SKILL.md +3 -3
- package/src/orchestrator/templates/ai-src/skills/sungen-data-factory/SKILL.md +9 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +2 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +5 -4
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +24 -8
- package/src/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +1 -3
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +5 -4
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +0 -18
- package/src/orchestrator/templates/specs-test-data.ts +22 -4
- package/src/orchestrator/viewpoint-pattern-catalog.ts +2 -5
- package/dist/cli/commands/viewpoint.d.ts +0 -7
- package/dist/cli/commands/viewpoint.d.ts.map +0 -1
- package/dist/cli/commands/viewpoint.js +0 -70
- package/dist/cli/commands/viewpoint.js.map +0 -1
- package/dist/harness/viewpoint-draft-header.d.ts +0 -7
- package/dist/harness/viewpoint-draft-header.d.ts.map +0 -1
- package/dist/harness/viewpoint-draft-header.js +0 -100
- package/dist/harness/viewpoint-draft-header.js.map +0 -1
- package/dist/harness/viewpoint-mode.d.ts +0 -9
- package/dist/harness/viewpoint-mode.d.ts.map +0 -1
- package/dist/harness/viewpoint-mode.js +0 -52
- package/dist/harness/viewpoint-mode.js.map +0 -1
- package/dist/harness/viewpoint-priority.d.ts +0 -19
- package/dist/harness/viewpoint-priority.d.ts.map +0 -1
- package/dist/harness/viewpoint-priority.js +0 -36
- package/dist/harness/viewpoint-priority.js.map +0 -1
- package/dist/harness/viewpoint-review-open.d.ts +0 -6
- package/dist/harness/viewpoint-review-open.d.ts.map +0 -1
- package/dist/harness/viewpoint-review-open.js +0 -90
- package/dist/harness/viewpoint-review-open.js.map +0 -1
- package/dist/harness/viewpoint-scan-refs.d.ts +0 -50
- package/dist/harness/viewpoint-scan-refs.d.ts.map +0 -1
- package/dist/harness/viewpoint-scan-refs.js +0 -200
- package/dist/harness/viewpoint-scan-refs.js.map +0 -1
- package/dist/harness/viewpoint-scan.d.ts +0 -41
- package/dist/harness/viewpoint-scan.d.ts.map +0 -1
- package/dist/harness/viewpoint-scan.js +0 -118
- package/dist/harness/viewpoint-scan.js.map +0 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/review-checklist.md +0 -63
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/viewpoint-template.md +0 -158
- package/dist/orchestrator/viewpoint-scaffold.d.ts +0 -3
- package/dist/orchestrator/viewpoint-scaffold.d.ts.map +0 -1
- package/dist/orchestrator/viewpoint-scaffold.js +0 -70
- package/dist/orchestrator/viewpoint-scaffold.js.map +0 -1
- package/src/cli/commands/viewpoint.ts +0 -58
- package/src/harness/viewpoint-draft-header.ts +0 -55
- package/src/harness/viewpoint-mode.ts +0 -49
- package/src/harness/viewpoint-priority.ts +0 -38
- package/src/harness/viewpoint-review-open.ts +0 -51
- package/src/harness/viewpoint-scan-refs.ts +0 -154
- package/src/harness/viewpoint-scan.ts +0 -111
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/review-checklist.md +0 -63
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/viewpoint-template.md +0 -158
- package/src/orchestrator/viewpoint-scaffold.ts +0 -36
|
@@ -12,32 +12,45 @@ You generate 3 files for sungen — a Gherkin compiler that produces Playwright
|
|
|
12
12
|
| `sungen-tc-generation` | Test case generation strategy, output format |
|
|
13
13
|
| `sungen-test-design-techniques` | EP, BVA, Decision Table, State Transition — systematic scenario generation |
|
|
14
14
|
| `sungen-tc-review` | Review scoring, quality rules, checklist |
|
|
15
|
-
| `sungen-viewpoint` |
|
|
15
|
+
| `sungen-viewpoint` | 32 UI patterns (17 web + 15 mobile-native) x 4 viewpoints — coverage checklists |
|
|
16
16
|
| `sungen-selector-keys` | YAML key generation from `[Reference]` names, suffixes, lookup priority |
|
|
17
17
|
| `sungen-selector-fix` | Selector generation from live page, auto-fix strategy |
|
|
18
|
-
| `sungen-delivery` | Export
|
|
18
|
+
| `sungen-delivery` | Export the Test Case & Coverage Matrix (or the legacy per-scenario CSV) from Gherkin + Playwright results |
|
|
19
19
|
| `sungen-capture` | Acquire visual/design context — one skill, 4 modes: figma-mcp (Dev Mode MCP), figma-pat (URL → spec_figma.md), live (Playwright MCP scan), local (images in `requirements/ui/`) |
|
|
20
20
|
| `sungen-locale` | Bootstrap i18n — audit selectors, detect locale switch mechanism, generate test-data overlay |
|
|
21
21
|
| `sungen-data-factory` | Standardized test-data — field-type/security catalog, 4-source method, `.overwrite`, blind-spot prompts |
|
|
22
|
+
| `sungen-harness-audit` | How to read `sungen audit` output and repair test-design findings (the create-test quality loop) |
|
|
23
|
+
| `sungen-capture-mobile` | Capture a live mobile screen via Appium MCP — locator tree + screenshot (`@platform:mobile/android/ios`) |
|
|
24
|
+
| `sungen-mobile-gestures` | Mobile gesture patterns (swipe, long-press, scroll-to, pinch, pull-to-refresh) — Gherkin + Appium MCP mapping |
|
|
25
|
+
| `sungen-selector-fix-mobile` | Mobile selector fixing — Appium MCP exploration, accessibility-id-first strategy, run via wdio |
|
|
26
|
+
| `sungen-api-design` | API-first design loop for an api unit — catalog, API viewpoints, `@api`/`@cases`/flow scenarios, audit gate + repair |
|
|
27
|
+
| `sungen-api-coverage-model` | Per-endpoint API coverage model — tiered case set expanded from the catalog's `fields:` metadata |
|
|
28
|
+
| `sungen-ingest-legacy` | Import a legacy manual test-case suite (Google Sheets or local file) via `sungen ingest` |
|
|
29
|
+
| `sungen-dashboard` | Single-file HTML dashboard of test cases + results (`sungen dashboard`) |
|
|
22
30
|
|
|
23
|
-
## Workflow (
|
|
31
|
+
## Workflow (12 AI commands)
|
|
24
32
|
|
|
25
33
|
| Command | What it does |
|
|
26
34
|
|---|---|
|
|
27
35
|
| `/sungen-add-screen <name> <path>` | Scaffold `qa/screens/<name>/` directories |
|
|
28
36
|
| `/sungen-add-flow <name> [--path <url>]` | Scaffold `qa/flows/<name>/` directories for E2E cross-screen testing |
|
|
29
|
-
| `/sungen-
|
|
37
|
+
| `/sungen-add-api <name> [--base-url <url>] [--flow <name>] [--import <spec>]` | Scaffold an API area (`qa/api/<name>/`), or an ordered `@api` chain with `--flow` (`qa/api/flows/<name>/`); `--base-url` is needed only until the project has an API datasource; `--import` an OpenAPI/CSV spec to populate the endpoint catalog |
|
|
38
|
+
| `/sungen-create-test <name>` | Generate `.feature` + `test-data.yaml`, then run the quality harness (auto-detects screen / flow / API) |
|
|
39
|
+
| `/sungen-design <name>` | Alias of `create-test` |
|
|
30
40
|
| `/sungen-create-data-test <name>` | Generate standardized test-data (valid/boundary/invalid + CHK trace) from the Data Factory catalog; no name = all units (asks to confirm) |
|
|
31
41
|
| `/sungen-review <name>` | Score syntax, coverage, viewpoint quality (auto-detects screen or flow) |
|
|
32
|
-
| `/sungen-run-test <name>` | Generate `selectors.yaml`, compile, run, auto-fix (auto-detects screen
|
|
33
|
-
| `/sungen-delivery [name...]` | Export the Test Case & Coverage Matrix (review/manual/customer deliverable; `--legacy` = classic CSV; all
|
|
42
|
+
| `/sungen-run-test <name>` | Generate `selectors.yaml`, compile, run, auto-fix (auto-detects screen / flow / API) |
|
|
43
|
+
| `/sungen-delivery [name...]` | Export the Test Case & Coverage Matrix (review/manual/customer deliverable; `--legacy` = classic CSV; all units if no arg) |
|
|
44
|
+
| `/sungen-dashboard [name...]` | Build the single-file HTML dashboard (`qa/dashboard/index.html`) — results, trends, run compare, CSV/XLSX export |
|
|
34
45
|
| `/sungen-locale <name> <locale>` | Bootstrap i18n for a screen — audit selectors, detect locale switch, generate overlay (run before `/sungen-run-test --env <locale>`) |
|
|
46
|
+
| `/sungen-feedback [message]` | Record QA feedback locally (`.sungen/feedback/`) with context attached |
|
|
35
47
|
|
|
36
48
|
**Screen path:** add-screen → create-test → review → run-test → delivery.
|
|
37
49
|
**Flow path:** add-flow → create-test → review → run-test → delivery.
|
|
50
|
+
**API path:** add-api (`--import <openapi|csv>` for accuracy) → create-test → run-test → delivery. API units carry no selectors and no visual capture; create-test runs the audit + reviewer loop itself.
|
|
38
51
|
**i18n path:** (after run-test passes for base locale) → locale → run-test --env <locale> → delivery --env <locale>.
|
|
39
52
|
|
|
40
|
-
`create-test
|
|
53
|
+
`create-test` and `run-test` auto-detect context: if `qa/api/<name>/` (or `qa/api/flows/<name>/`) exists → API mode, else if `qa/flows/<name>/` → flow mode, else `qa/screens/<name>/` → screen mode. `review` covers screen and flow units.
|
|
41
54
|
|
|
42
55
|
After each command completes, present the next actions as selectable options. Never just print text — always give clickable choices so the user can continue the workflow seamlessly.
|
|
43
56
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sungen-api-coverage-model
|
|
3
|
-
description: The per-endpoint API coverage model — the systematic case set to enumerate for each endpoint (read / mutating / flow), keyed to the catalog's fields: metadata, tiered so a first pass is complete (not "happy path + 1 error"). Load this in API unit mode alongside sungen-api-design; it turns endpoints × field constraints into a concrete case list, mechanically, instead of guessing.
|
|
3
|
+
description: 'The per-endpoint API coverage model — the systematic case set to enumerate for each endpoint (read / mutating / flow), keyed to the catalog''s fields: metadata, tiered so a first pass is complete (not "happy path + 1 error"). Load this in API unit mode alongside sungen-api-design; it turns endpoints × field constraints into a concrete case list, mechanically, instead of guessing.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# API coverage model (what to test per endpoint)
|
|
@@ -6,7 +6,7 @@ user-invocable: false
|
|
|
6
6
|
|
|
7
7
|
## Purpose
|
|
8
8
|
|
|
9
|
-
Launch a mobile app on a device/emulator, capture **one locator tree** (`generate_locators`) and **one screenshot**, and save them as visual context for test generation. The mobile analogue of `sungen-capture
|
|
9
|
+
Launch a mobile app on a device/emulator, capture **one locator tree** (`generate_locators`) and **one screenshot**, and save them as visual context for test generation. The mobile analogue of `sungen-capture` mode `live` — same job, Appium MCP instead of Playwright MCP.
|
|
10
10
|
|
|
11
11
|
Use when the target is a native/Flutter/React-Native app (`@platform:android` or `@platform:ios`) running on a connected device or emulator.
|
|
12
12
|
|
|
@@ -188,8 +188,8 @@ Flutter/RN expose the same Semantics on each, so one exploration covers most of
|
|
|
188
188
|
|
|
189
189
|
## Relationship to other capture skills
|
|
190
190
|
|
|
191
|
-
- `sungen-capture-figma` / `
|
|
192
|
-
- `sungen-capture
|
|
191
|
+
- `sungen-capture` (modes `figma-mcp` / `figma-pat` / `local`) — design/image sources (platform-agnostic)
|
|
192
|
+
- `sungen-capture` (mode `live`) — web (Playwright MCP)
|
|
193
193
|
- `sungen-capture-mobile` — this skill, mobile (Appium MCP)
|
|
194
194
|
|
|
195
195
|
All write to `requirements/ui/` and report back to the caller.
|
|
@@ -69,6 +69,15 @@ Rules:
|
|
|
69
69
|
catalog defaults).
|
|
70
70
|
- Map every `errorRef` placeholder (`M13`, `M39`, …) to the spec's real code via `errorMap`. Leave
|
|
71
71
|
one unmapped **only** if the spec has none — the lint flags it as an open point to confirm.
|
|
72
|
+
- Some catalog constraints are only placeholders (string `minLength: 1` / `maxLength: 255`, phone
|
|
73
|
+
`8..15`, …). A field that does not set its own value is warned **`PLACEHOLDER_CONSTRAINT`** — the
|
|
74
|
+
boundary cases are built from that number, so take it from the spec.
|
|
75
|
+
- An **empty value** is judged by `required`, not by `minLength`: `required: false` → `""` is valid
|
|
76
|
+
(no "below minimum" case for it); `required: true` **or unset** → `""` is invalid. `minLength: 0`
|
|
77
|
+
has no min-1 case. So an optional field must say `required: false`.
|
|
78
|
+
- To drop **one** generated case for **one** field, list its id under the field's **`disable:`**
|
|
79
|
+
(e.g. `disable: [CHK-STR-I2]` drops min-1 and keeps max+1 `CHK-STR-I2a`). It is reported as
|
|
80
|
+
not applicable. The overwrite's `disable:` drops a case for every field of the type.
|
|
72
81
|
- **Complex forms:** a **group** field takes nested **`fields:`** (recursive; no `type` on the group → renders a nested object like `address.city`). A field whose rule depends on another takes **`dependsOn: { field, value }`** — recorded as a `_depends_on` note (the generator doesn't resolve the runtime condition; weave it into the scenario/`@cases`).
|
|
73
82
|
- Set **`unique: true`** on a field that must be fresh every run (register email/username, anything
|
|
74
83
|
that would clash with a record a previous run left behind). Its valid value then carries a runtime
|
|
@@ -126,7 +126,8 @@ exist in test-data, so Gate D does not ask for them: the capture step renders as
|
|
|
126
126
|
are resolved by TestDataLoader at execution time — including when nested inside a test-data value
|
|
127
127
|
(`seed: "Summary intro {{$timestamp}}"`). They have no static value to print, so the report names
|
|
128
128
|
them instead: `<unique timestamp>`, `<unique id>`, `<random 1-100>`, `<today's date>`,
|
|
129
|
-
`<current date-time
|
|
129
|
+
`<current date-time>`, and `{{$date:+1d}}` reads `<today's date + 1 day>`. An unknown `{{$foo}}` (or an
|
|
130
|
+
argument the runtime does not support, e.g. `{{$date:tz=…}}`) is left raw on purpose — it is a typo and Gate D says so.
|
|
130
131
|
|
|
131
132
|
**Data fidelity**: invisible test data is made visible, never normalized — `''` → `(empty)`,
|
|
132
133
|
whitespace-only → `(5 spaces)`, padded → `" value "` quoted verbatim. A trim/collapse here would
|
|
@@ -124,9 +124,9 @@ reason comment); the Given/When/Then stay **exactly** as QA approved them:
|
|
|
124
124
|
| `SG-W012` | A mock-install step written AFTER a navigation step in the same block — `page.route()` registered after `goto()` misses every request fired during page load | Move the mock-install step before the navigation, or into `Background` |
|
|
125
125
|
| `SG-W013` | A page step (`see [X] page` / `is on [X] page` / `wait for [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 match can never reach it: the assertion always fails, the wait can only time out | 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) |
|
|
126
126
|
| `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 |
|
|
127
|
-
| `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 |
|
|
127
|
+
| `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. In a `@cases` scenario the value is judged on every dataset row; it also fires on `see [X] row with {{n}}` whose rows MIX numbers and text (a row count needs every row numeric, so the step stays a text assertion) | For mixed `@cases` rows: put the counting rows in their own `@cases` scenario. 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 |
|
|
128
128
|
| `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 |
|
|
129
|
-
| `SG-W017` | A step needs a **selector-entry field the entry does not declare**, so there is nothing honest to compile — today
|
|
129
|
+
| `SG-W017` | A step needs a **selector-entry field the entry does not declare**, so there is nothing honest to compile — today: a table paginate step whose table entry declares no `pagination.next`/`.prev` (#712); a web `fill [X] date-picker` whose entry locates a `<button>` (a calendar trigger) and names no fill recipe (#907). The mirror of `SG-W004`: that one reports a field written that nothing reads, this one a field a consumer needs that nobody wrote. The step compiles to a loud runtime `throw` rather than guessing a locator, which is the #690 failure mode (a locator no page has, failing far from its cause) | Add the missing field to the entry in selectors.yaml — for pagination: `pagination:` with `next:`/`prev:` locators; for a button date-picker: `variant: <recipe>` naming a `qa/app.yaml` recipe with a `fill` intent (and `assertValue` to read it back). This is a selectors.yaml-only fix, allowed inside the run-test auto-fix loop |
|
|
130
130
|
| `SG-W018` | A selectors.yaml entry declares an **`nth:` outside the contract** — `nth: -2`, `nth: 'last'`, `nth: 1.5`. `nth:` was the one entry field whose VALUE nothing checked (issue #720), so the web adapter silently DROPPED the filter (identical to deleting the line — the step then hits `strict mode violation: resolved to N elements`, which names the DOM and never the YAML line) while the mobile adapter emitted `$$('~x')[-1]` / `[last]` — `undefined` indexing and a bare identifier. The value is now ignored and reported once per entry | Write an integer `>= 0` for a zero-based index, or `-1` for the last match (`.last()` on web, `.at(-1)` on mobile). There is no keyword form: `'last'` is a string, not an index. This is a selectors.yaml-only fix — allowed inside the run-test auto-fix loop |
|
|
131
131
|
| `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 |
|
|
132
132
|
| `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 |
|
|
@@ -139,7 +139,7 @@ reason comment); the Given/When/Then stay **exactly** as QA approved them:
|
|
|
139
139
|
| `SG-W028` | A trailing **scope clause was DROPPED**, so the step runs against more of the page than its sentence names. Two causes, one code: (1) the word after `in [X]` is not a container type — `in [Job Card] widget`, or a leaf element used as a parent — so the clause matched nothing and the first-`[ref]`/first-`{{data}}` extractors took only the head of the sentence; (2) the by-content form `in [X] <type> with {{v}}` on the **mobile** adapter, where a parent is chained with `$()` and has no content filter. Before this code, both compiled clean and silently widened the assertion to the WHOLE screen — in a list of look-alike cards the test then passes for the wrong reason, findable only by diffing the generated `.spec.ts` against the Gherkin (#667) | For (1) use a real container type: `table` `list` `list-item` `section` `dialog` `modal` `drawer` `overlay` `form` `card` `item` `row` `group` `panel` `region` `container` `tab` `menu`. For (2) give the container an accessibility-id that identifies it and scope by that, or assert on the element the value belongs to |
|
|
140
140
|
| `SG-W029` | A **mobile feature with no usable app-package `Path:`** — the line is missing, or it holds a web-style route (`Path: /applied-jobs`). The compiler then cannot name the app to reset between scenarios, and before this code that was SILENT: the per-scenario `beforeEach` was not emitted at all, so Background fell to Mocha's `before()` — once per FILE. Every scenario shared one continuous app session, so a tab switch or a scroll in one stranded elements off-screen for every later one, and the `@reset:` tier ladder was dropped with it, leaving the tag inert (#669). The spec now always emits the reset machinery and resolves the app id from the session capability instead, throwing a named error at run time if nothing supplies one | Add the package to the feature: `Path: com.example.app/.MainActivity`, or `Path: <androidPkg>/<activity> | <iosBundleId>` for a dual-id feature. A `Path:` starting with `/` is a web route and will not be read as a package. Failing that, set `APP_PACKAGE` (Android) / `IOS_BUNDLE_ID` (iOS) so `wdio.conf` puts the id on the session |
|
|
141
141
|
| `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 |
|
|
142
|
-
| `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 |
|
|
142
|
+
| `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. Also fires on a trailing `is <word>` / `is not <word>` that is no state (`is on`, `is open`, `is active`, `is not visible`) — only `is visible`/`displayed`/`shown`/`present` and `is not hidden` are what a visibility check asserts | For an `is <word>` ending: use a state from the `sungen-gherkin-syntax` States list (`is expanded`/`is collapsed` for a disclosure), or `@manual`. Otherwise, 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 |
|
|
143
143
|
| `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 |
|
|
144
144
|
| `SG-E025` | A feature tagged `@platform:ios` uses a step the Appium adapter does **not** serve on iOS — the template either throws deep inside a device run (`set-connectivity`, `location-services`, `open-notifications`) or silently does nothing (`hide-keyboard`, a guarded `if (!driver.isIOS …)`). Derived from the template body via the per-OS axis of the step-support matrix, so it cannot disagree with the adapter. Only an explicit `@platform:ios` is gated (`@platform:mobile` picks its OS at run time). Compiles to a loud `throw` | Use a step the matrix marks ✅ for iOS (see `docs/spec/step-support-matrix.md`), tag the feature `@platform:mobile` if it should run on either OS, or tag the scenario `@manual` |
|
|
145
145
|
| `SG-E026` | A **click step names a position** (`at position "top-left"`, `at offset x=…, y=…`) that would not be honoured: the keyword is misspelt or unquoted, a value is outside the grammar (not a number, negative, above 100%, a missing axis, an unknown preset), text follows the position, or another click form claimed the step first (`try to click`, a `with {{v}}` text filter, `… and accept [OK] alert`, `double click`). Each of those would compile to a click on the element's CENTRE and pass — for a backdrop-dismiss overlay that is the one outcome that proves nothing — so the step compiles to a loud `throw` | Write `click [X] <type> at position "top-left\|top-right\|bottom-left\|bottom-right"` or `… at offset x=<n>px\|%, y=<n>px\|%` as the LAST thing in the step, on a plain `click` (no `try to`, no `with {{v}}`, no alert clause). Web only — on mobile tap the control the position targets |
|
|
@@ -175,7 +175,8 @@ The scenario uses a SECRET test-data key whose `QA_*` variable is missing in thi
|
|
|
175
175
|
|
|
176
176
|
Sungen picks assertion based on element type:
|
|
177
177
|
- **Input** (`field`, `textarea`, `search`, `slider`) → `toHaveValue()`
|
|
178
|
-
- **Dropdown** (`dropdown`, `select`, `combobox`) → runtime tag probe: `toHaveValue()` on a native `<select>`/input, `toHaveText()` on a `<button role="combobox">` trigger (Radix/shadcn, MUI). Pin one branch with `variant: native` or an `assertValue` recipe in `qa/app.yaml`.
|
|
178
|
+
- **Dropdown / date-picker** (`dropdown`, `select`, `combobox`, `date-picker`) → the `assertValue` recipe when the entry's `variant:` (or `widgets.select` / `widgets.datepicker`) names one; otherwise a runtime tag probe: `toHaveValue()` on a native `<select>`/input, `toHaveText()` on a `<button role="combobox">` trigger (Radix/shadcn, MUI) or a `<button>` that opens a calendar popover. Pin one branch with `variant: native` or an `assertValue` recipe in `qa/app.yaml`. Any other element with a `variant:` uses its recipe's `assertValue` too.
|
|
179
|
+
- **`SG-W017` on a date-picker fill** (the entry is provably a button: `role: button`, or a css/locator ending in a `button` tag) and, at run time, **`[sungen] [X] is not a fillable element`** (a trigger the compiler could not prove is a button) → the fill target is a calendar / popup trigger. Declare a recipe with `fill` (and `assertValue`) intents in `qa/app.yaml` and set `variant: <recipe>` on the entry, or `widgets.datepicker` for every date-picker. Do not change the step to a click.
|
|
179
180
|
- **Text** (everything else: `message`, `heading`, `label`, `row`) → `toHaveText()`
|
|
180
181
|
- **Partial** (`contains` keyword) → `toContainText()`
|
|
181
182
|
|
|
@@ -37,7 +37,7 @@ AND → inherits from preceding keyword
|
|
|
37
37
|
|
|
38
38
|
```
|
|
39
39
|
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"
|
|
40
|
-
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
|
+
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; on web a <button> trigger (calendar popover) needs a fill recipe (variant: / widgets.datepicker) or it is refused (SG-W017)
|
|
41
41
|
User fill [T] uploader with {{f}} [web]
|
|
42
42
|
User clear [T] field
|
|
43
43
|
User check [T] checkbox | toggle | radio
|
|
@@ -145,14 +145,20 @@ two content-filtered queries can hit different rows if the table re-renders in b
|
|
|
145
145
|
```
|
|
146
146
|
# 1. Visibility: User see [T] type (NEVER add "is visible") | is hidden
|
|
147
147
|
# 2. Text (toHaveText): User see [T] message | header | label with {{v}}
|
|
148
|
-
# `label` forks on the entry: declared by IDENTITY (xpath/accessibility-id/testid/id/css)
|
|
149
|
-
# →
|
|
150
|
-
# or `type: text` → matches
|
|
148
|
+
# `label` forks on the entry: declared by IDENTITY (xpath/accessibility-id/testid/id/css/locator)
|
|
149
|
+
# → toContainText on THAT element; `exact: true` → toHaveText, and an EMPTY {{v}} asserts the
|
|
150
|
+
# element shows no text (contains '' would pass on anything); no entry, or `type: text` → matches
|
|
151
|
+
# the caption+value text pair anywhere on screen
|
|
151
152
|
# 3. Partial (toContainText): User see [T] text contains {{v}}
|
|
152
153
|
# 3b. Format (shape, not value): User see [T] label matches "yyyy/mm/dd ~ yyyy/mm/dd"
|
|
153
|
-
# 4. Input (toHaveValue): User see [T] field |
|
|
154
|
-
# dropdown | select | combobox →
|
|
154
|
+
# 4. Input (toHaveValue): User see [T] field | search | slider with {{v}}
|
|
155
|
+
# dropdown | select | combobox | date-picker → the entry's `assertValue` recipe if it has one
|
|
156
|
+
# (variant: / widgets.select / widgets.datepicker), else the tag is probed at runtime
|
|
157
|
+
# (toHaveValue on a native input/select, toHaveText on a custom trigger such as a <button>).
|
|
158
|
+
# Any element whose entry has a `variant:` reads back through that recipe's `assertValue`
|
|
155
159
|
# 5. State: User see [T] button is disabled | checkbox is checked | dialog with {{v}} is hidden
|
|
160
|
+
# open/closed: User see [T] button | section is expanded | is collapsed [web — reads `expanded:`, else aria-expanded;
|
|
161
|
+
# an entry with `expanded: { state: none }` has no state to read → refused, SG-W017]
|
|
156
162
|
# 6. Attribute (toHaveAttribute): User see [T] image | link with {{v}}
|
|
157
163
|
# WHICH attribute comes from the entry's `attribute:` — any name, not just href/src
|
|
158
164
|
# 7. Count: User see [T] row with {{count}}
|
|
@@ -501,7 +507,11 @@ zero-match assertion failure, not a platform gap.
|
|
|
501
507
|
|
|
502
508
|
### States
|
|
503
509
|
|
|
504
|
-
`hidden` `visible` `disabled` `enabled` `checked` `unchecked` `focused` `empty` `loading` `selected` `sorted ascending` `sorted descending`
|
|
510
|
+
`hidden` `visible` `disabled` `enabled` `checked` `unchecked` `focused` `empty` `loading` `selected` `expanded` `collapsed` `sorted ascending` `sorted descending`
|
|
511
|
+
|
|
512
|
+
Any other trailing `is <word>` / `is not <word>` (`is on`, `is active`, `is open`, `is not visible`) is
|
|
513
|
+
**not** a state: the compiler refuses it (SG-E023) rather than check only visibility. `is displayed` /
|
|
514
|
+
`is shown` / `is present` read as `is visible`, and `is not hidden` as visible too.
|
|
505
515
|
|
|
506
516
|
### Element Types
|
|
507
517
|
|
|
@@ -539,12 +549,18 @@ Use `{{$var}}` in test-data YAML for values that must be unique per test run. Re
|
|
|
539
549
|
|
|
540
550
|
| Variable | Example | Output |
|
|
541
551
|
|---|---|---|
|
|
542
|
-
| `{{$timestamp}}` | `"User-{{$timestamp}}"` | `"User-
|
|
552
|
+
| `{{$timestamp}}` | `"User-{{$timestamp}}"` | `"User-1714000000000"` (milliseconds) |
|
|
543
553
|
| `{{$uuid}}` | `"{{$uuid}}"` | `"a1b2c3d4-..."` |
|
|
544
554
|
| `{{$random:min:max}}` | `"{{$random:1:100}}"` | `"42"` |
|
|
545
555
|
| `{{$date}}` | `"{{$date}}"` | `"2026-04-24"` |
|
|
556
|
+
| `{{$date:+Nd}}` / `{{$date:-Nd}}` | `"{{$date:+1d}}"` | `"2026-04-25"` — today shifted by N days |
|
|
546
557
|
| `{{$datetime}}` | `"{{$datetime}}"` | `"2026-04-24T10:30:00.000Z"` |
|
|
547
558
|
|
|
559
|
+
`{{$date}}` is the LOCAL calendar date of the machine running the tests — the "today" the browser shows
|
|
560
|
+
when `playwright.config.ts` sets no `timezoneId`. If the config pins `timezoneId`, export the same zone as
|
|
561
|
+
`TZ` (e.g. `TZ=Asia/Tokyo npx playwright test`) so both agree. `{{$datetime}}` is a UTC instant (`…Z`). Any `{{$date:…}}` argument other than `±Nd`
|
|
562
|
+
(`tz=…`, a format) and any `{{$datetime:…}}` argument are not supported and stay literal.
|
|
563
|
+
|
|
548
564
|
**Rules:**
|
|
549
565
|
- `$timestamp` and `$uuid` → same value across all keys in one `load()` call (stable within a test file)
|
|
550
566
|
- `$random` → unique per occurrence (each key gets a different random)
|
|
@@ -38,8 +38,6 @@ user-invocable: false
|
|
|
38
38
|
| **DEFECT-REF-MISSING** | a `@known-defect:DEF-NNN` tag names no file in `qa/defects/` | Draft the record (`sungen defect draft --screen <name> --scenario "<title>"`) or correct the id. Never drop the tag to silence it — the scenario still asserts a live defect. |
|
|
39
39
|
| **DEFECT-ORPHAN** (info) | an open defect record names a scenario this unit no longer has | Update the record's `scenarios:` to the renamed title, or close the record if the scenario was removed on purpose. Nothing in the suite to repair. |
|
|
40
40
|
| **UNIVERSAL** | a universal theme (error/empty-state, accessibility) is absent | Low priority — add if in scope; otherwise note as out-of-scope with reason. |
|
|
41
|
-
| **VIEWPOINT-DRAFT-UNREVIEWED** | `test-viewpoint.md` carries `drafted-by: ai`: no baseline, `atomicLedger` + `traceability` held | A QA action, not a repair. Keep generating; list the decision `sungen audit --screen <name> --accept-viewpoint` in the hand-back for the QA to run after reviewing the draft. Never run it, never delete the line yourself. |
|
|
42
|
-
| **VIEWPOINT-REVIEW-OPEN** (info) | `requirements/viewpoint-review.md` still has `[ ]` rows | A QA action. Report the count in the hand-back with the option to open the file; never tick rows or edit a QA-written viewpoint to clear it. Not scored. |
|
|
43
41
|
|
|
44
42
|
## P5 steps for deep cross-screen / list coverage
|
|
45
43
|
|
|
@@ -68,7 +66,7 @@ Use these when repairing GATE/DEPTH findings for the hard viewpoints (cart/detai
|
|
|
68
66
|
## Repair loop rules
|
|
69
67
|
|
|
70
68
|
1. **Budget = 3 rounds.** Re-run `sungen audit` after each repair; track score delta.
|
|
71
|
-
2. **Stop when** `gateStatus == PASS` AND `findings` empty — or budget exhausted.
|
|
69
|
+
2. **Stop when** `gateStatus == PASS` AND `findings` empty — or budget exhausted.
|
|
72
70
|
3. **Never fake a pass.** A shallow `see [Cart] page` does not satisfy `cart-correctness`. If a gap is genuinely cross-screen or needs capabilities the DSL lacks (e.g. capture an element value to compare elsewhere), **report it as a residual gap / flow item** instead of forcing a green gate.
|
|
73
71
|
4. **EP/data families are OK.** A `duplicates` cluster with `sameDataLikely=false` is an intentional equivalence-partition family (e.g. many invalid-email cases) — keep it; only collapse `sameDataLikely=true` exact duplicates.
|
|
74
72
|
5. **Advisory findings — surface, don't gate.** `MANUAL-REASON-MISMATCH` → fix the scenario's `@manual:Mx` code (so the planner recommends the right driver) during repair. `CAPABILITY-SUGGESTION` → **present it to the user as a next-step option** (e.g. "N @manual could be automated — `sungen capability add api db`?"), **recommend-only — never auto-install**. `VERIFICATION-OUT-OF-SCOPE (api|db)` → the unit uses `@api`/`@query`/`@requires:api|db` verification the project's recorded **test type** never opted into (E2E/UI-only per `capabilities.yaml`, and the viewpoint doesn't ask for it). During repair: **rewrite the oracle to be UI-observable** (or downgrade the deep check to an `(optional deep check: …)` note); only keep the API/DB verification if the project genuinely tests that layer — then record it (`verification: [ui, api]` / `sungen capability add`). Keep API/DB-in-E2E to the ≤20% band. Neither of these fails the gate.
|
|
@@ -385,7 +385,7 @@ Security: [S1 – admin only]
|
|
|
385
385
|
- **`--accept-viewpoint` is the QA's command, not yours.** `sungen next` shows it as a `⚖ decide`
|
|
386
386
|
step. Present the declared-id diff and stop; a yardstick accepted by the process it measures
|
|
387
387
|
reads 100% by construction.
|
|
388
|
-
- **Match the scenarios to the file — never the file to the scenarios.** A filled `test-viewpoint.md` is an input; do not rewrite its declarations to fit what you generated. That is not compliance, it is moving the yardstick: `traceability` + `atomicLedger` then read 100% by construction and a dropped viewpoint stops being missing from anything. Disagree with the taxonomy →
|
|
388
|
+
- **Match the scenarios to the file — never the file to the scenarios.** A filled `test-viewpoint.md` is an input; do not rewrite its declarations to fit what you generated. That is not compliance, it is moving the yardstick: `traceability` + `atomicLedger` then read 100% by construction and a dropped viewpoint stops being missing from anything. Disagree with the taxonomy → propose the diff and ask. `sungen audit` reports `VIEWPOINT-BASELINE-CHANGED` and excludes both axes until a human accepts the change (`sungen audit --screen <name> --accept-viewpoint`).
|
|
389
389
|
2. **Spec-coverage triggers** (`TRIGGER-UNCOVERED`, gate-FAIL) — the Validation-Rules table lists a **trigger** per constraint (e.g. `blur, submit`). Generate one scenario **per (constraint × trigger)** — a `format` rule validating *on blur AND on submit* needs BOTH a blur scenario (`press Tab`) and a submit scenario (`click [Submit]` / `press Enter`). Never collapse the trigger × input matrix to one representative case.
|
|
390
390
|
3. **Claim-Proof** (`CLAIM-UNPROVEN`) — a title claiming `all`/`only`/`every`/`single`/`correct`/`same`/`changes`/`hidden`/`cleared`/`restored`/`independent`/`sanitized`/`announces` MUST have the matching assertion (`see all …`, count, `remember`+compare, `is hidden`, return-and-assert-empty, etc.). If the title promises it, the steps must prove it.
|
|
391
391
|
- **Negative / absence claims** (`does not` / `no` / `never` / `prevents` / `không` / `chưa` — any language; `no-side-effect/no-duplicate`, `negative-claim/absence`): the `Then` must **differ** between the claim holding and not holding. A terminal `see [X] page` that looks identical whether or not the bad thing happened proves nothing. For a side-effect that should NOT repeat (re-submit on back, re-charge, duplicate order, resend OTP), assert the **count is unchanged** (`User see [Records] table with {{one}}` / `row with {{count}}`); if it's not UI-observable, mark `@manual` with a request-count oracle (shape below). This is general — it covers any side-effect, not a fixed verb list.
|
|
@@ -773,8 +773,9 @@ those namespaces are the **System INTEGRATION** group — keep their oracles abo
|
|
|
773
773
|
> **Auto-detect**: if path is `qa/flows/<name>/` → use this section. Skip Steps 1–4 above.
|
|
774
774
|
|
|
775
775
|
**Read `requirements/flow-contract.yaml` FIRST — it is the flow's boundary and the yardstick
|
|
776
|
-
`sungen audit` scores the flow against** (`flowCoverage` axis =
|
|
777
|
-
`FLOW-OUTCOME-UNPROVEN`; `FLOW-SCOPE-CREEP`
|
|
776
|
+
`sungen audit` scores the flow against** (`flowCoverage` axis = the declared flows / phases
|
|
777
|
+
automated; `FLOW-OUTCOME-UNPROVEN`; `FLOW-SCOPE-CREEP`; `FLOW-GUARANTEE-MISSING`;
|
|
778
|
+
`FLOW-INVENTORY-MISSING`). **A filled contract is an INPUT — never rewrite it
|
|
778
779
|
to match your output** (same rule as `test-viewpoint.md`).
|
|
779
780
|
|
|
780
781
|
> **HARD RULE — the contract must be AUTHORED, never merely cited.** If
|
|
@@ -796,7 +797,7 @@ to match your output** (same rule as `test-viewpoint.md`).
|
|
|
796
797
|
| Selector format | `[Element]` | `[Screen:Element]` (namespaced) |
|
|
797
798
|
| Test data keys | `{{variable}}` | `{{phase.variable}}` |
|
|
798
799
|
| Feature tag | `@auto` / `@smoke` etc. | `@flow` (required) |
|
|
799
|
-
| Scenario ids | `VP-<CATEGORY>-NNN` | `FL-<PHASE>-NNN` — use-case phases `BF`/`AF0n`/`EF0n` (see below), or the flat `HP` (happy path) / `ER` (error recovery) / `EH` (guards) / `UI` for a short single-outcome flow |
|
|
800
|
+
| Scenario ids | `VP-<CATEGORY>-NNN` | `FL-<PHASE>-NNN` — use-case phases `BF`/`AF0n`/`EF0n` (see below), or the flat `HP` (happy path) / `ER` (error recovery) / `EH` (guards) / `UI` for a short single-outcome flow — the contract still needs both guarantees and a `flows:` inventory either way |
|
|
800
801
|
|
|
801
802
|
**Scenarios to generate — every phase demanded by the contract, automated:**
|
|
802
803
|
|
|
@@ -15,24 +15,6 @@ This skill is a **router**. The detailed checklists live in 6 group files — lo
|
|
|
15
15
|
|
|
16
16
|
> All checklist items are written in English. Render scenario names, step text, and test IDs in English.
|
|
17
17
|
|
|
18
|
-
## Authoring (mode 2) — drafting `test-viewpoint.md`
|
|
19
|
-
|
|
20
|
-
Draft `requirements/test-viewpoint.md` only when the file is missing, is an untouched scaffold
|
|
21
|
-
(headings, `<…>` placeholders, empty tables), or has one or more VP rows and every one lacks a
|
|
22
|
-
`Kỳ vọng` — a file with no VP ids that is not the scaffold is QA-written (mode 1). Draft it from
|
|
23
|
-
`viewpoint-template.md` in this folder (the QA's sections, in Vietnamese): the file's first line is
|
|
24
|
-
`drafted-by: ai`; every VP row has a non-empty `Kỳ vọng`; whatever the spec leaves open becomes a
|
|
25
|
-
closed question in mục 5, never an invented expectation. Fill sections 1 and 3 and the Coverage
|
|
26
|
-
table from the patterns and Shared Checks below. A viewpoint without `drafted-by: ai` is QA-owned:
|
|
27
|
-
read it, never edit it.
|
|
28
|
-
|
|
29
|
-
## Review (both modes) — `review-checklist.md`
|
|
30
|
-
|
|
31
|
-
create-test reviews every viewpoint with `review-checklist.md` in this folder, fed by
|
|
32
|
-
`sungen viewpoint scan --json`, and writes `requirements/viewpoint-review.md`. On a QA-owned file
|
|
33
|
-
that review file is the only output, limited to the checks its `Mode 1` column allows; on your own
|
|
34
|
-
draft, run every check and fix the draft first.
|
|
35
|
-
|
|
36
18
|
## Routing table
|
|
37
19
|
|
|
38
20
|
| UI element on the screen | Pattern | Read file |
|
|
@@ -329,7 +329,19 @@ function resolveDynamicVars(data: Record<string, any>): Record<string, any> {
|
|
|
329
329
|
const ts = String(Date.now());
|
|
330
330
|
const uid = crypto.randomUUID();
|
|
331
331
|
const now = new Date();
|
|
332
|
-
|
|
332
|
+
// `{{$date}}` is the runner's LOCAL calendar date — the "today" the browser sees when
|
|
333
|
+
// playwright.config sets no `timezoneId`. toISOString() is UTC, so east of Greenwich it named
|
|
334
|
+
// yesterday until the offset passed (00:00–06:59 at UTC+7). Node reads the `TZ` env var, so a
|
|
335
|
+
// config that pins `timezoneId` pins this too by exporting the same zone as `TZ`.
|
|
336
|
+
// `{{$datetime}}` stays an ISO-8601 UTC instant: it names a moment, not a calendar day.
|
|
337
|
+
const pad = (n: number): string => String(n).padStart(2, '0');
|
|
338
|
+
// `{{$date:+1d}}` / `{{$date:-7d}}` shift by whole days; built from the local Y/M/D, so it stays
|
|
339
|
+
// the right calendar day across a DST change.
|
|
340
|
+
const localDate = (offsetDays: number): string => {
|
|
341
|
+
const d = new Date(now.getFullYear(), now.getMonth(), now.getDate() + offsetDays);
|
|
342
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
343
|
+
};
|
|
344
|
+
const date = localDate(0);
|
|
333
345
|
const datetime = now.toISOString();
|
|
334
346
|
|
|
335
347
|
function resolveValue(value: any): any {
|
|
@@ -344,10 +356,16 @@ function resolveDynamicVars(data: Record<string, any>): Record<string, any> {
|
|
|
344
356
|
const [min, max] = (args || '1:9999').split(':').map(Number);
|
|
345
357
|
return String(Math.floor(Math.random() * (max - min + 1)) + min);
|
|
346
358
|
}
|
|
347
|
-
case 'date':
|
|
348
|
-
|
|
359
|
+
case 'date': {
|
|
360
|
+
// An argument this does not understand stays literal, like an unknown variable — it used
|
|
361
|
+
// to be ignored, so `{{$date:+1d}}` silently produced today. Up to 5 digits (~270 years):
|
|
362
|
+
// a typo like +99999999999d would build an Invalid Date.
|
|
363
|
+
if (!args) return date;
|
|
364
|
+
const off = /^([+-]\d{1,5})d$/.exec(args.trim());
|
|
365
|
+
return off ? localDate(Number(off[1])) : match;
|
|
366
|
+
}
|
|
349
367
|
case 'datetime':
|
|
350
|
-
return datetime;
|
|
368
|
+
return args ? match : datetime;
|
|
351
369
|
default:
|
|
352
370
|
return match;
|
|
353
371
|
}
|
|
@@ -15,9 +15,6 @@ export interface ViewpointPattern {
|
|
|
15
15
|
mobile?: boolean;
|
|
16
16
|
}
|
|
17
17
|
export declare const VIEWPOINT_PATTERNS: readonly ViewpointPattern[];
|
|
18
|
-
/**
|
|
19
|
-
* The scaffold's pattern-table rows: 1–17 for every screen, plus Group F on a mobile project.
|
|
20
|
-
* `Áp dụng?` is left empty for the QA to mark ✅ / ❌, as the QA template does.
|
|
21
|
-
*/
|
|
18
|
+
/** The scaffold's pattern-table rows: 1–17 for every screen, plus Group F on a mobile project. */
|
|
22
19
|
export declare function uiPatternRows(isMobile: boolean): string[];
|
|
23
20
|
//# sourceMappingURL=viewpoint-pattern-catalog.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"viewpoint-pattern-catalog.d.ts","sourceRoot":"","sources":["../../src/orchestrator/viewpoint-pattern-catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,WAAW,gBAAgB;IAC/B,CAAC,EAAE,MAAM,CAAC;IACV,IAAI,EAAE,MAAM,CAAC;IACb,mFAAmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,kBAAkB,EAAE,SAAS,gBAAgB,EAiCzD,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"viewpoint-pattern-catalog.d.ts","sourceRoot":"","sources":["../../src/orchestrator/viewpoint-pattern-catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,WAAW,gBAAgB;IAC/B,CAAC,EAAE,MAAM,CAAC;IACV,IAAI,EAAE,MAAM,CAAC;IACb,mFAAmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,kBAAkB,EAAE,SAAS,gBAAgB,EAiCzD,CAAC;AAEF,kGAAkG;AAClG,wBAAgB,aAAa,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,EAAE,CAIzD"}
|
|
@@ -36,13 +36,10 @@ exports.VIEWPOINT_PATTERNS = [
|
|
|
36
36
|
{ n: 31, name: 'Settings Toggle List', mobile: true },
|
|
37
37
|
{ n: 32, name: 'App Lifecycle & Orientation', mobile: true },
|
|
38
38
|
];
|
|
39
|
-
/**
|
|
40
|
-
* The scaffold's pattern-table rows: 1–17 for every screen, plus Group F on a mobile project.
|
|
41
|
-
* `Áp dụng?` is left empty for the QA to mark ✅ / ❌, as the QA template does.
|
|
42
|
-
*/
|
|
39
|
+
/** The scaffold's pattern-table rows: 1–17 for every screen, plus Group F on a mobile project. */
|
|
43
40
|
function uiPatternRows(isMobile) {
|
|
44
41
|
return exports.VIEWPOINT_PATTERNS
|
|
45
42
|
.filter((p) => isMobile || !p.mobile)
|
|
46
|
-
.map((p) => `| ${p.n} | ${p.name} | | |`);
|
|
43
|
+
.map((p) => `| ${p.n} | ${p.name} | ☐ | |`);
|
|
47
44
|
}
|
|
48
45
|
//# sourceMappingURL=viewpoint-pattern-catalog.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"viewpoint-pattern-catalog.js","sourceRoot":"","sources":["../../src/orchestrator/viewpoint-pattern-catalog.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"viewpoint-pattern-catalog.js","sourceRoot":"","sources":["../../src/orchestrator/viewpoint-pattern-catalog.ts"],"names":[],"mappings":";;;AAqDA,sCAIC;AAxCY,QAAA,kBAAkB,GAAgC;IAC7D,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE;IAC/B,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE;IAC7B,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE;IACjC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE;IAC9B,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE;IAC/B,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE;IACxB,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE;IAC5B,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE;IACxB,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE;IACxB,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE;IACnC,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE;IACpC,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE;IACjC,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE;IAC7B,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,8BAA8B,EAAE;IAC/C,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE;IACjC,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE;IAC3B,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,qBAAqB,EAAE;IACtC,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,IAAI,EAAE;IAC7C,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,6BAA6B,EAAE,MAAM,EAAE,IAAI,EAAE;IAC5D,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,qBAAqB,EAAE,MAAM,EAAE,IAAI,EAAE;IACpD,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,IAAI,EAAE;IAC9C,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE,MAAM,EAAE,IAAI,EAAE;IAClD,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,MAAM,EAAE,IAAI,EAAE;IACjD,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,yBAAyB,EAAE,MAAM,EAAE,IAAI,EAAE;IACxD,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE,MAAM,EAAE,IAAI,EAAE;IAClD,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,MAAM,EAAE,IAAI,EAAE;IAChD,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,IAAI,EAAE;IAC/C,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,qBAAqB,EAAE,MAAM,EAAE,IAAI,EAAE;IACpD,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,sBAAsB,EAAE,MAAM,EAAE,IAAI,EAAE;IACrD,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,uBAAuB,EAAE,MAAM,EAAE,IAAI,EAAE;IACtD,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,sBAAsB,EAAE,MAAM,EAAE,IAAI,EAAE;IACrD,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,6BAA6B,EAAE,MAAM,EAAE,IAAI,EAAE;CAC7D,CAAC;AAEF,kGAAkG;AAClG,SAAgB,aAAa,CAAC,QAAiB;IAC7C,OAAO,0BAAkB;SACtB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;SACpC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC;AAChD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sun-asterisk/sungen",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.35",
|
|
4
4
|
"description": "Deterministic E2E Test Compiler - Gherkin + Selectors → Playwright tests",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"@babel/types": "^7.28.5",
|
|
41
41
|
"@cucumber/gherkin": "^37.0.0",
|
|
42
42
|
"@cucumber/messages": "^31.0.0",
|
|
43
|
-
"@sungen/driver-data-factory": "3.2.
|
|
44
|
-
"@sungen/driver-ui": "3.2.
|
|
43
|
+
"@sungen/driver-data-factory": "3.2.35",
|
|
44
|
+
"@sungen/driver-ui": "3.2.35",
|
|
45
45
|
"chalk": "^5.6.2",
|
|
46
46
|
"commander": "^14.0.2",
|
|
47
47
|
"dotenv": "^17.2.3",
|
|
@@ -4,7 +4,6 @@ import * as fs from 'fs';
|
|
|
4
4
|
import { runAudit, AuditReport, catalogIdFromScreenDir } from '../../harness/audit';
|
|
5
5
|
import { reportSlug } from '../../harness/unit-paths';
|
|
6
6
|
import { acceptViewpointBaseline } from '../../harness/viewpoint-baseline';
|
|
7
|
-
import { removeDraftLineFromFile, viewpointFileIsAiDraft } from '../../harness/viewpoint-draft-header';
|
|
8
7
|
import { findUnitDir } from '../resolve-unit';
|
|
9
8
|
|
|
10
9
|
|
|
@@ -144,7 +143,7 @@ function render(r: AuditReport): void {
|
|
|
144
143
|
const held = new Set(r.score.axes.filter((x) => x.withheld).map((x) => x.key));
|
|
145
144
|
const absent = na.filter((k) => !held.has(k));
|
|
146
145
|
if (absent.length) L(` not scored (no evidence in this unit): ${absent.join(', ')}`);
|
|
147
|
-
if (held.size) L(` measured but held (viewpoint
|
|
146
|
+
if (held.size) L(` measured but held (viewpoint baseline changed — review, then --accept-viewpoint): ${[...held].join(', ')}`);
|
|
148
147
|
}
|
|
149
148
|
L(` weakest: ${r.calibration.weakest.axis} ${(r.calibration.weakest.value * 100).toFixed(0)}%${r.calibration.inflated ? ' ⚠ SCORE-INFLATED-BY-BREADTH' : ''}`);
|
|
150
149
|
if (r.calibration.cappedAt !== undefined) {
|
|
@@ -174,14 +173,10 @@ export function registerAuditCommand(program: Command): void {
|
|
|
174
173
|
.option('--area <name>', 'Alias of --api — an API-first area (qa/api/<name>)')
|
|
175
174
|
.option('--json', 'Output the raw JSON report')
|
|
176
175
|
.option('--accept-viewpoint', 'Accept the current test-viewpoint.md as the baseline the suite is measured against')
|
|
177
|
-
.option('--no-baseline', 'Measure without writing .sungen/viewpoint-baseline.json (audit a drafted viewpoint before generating)')
|
|
178
176
|
.action((options) => {
|
|
179
177
|
try {
|
|
180
178
|
const name = options.screen || options.api || options.area;
|
|
181
179
|
if (!name) throw new Error('Provide --screen <name> (or --api <area>)');
|
|
182
|
-
// Commander's `--no-*`: `baseline` is true unless the flag is passed.
|
|
183
|
-
const recordBaseline = options.baseline !== false;
|
|
184
|
-
if (!recordBaseline && options.acceptViewpoint) throw new Error('--no-baseline and --accept-viewpoint cannot be combined');
|
|
185
180
|
const dir = findUnitDir(name);
|
|
186
181
|
if (!dir) throw new Error(`Not found: qa/screens/${name}, qa/flows/${name}, or qa/api/${name}`);
|
|
187
182
|
|
|
@@ -200,35 +195,18 @@ export function registerAuditCommand(program: Command): void {
|
|
|
200
195
|
const unitId = catalogIdFromScreenDir(dir);
|
|
201
196
|
const vb = report.viewpointBaseline;
|
|
202
197
|
const postHoc = vb.status === 'new' && report.scenarioCount > 0;
|
|
203
|
-
// `drafted-by: ai` — a viewpoint the AI wrote is never accepted by the audit that measures it,
|
|
204
|
-
// with scenarios or without: only `--accept-viewpoint` does, and that removes the line.
|
|
205
|
-
const vpPath = path.join(dir, 'requirements', 'test-viewpoint.md');
|
|
206
|
-
const hasDraftMarker = viewpointFileIsAiDraft(vpPath);
|
|
207
|
-
// A draft with no VP id has nothing to accept — only VIEWPOINT-MISSING speaks for it. Fail
|
|
208
|
-
// here, before any write, so the file stays as it is and the QA is told why.
|
|
209
|
-
if (options.acceptViewpoint && vb.status === 'absent' && hasDraftMarker) {
|
|
210
|
-
throw new Error('the draft declares no VP ids — nothing to accept. Add the viewpoint ids (VP-… in the Priority Viewpoints table or checks), then run --accept-viewpoint again.');
|
|
211
|
-
}
|
|
212
|
-
const aiDraft = vb.status !== 'absent' && hasDraftMarker;
|
|
213
198
|
// A re-parsed baseline is a CACHE migration, not an acceptance: the comparison already
|
|
214
199
|
// resolved to `unchanged`, and rewriting the record only lets the NEXT real edit be
|
|
215
200
|
// detected. Left unwritten it would re-migrate — and re-announce — on every run.
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
// scenarios exist is `new` and not post-hoc, and a plain audit would record it as the yardstick.
|
|
219
|
-
if (recordBaseline && !aiDraft && vb.reparsed) acceptViewpointBaseline(process.cwd(), unitId, { hash: vb.hash, ids: vb.ids, fileHash: vb.fileHash });
|
|
220
|
-
if (recordBaseline && ((vb.status === 'new' && !postHoc && !aiDraft) || (options.acceptViewpoint && (vb.status === 'changed' || postHoc || aiDraft)))) {
|
|
201
|
+
if (vb.reparsed) acceptViewpointBaseline(process.cwd(), unitId, { hash: vb.hash, ids: vb.ids, fileHash: vb.fileHash });
|
|
202
|
+
if ((vb.status === 'new' && !postHoc) || (options.acceptViewpoint && (vb.status === 'changed' || postHoc))) {
|
|
221
203
|
acceptViewpointBaseline(process.cwd(), unitId, { hash: vb.hash, ids: vb.ids, fileHash: vb.fileHash });
|
|
222
204
|
if (options.acceptViewpoint) {
|
|
223
|
-
// The baseline is recorded first: if the line cannot be removed the draft stays flagged
|
|
224
|
-
// (held, finding, decide step) and the accept can simply be run again.
|
|
225
|
-
const lineRemoved = aiDraft && removeDraftLineFromFile(vpPath);
|
|
226
205
|
// Measure again against what was just accepted: the report printed and saved below (the
|
|
227
206
|
// one `sungen next` reads) must not be the pre-accept one, still holding the axes (#867).
|
|
228
207
|
report = runAudit(dir, name);
|
|
229
208
|
if (!options.json) {
|
|
230
209
|
console.log(`\n ✓ test-viewpoint.md accepted as the baseline for ${name} (${vb.ids.length} viewpoint id(s)).`);
|
|
231
|
-
if (lineRemoved) console.log(' Removed the `drafted-by: ai` line from test-viewpoint.md — it is QA-reviewed from here on.');
|
|
232
210
|
console.log(' The report below is measured against it.\n');
|
|
233
211
|
}
|
|
234
212
|
}
|
|
@@ -250,7 +228,6 @@ export function registerAuditCommand(program: Command): void {
|
|
|
250
228
|
} else {
|
|
251
229
|
render(report);
|
|
252
230
|
console.log(` Report: ${path.relative(process.cwd(), outPath)}`);
|
|
253
|
-
if (!recordBaseline) console.log(' ○ baseline not recorded (--no-baseline)');
|
|
254
231
|
console.log('');
|
|
255
232
|
}
|
|
256
233
|
// Non-zero exit when the gate fails — usable in CI / repair loop. Set exitCode (not
|
package/src/cli/index.ts
CHANGED
|
@@ -41,7 +41,6 @@ import { registerTriageCommand } from './commands/triage';
|
|
|
41
41
|
import { registerDefectCommand } from './commands/defect';
|
|
42
42
|
import { registerResultsCommand } from './commands/results-merge';
|
|
43
43
|
import { registerLoginCommand } from './commands/login';
|
|
44
|
-
import { registerViewpointCommand } from './commands/viewpoint';
|
|
45
44
|
import { capabilityRegistry } from '../capabilities/registry';
|
|
46
45
|
import { discoverAndRegisterCapabilities } from '../capabilities/discover';
|
|
47
46
|
import { warnAssetsDrift } from '../orchestrator/assets-drift';
|
|
@@ -98,7 +97,6 @@ registerSetupMobileEnvCommand(program);
|
|
|
98
97
|
registerIngestCommand(program);
|
|
99
98
|
registerEvalCommand(program);
|
|
100
99
|
registerLoginCommand(program);
|
|
101
|
-
registerViewpointCommand(program);
|
|
102
100
|
|
|
103
101
|
// Capability-contributed CLI commands (Capability SPI): drivers own their authoring commands
|
|
104
102
|
// (e.g. @sungen/driver-api → `sungen api import`). Discover, then register each.
|
|
@@ -183,8 +183,16 @@ export function renderDynamicTokens(text: string): string {
|
|
|
183
183
|
const [min, max] = (args || '1:9999').split(':');
|
|
184
184
|
return `<random ${min}-${max}>`;
|
|
185
185
|
}
|
|
186
|
-
case 'date':
|
|
187
|
-
|
|
186
|
+
case 'date': {
|
|
187
|
+
if (!args) return "<today's date>";
|
|
188
|
+
// Same argument grammar as the runtime resolver (specs-test-data.ts), which leaves anything
|
|
189
|
+
// else raw — so a raw token here is what the tester would see too, and Gate D flags it.
|
|
190
|
+
const off = /^([+-]\d{1,5})d$/.exec(args.trim());
|
|
191
|
+
if (!off) return m;
|
|
192
|
+
const n = Number(off[1]);
|
|
193
|
+
return `<today's date ${n < 0 ? '-' : '+'} ${Math.abs(n)} day${Math.abs(n) === 1 ? '' : 's'}>`;
|
|
194
|
+
}
|
|
195
|
+
case 'datetime': return args ? m : '<current date-time>';
|
|
188
196
|
default: return m;
|
|
189
197
|
}
|
|
190
198
|
});
|
|
@@ -18,6 +18,7 @@ export const STATE_CLAUSES: ReadonlyArray<StateClause> = [
|
|
|
18
18
|
'sorted ascending', 'sorted descending',
|
|
19
19
|
'unchecked', 'checked',
|
|
20
20
|
'hidden', 'visible', 'disabled', 'enabled', 'focused', 'empty', 'loading', 'selected',
|
|
21
|
+
'expanded', 'collapsed',
|
|
21
22
|
];
|
|
22
23
|
|
|
23
24
|
/**
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
? [await __el.getAttribute('label'), await __el.getAttribute('value')]
|
|
9
9
|
: [await __el.getText(), await __el.getAttribute('content-desc')];
|
|
10
10
|
const __actual = String(__sources.find(s => s && String(s).trim() !== '') ?? '').trim();
|
|
11
|
-
|
|
11
|
+
{{!-- An empty expected value means "shows no text": includes('') holds for any text (#910). --}}
|
|
12
|
+
if ({{#if emptyMeansEmpty}}__expected === '' ? __actual !== '' : {{/if}}!__actual.includes(__expected)) {
|
|
12
13
|
throw new Error(`[sungen] Expected text containing "${__expected}" but element reads "${__actual}"`);
|
|
13
14
|
}
|
|
14
15
|
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
{{!-- aria-expanded has no native-mobile equivalent; the appium expand action is a bare tap. --}}
|
|
2
|
+
throw new Error("[sungen] 'is expanded' / 'is collapsed' (aria-expanded) is web-only and has no native-mobile equivalent. Assert what the open panel shows instead (e.g. see [X] section), or tag this scenario @manual.");
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{{!-- A date-picker whose trigger is a <button> (a calendar popover, #907) has no value to type:
|
|
2
|
+
fill() throws "Element is not an <input>". With no recipe there is no honest compilation, so
|
|
3
|
+
fail loud and name the way forward — the appium twin of this template does the same for a
|
|
4
|
+
native picker dialog. --}}
|
|
5
|
+
throw new Error('[sungen] {{escapeQuotes reason}}');
|