@sun-asterisk/sungen 3.2.2-beta.11 → 3.2.2-beta.14
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.map +1 -1
- package/dist/capabilities/discover.js +26 -3
- package/dist/capabilities/discover.js.map +1 -1
- package/dist/cli/commands/add-flow.d.ts.map +1 -1
- package/dist/cli/commands/add-flow.js +2 -0
- package/dist/cli/commands/add-flow.js.map +1 -1
- package/dist/cli/commands/add.d.ts.map +1 -1
- package/dist/cli/commands/add.js +4 -0
- package/dist/cli/commands/add.js.map +1 -1
- package/dist/cli/commands/capability.d.ts.map +1 -1
- package/dist/cli/commands/capability.js +61 -0
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +89 -2
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +2 -1
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +27 -2
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/journey.d.ts.map +1 -1
- package/dist/cli/commands/journey.js +19 -1
- package/dist/cli/commands/journey.js.map +1 -1
- package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
- package/dist/dashboard/snapshot-builder.js +10 -1
- package/dist/dashboard/snapshot-builder.js.map +1 -1
- package/dist/exporters/csv-exporter.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.js +3 -1
- package/dist/exporters/csv-exporter.js.map +1 -1
- package/dist/exporters/json-exporter.js +3 -1
- package/dist/exporters/json-exporter.js.map +1 -1
- package/dist/exporters/mobile-app-id.d.ts +22 -0
- package/dist/exporters/mobile-app-id.d.ts.map +1 -0
- package/dist/exporters/mobile-app-id.js +37 -0
- package/dist/exporters/mobile-app-id.js.map +1 -0
- package/dist/exporters/spec-parser.d.ts.map +1 -1
- package/dist/exporters/spec-parser.js +6 -2
- package/dist/exporters/spec-parser.js.map +1 -1
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts +3 -0
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/scroll-action.hbs +7 -4
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/toggle-action.hbs +20 -3
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/checked-assertion.hbs +4 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/not-checked-assertion.hbs +4 -1
- package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +3 -0
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +38 -3
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/template-engine.d.ts +3 -0
- package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
- package/dist/generators/test-generator/template-engine.js +11 -1
- package/dist/generators/test-generator/template-engine.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts +11 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +33 -0
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/harness/audit.js +1 -1
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/catalog/universal-viewpoints.yaml +15 -0
- package/dist/harness/journey.d.ts +20 -0
- package/dist/harness/journey.d.ts.map +1 -1
- package/dist/harness/journey.js +97 -11
- package/dist/harness/journey.js.map +1 -1
- package/dist/harness/sensors.d.ts +1 -1
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +32 -9
- package/dist/harness/sensors.js.map +1 -1
- package/dist/orchestrator/flow-manager.d.ts +11 -0
- package/dist/orchestrator/flow-manager.d.ts.map +1 -1
- package/dist/orchestrator/flow-manager.js +147 -29
- package/dist/orchestrator/flow-manager.js.map +1 -1
- package/dist/orchestrator/mobile-runtime-scaffolder.d.ts +59 -0
- package/dist/orchestrator/mobile-runtime-scaffolder.d.ts.map +1 -0
- package/dist/orchestrator/mobile-runtime-scaffolder.js +298 -0
- package/dist/orchestrator/mobile-runtime-scaffolder.js.map +1 -0
- package/dist/orchestrator/project-initializer.d.ts +25 -7
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +115 -62
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/screen-manager.d.ts +30 -0
- package/dist/orchestrator/screen-manager.d.ts.map +1 -1
- package/dist/orchestrator/screen-manager.js +205 -22
- package/dist/orchestrator/screen-manager.js.map +1 -1
- package/dist/orchestrator/templates/ai-instructions/claude-agent-reviewer.md +12 -0
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-flow.md +32 -4
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +51 -10
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +23 -2
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-delivery.md +6 -0
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-locale.md +25 -4
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-review.md +8 -1
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +38 -4
- package/dist/orchestrator/templates/ai-instructions/claude-skill-delivery.md +2 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-locale.md +106 -9
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +33 -3
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +50 -9
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +11 -2
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +6 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-locale.md +25 -4
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-review.md +8 -1
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +38 -4
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +2 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-locale.md +106 -9
- package/package.json +3 -3
- package/src/capabilities/discover.ts +29 -3
- package/src/cli/commands/add-flow.ts +2 -0
- package/src/cli/commands/add.ts +4 -0
- package/src/cli/commands/capability.ts +64 -1
- package/src/cli/commands/delivery.ts +89 -2
- package/src/cli/commands/generate.ts +2 -1
- package/src/cli/commands/init.ts +38 -3
- package/src/cli/commands/journey.ts +15 -2
- package/src/dashboard/snapshot-builder.ts +10 -1
- package/src/exporters/csv-exporter.ts +3 -1
- package/src/exporters/json-exporter.ts +3 -1
- package/src/exporters/mobile-app-id.ts +34 -0
- package/src/exporters/spec-parser.ts +6 -2
- package/src/generators/test-generator/adapters/adapter-interface.ts +3 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/scroll-action.hbs +7 -4
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/toggle-action.hbs +20 -3
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/checked-assertion.hbs +4 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/not-checked-assertion.hbs +4 -1
- package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +3 -0
- package/src/generators/test-generator/code-generator.ts +39 -3
- package/src/generators/test-generator/template-engine.ts +14 -1
- package/src/generators/test-generator/utils/selector-resolver.ts +51 -1
- package/src/harness/audit.ts +1 -1
- package/src/harness/catalog/universal-viewpoints.yaml +15 -0
- package/src/harness/journey.ts +110 -9
- package/src/harness/sensors.ts +25 -8
- package/src/orchestrator/flow-manager.ts +165 -29
- package/src/orchestrator/mobile-runtime-scaffolder.ts +277 -0
- package/src/orchestrator/project-initializer.ts +127 -64
- package/src/orchestrator/screen-manager.ts +237 -22
- package/src/orchestrator/templates/ai-instructions/claude-agent-reviewer.md +12 -0
- package/src/orchestrator/templates/ai-instructions/claude-cmd-add-flow.md +32 -4
- package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +51 -10
- package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +23 -2
- package/src/orchestrator/templates/ai-instructions/claude-cmd-delivery.md +6 -0
- package/src/orchestrator/templates/ai-instructions/claude-cmd-locale.md +25 -4
- package/src/orchestrator/templates/ai-instructions/claude-cmd-review.md +8 -1
- package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +38 -4
- package/src/orchestrator/templates/ai-instructions/claude-skill-delivery.md +2 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-locale.md +106 -9
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +33 -3
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +50 -9
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +11 -2
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +6 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-locale.md +25 -4
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-review.md +8 -1
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +38 -4
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +2 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-locale.md +106 -9
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sungen-locale
|
|
3
|
-
description: 'Bootstrap i18n for a screen/flow — audit selectors, detect locale switch mechanism via Playwright, generate test-data overlay so `sungen:run-test --env <locale>` works.'
|
|
3
|
+
description: 'Bootstrap i18n for a screen/flow — audit selectors, detect locale switch mechanism via Playwright (web) or device locale (mobile), generate test-data overlay so `sungen:run-test --env <locale>` works.'
|
|
4
4
|
argument-hint: "<name> <locale> [--base-locale <code>] [--offline]"
|
|
5
|
-
tools: [read, execute, edit, vscode/askQuestions, playwright/*]
|
|
5
|
+
tools: [read, execute, edit, vscode/askQuestions, playwright/*, appium/*]
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
## Role
|
|
@@ -15,10 +15,31 @@ Parse from `$ARGUMENTS`:
|
|
|
15
15
|
- **name** — screen or flow name (e.g. `home`, `awards`). If missing, ask the user.
|
|
16
16
|
- **locale** — target locale code (e.g. `en`, `ja`, `en-US`, `staging-ja`). If missing, ask. This becomes the suffix in `test-data/<feature>.<locale>.yaml` and `SUNGEN_ENV=<locale>` at run time.
|
|
17
17
|
- **`--base-locale <code>`** — optional. Locale of the existing `test-data/<feature>.yaml`. Default `vi`. Reporting only — files are never renamed.
|
|
18
|
-
- **`--offline`** — force OFFLINE mode (skip
|
|
18
|
+
- **`--offline`** — force OFFLINE mode (skip live capture). Useful when you know the live page/device can't be reached.
|
|
19
19
|
|
|
20
20
|
Reject if name == locale.
|
|
21
21
|
|
|
22
|
+
## Platform detection (do this FIRST)
|
|
23
|
+
|
|
24
|
+
Read `qa/capabilities.yaml` and check the `platform` field.
|
|
25
|
+
|
|
26
|
+
- If `platform: mobile` → switch to the **mobile path** (below).
|
|
27
|
+
- If `platform: web` or field absent → follow the **web path** (existing behavior, unchanged).
|
|
28
|
+
|
|
29
|
+
### Mobile path
|
|
30
|
+
|
|
31
|
+
When `platform: mobile`:
|
|
32
|
+
|
|
33
|
+
- **Live capture**: use **Appium MCP** (`select_device` → `appium_session_management(create)` with device at base locale → `appium_get_page_source` / `appium_screenshot`) — NOT Playwright browser tools.
|
|
34
|
+
- **Locale-switch mechanism**: **device locale** — `appium:language` / `appium:locale` caps + `noReset:false` (set by wdio `resolveLocaleCaps` at runtime). There is no URL prefix, query param, or browser-storage injection on mobile.
|
|
35
|
+
- **Phases skipped for mobile**: 3.5 (browser storage diff), 3.6 (storage verification), 6d (Pages URL update), 6f (locale-config.json / locale-fixture). These are web-only; wdio `resolveLocaleCaps` handles runtime locale on mobile automatically — no fixture files needed.
|
|
36
|
+
- **OFFLINE mode** (when device unreachable or `--offline` flag): scaffold overlay template + let user fill manually.
|
|
37
|
+
- **Hand-off**: print `SUNGEN_ENV=<locale> npm run test:mobile`. Include note: "If the device locale code differs from the overlay env name, set `SUNGEN_LOCALE=<code>` — wdio `resolveLocaleCaps` reads it as an override."
|
|
38
|
+
- **Phase 6 (mobile) writes only**: 6a selectors `{{var}}`, 6b base `test-data/<feature>.yaml`, 6c overlay `test-data/<feature>.<locale>.yaml`. No 6d or 6f.
|
|
39
|
+
- **Compile**: `sungen generate --screen <name>` (appium adapter auto-selected in a mobile project).
|
|
40
|
+
|
|
41
|
+
Stop here for mobile — do not continue to the web path.
|
|
42
|
+
|
|
22
43
|
## Auto-detect context
|
|
23
44
|
|
|
24
45
|
Same as `/sungen:run-test`:
|
|
@@ -26,7 +47,7 @@ Same as `/sungen:run-test`:
|
|
|
26
47
|
- Else `qa/screens/<name>/` exists → screen mode (base path: `qa/screens/<name>/`)
|
|
27
48
|
- Neither → tell user and stop
|
|
28
49
|
|
|
29
|
-
## Steps
|
|
50
|
+
## Steps (web)
|
|
30
51
|
|
|
31
52
|
The `sungen-locale` skill defines 7 phases. Execute them in order:
|
|
32
53
|
|
|
@@ -8,6 +8,13 @@ tools: [vscode, execute, read, edit, search, todo]
|
|
|
8
8
|
|
|
9
9
|
**Input**: Screen or flow name (e.g., `/sungen-review admin-users`).
|
|
10
10
|
|
|
11
|
+
## Platform detection (do this FIRST)
|
|
12
|
+
|
|
13
|
+
Read `qa/capabilities.yaml` and check the `platform` field.
|
|
14
|
+
|
|
15
|
+
- If `platform: mobile` → this is a **mobile project**. When reviewing scenarios, apply the **mobile viewpoint**: mobile-specific steps (tap, swipe, long-press, permission dialogs, clipboard, geolocation) are valid — do not flag them as incorrect or web-incompatible. Judge selector references against mobile accessibility patterns (`[accessibility-id]`, content-desc, etc.), not CSS/role. Load `sungen-mobile-gestures` for the canonical mobile step vocabulary. All other harness steps (audit, script-check, scorecard) run unchanged.
|
|
16
|
+
- If `platform: web` or field absent → **web path**, proceed as normal below.
|
|
17
|
+
|
|
11
18
|
## Role
|
|
12
19
|
|
|
13
20
|
You are an **independent QA Reviewer** — you did not author these tests. You do **not** invent a parallel score; you run the **harness** and present its signals as a human scorecard. Skills: `sungen-tc-review` (presentation rubric), `sungen-viewpoint`, `sungen-gherkin-syntax`.
|
|
@@ -25,7 +32,7 @@ You are an **independent QA Reviewer** — you did not author these tests. You d
|
|
|
25
32
|
1. **Enumerate** `<base>/${input:name}/features/*.feature`. If none → `/sungen-create-test` first.
|
|
26
33
|
2. **Run the harness (source of truth) — no separate rubric:**
|
|
27
34
|
- `sungen audit --screen ${input:name}` → gate, business-weighted score, findings, gaps.
|
|
28
|
-
- Apply the **`sungen-reviewer` criteria inline** → semantic verdict (do steps prove the title? observable Then? business-critical depth? @manual justified?).
|
|
35
|
+
- Apply the **`sungen-reviewer` criteria inline** → semantic verdict (do steps prove the title? observable Then? business-critical depth? @manual justified?). Walk the **lenses sequentially** (#410) — `correctness`, `depth`, `traceability`, `manual` (+ `api`) — deeper per lens than one pass, then merge + dedupe by `[scenario id]` + problem.
|
|
29
36
|
- `sungen script-check --screen ${input:name}` → spec is 1:1 with the Gherkin (flags hand-edit / stale drift; only if a spec exists).
|
|
30
37
|
3. **Unified scorecard** per feature, anchored on harness signals (the `sungen-tc-review` 7 dimensions are a presentation layer, not a competing score):
|
|
31
38
|
```
|
|
@@ -1,13 +1,47 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sungen-run-test
|
|
3
3
|
description: 'Generate selectors + auth state via Playwright MCP, compile, and run Playwright tests — auto-fixes selectors on failure'
|
|
4
|
-
argument-hint: "[name] [--env <locale>]"
|
|
5
|
-
tools: [read, execute, edit, vscode/askQuestions, playwright/*]
|
|
4
|
+
argument-hint: "[name] [--env <locale>] [--ios|--android|--both]"
|
|
5
|
+
tools: [read, execute, edit, vscode/askQuestions, playwright/*, appium/*]
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
## Role
|
|
9
9
|
|
|
10
|
-
You are a **Senior Developer**.
|
|
10
|
+
You are a **Senior Developer**.
|
|
11
|
+
- **Web screens/flows** → use `sungen-selector-fix`, `sungen-selector-keys`, and `sungen-error-mapping` skills (Playwright MCP + `npx playwright test`).
|
|
12
|
+
- **Mobile screens** (`platform: mobile` in `qa/capabilities.yaml`) → use `sungen-selector-fix-mobile`, `sungen-capture-mobile`, `sungen-mobile-gestures`, `sungen-selector-keys` (Appium MCP + `npm run test:mobile`).
|
|
13
|
+
|
|
14
|
+
## Platform detection (do this FIRST)
|
|
15
|
+
|
|
16
|
+
Read `qa/capabilities.yaml` and check the `platform` field.
|
|
17
|
+
|
|
18
|
+
- If `platform: mobile` → switch to the **mobile path** (below).
|
|
19
|
+
- If `platform: web` or field absent → follow the **web path** (all steps below, unchanged).
|
|
20
|
+
|
|
21
|
+
### Mobile path
|
|
22
|
+
|
|
23
|
+
When `platform: mobile`:
|
|
24
|
+
|
|
25
|
+
- **Selectors**: use **Appium MCP** (`select_device` → `appium_session_management(create)` → `generate_locators`), per the `sungen-selector-fix-mobile` skill. Also load `sungen-capture-mobile` and `sungen-mobile-gestures`.
|
|
26
|
+
- **Auth**: web Phase 0.5 (`storageState`) does not exist on mobile. When the feature has `@auth:<role>`, check `specs/.auth-mobile/<role>.json`; if missing, run the guided auth-contract builder (Phase 0.5 in `sungen-selector-fix-mobile`) BEFORE Phase 1.
|
|
27
|
+
- **Compile**: `npx sungen generate --screen <name> --framework appium`
|
|
28
|
+
- **OS selection**: parse the target OS from `$ARGUMENTS` — `--ios`, `--android`, or `--both`
|
|
29
|
+
(alias: `--platform <android|ios|both>`) → set `MOBILE_PLATFORM=<value>`. Default to `android` when no flag is given.
|
|
30
|
+
- **Run**: WebdriverIO, not Playwright. Scope one spec with `SUNGEN_SPECS`; prefix the parsed `MOBILE_PLATFORM`:
|
|
31
|
+
```bash
|
|
32
|
+
# Android (default — no flag, or --android)
|
|
33
|
+
SUNGEN_SPECS='./specs/generated/<name>/<name>.spec.ts' npm run test:mobile
|
|
34
|
+
# iOS only (--ios)
|
|
35
|
+
SUNGEN_SPECS='./specs/generated/<name>/<name>.spec.ts' MOBILE_PLATFORM=ios npm run test:mobile
|
|
36
|
+
# both OSes (--both)
|
|
37
|
+
SUNGEN_SPECS='./specs/generated/<name>/<name>.spec.ts' MOBILE_PLATFORM=both npm run test:mobile
|
|
38
|
+
```
|
|
39
|
+
`wdio.conf.ts` auto-selects `@wdio/globals` specs and auto-starts Appium. iOS needs a booted Simulator and `IOS_BUNDLE_ID`/`IOS_UDID` env vars.
|
|
40
|
+
- **Results**: the wdio reporter writes `<name>-test-result.<android|ios>[.<env>].json` — one file per capability, so a both-OS run keeps both outcomes.
|
|
41
|
+
- **Skip** the "Playwright command guidelines" section below — it applies to web only.
|
|
42
|
+
- The phased waves (smoke → priority → full → regression), attempt budgets, and next-steps mirror the web flow — follow the `sungen-selector-fix-mobile` skill for the mobile equivalents.
|
|
43
|
+
|
|
44
|
+
Stop here for mobile — do not continue to the web path.
|
|
11
45
|
|
|
12
46
|
> ⛔ **Source of truth — the live page is NOT the oracle; `.feature`/`test-data`/`spec.md` are.** Auto-fix is for **selector-resolution** failures (wrong locator → fix `selectors.yaml`). An **assertion-value** failure where the app contradicts the spec is a **CANDIDATE BUG → report it, let it FAIL** — never loosen the rule, weaken the assertion, edit the expected value/`.feature`, or hand-edit the generated `.spec.ts` to make it pass. See `sungen-error-mapping` § "Source of truth". (A `password > 8` test that fails on 6 chars is a bug to report, not a `>= 6` edit.)
|
|
13
47
|
|
|
@@ -94,7 +128,7 @@ If the unit is **api-first**, skip every selector/capture phase (an API test has
|
|
|
94
128
|
6. **Phase 2 — Priority Wave**: Run all `@high` scenarios. Fix only failures from this wave. Max 2 attempts. Shared selectors fixed here cascade to later phases.
|
|
95
129
|
7. **Phase 3 — Full Run**: Run all tests. Fix only **new** failures (elements unique to `@normal`/`@low`). Max 1 attempt. Don't loop on low-priority failures.
|
|
96
130
|
8. **Phase 4 — Regression**: One final full run. Report results. No more fix loops.
|
|
97
|
-
9. **Integrity & trace (always run after the final run).** `sungen script-check --screen <name>` — verify the spec is a **1:1** of the Gherkin; if **DRIFT**, re-run `sungen generate --screen <name>` (never hand-edit the `.spec.ts` — auto-fix edits `selectors.yaml`). Then `sungen ledger record --screen <name> --step run --ms <elapsed>` and `sungen
|
|
131
|
+
9. **Integrity & trace (always run after the final run).** `sungen script-check --screen <name>` — verify the spec is a **1:1** of the Gherkin; if **DRIFT**, re-run `sungen generate --screen <name>` (never hand-edit the `.spec.ts` — auto-fix edits `selectors.yaml`). Then `sungen ledger record --screen <name> --step run --ms <elapsed>` and **present the journey board** — `sungen journey --screen <name>` — as the human-facing close-out: **📍 you-are-here**, the **obligation checklist**, and **especially the 🔎 Review queue** with per-item anchors 〔`VP-id · file:line`〕 (*review what / where*) plus any candidate bugs left FAILING by the source-of-truth rule (#387). Then `sungen trace --screen <name>` for the process map + bottlenecks; the board's **→ Next** says whether to sign off & deliver.
|
|
98
132
|
9b. **Phase gate (boundary — do NOT skip).** `sungen gate --screen <name> --phase run` (exit 2 = HALT): run-boundary obligations (incl. automation) must be **satisfied or explicitly waived**. On HALT, classify per `sungen-error-mapping` § Source of truth (#387): selector-resolution failure → fix `selectors.yaml` + re-run; assertion-vs-spec failure → **report as a candidate bug / leave it FAIL** (never weaken to pass); accepted gap → `sungen journey --screen <name> --waive <OB> --reason "..."`. Don't declare "done" past a HALT without a fix, a reported bug, or a reasoned waiver.
|
|
99
133
|
10. **Capability-pending offer (consent-gated).** If `sungen audit` reports `AUTOMATION-READY-PENDING` (or `@requires:<cap>` tests are skipped "requires …"), offer: *"N scenario(s) are automation-ready — enable `<cap>` to run them? (`sungen capability add <cap>`)"*. Only on the user's yes, run `sungen capability add <cap>` + re-run; on no, leave skipped (not failures, not manual). **Never auto-install.**
|
|
100
134
|
|
|
@@ -53,6 +53,8 @@ Source modules: `src/exporters/*.ts`
|
|
|
53
53
|
|
|
54
54
|
The CLI reads the **per-target result file first** (co-located with `.spec.ts`), then falls back to the global `test-results/results.json`. Per-target is preferred because the global file gets OVERWRITTEN each time Playwright runs, losing results from earlier targets.
|
|
55
55
|
|
|
56
|
+
**Mobile (per-OS deliverables):** an Appium run writes one result file per capability — `<name>-test-result.<android|ios>[.<env>].json`. For a mobile project (`platform: mobile`), a plain `sungen delivery <name>` (no `SUNGEN_ENV`) **auto-emits one deliverable PER OS** — `<name>-testcases.android.{csv,xlsx}` + `<name>-testcases.ios.{csv,xlsx}` — and does **NOT** write a base `<name>-testcases.{csv,xlsx}` (android/ios are separate platform runs, not locales of one run, so a base aggregate would be empty/misleading; a stale base pair is removed). The Appium spec (mocha `describe()/it()`) is recognized as compiled (issue #391), so each per-OS CSV shows real Passed/Failed with `@manual` rows as N/A. `SUNGEN_ENV=android|ios sungen delivery <name>` still works to target a single OS. The **Test Environment** column is OS-precise: it shows only that OS's app id from the dual-id `Path:` — the Android **package** (`com.kngroup.media.converter`) on android, the iOS **bundle id** (`com.kngroup.media.converter.dev`) on ios — never the combined dual-id.
|
|
57
|
+
|
|
56
58
|
---
|
|
57
59
|
|
|
58
60
|
## Column mapping (handled by CLI)
|
|
@@ -30,7 +30,88 @@ Try: browser_navigate(baseURL)
|
|
|
30
30
|
|
|
31
31
|
Announce which mode is being used before Phase 1.
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Mobile (Appium) variant
|
|
36
|
+
|
|
37
|
+
> **When to use:** `qa/capabilities.yaml` has `platform: mobile`. The command reads this file and dispatches here. The web phases below still apply for web projects.
|
|
38
|
+
|
|
39
|
+
### Mechanism overview
|
|
40
|
+
|
|
41
|
+
| Web | Mobile |
|
|
42
|
+
|---|---|
|
|
43
|
+
| Switch via URL prefix / query param / UI switcher; persist via cookie / localStorage | Switch via **device locale** (`appium:language` / `appium:locale` caps + `noReset:false`) — wdio `resolveLocaleCaps` drives this at runtime |
|
|
44
|
+
| Capture via Playwright MCP `browser_snapshot` | Capture via **Appium MCP** (`select_device` → `appium_session_management` → `appium_get_page_source` / `appium_screenshot`) |
|
|
45
|
+
| Phase 6 writes selectors + test-data + Pages `{{base_path}}` + `locale-config.json` + `locale-fixture.ts` | Phase 6 writes **only** selectors + test-data (6a/6b/6c). No 6d, no 6f. |
|
|
46
|
+
| Handoff: `SUNGEN_ENV=<locale> npx playwright test` | Handoff: `SUNGEN_ENV=<locale> npm run test:mobile` |
|
|
47
|
+
|
|
48
|
+
### Mobile Phase 1 — Audit selectors (same as web, no MCP)
|
|
49
|
+
|
|
50
|
+
Identical to the web Phase 1 below. Read the screen's `selectors/<feature>.yaml` and list every entry with hardcoded `name` or `value` text. Multi-source text rules for mobile: Android elements expose text via `@text` first, then `@content-desc`; iOS exposes via `@label` first, then `@value`. Treat both as potential locale-dependent candidates.
|
|
51
|
+
|
|
52
|
+
### Mobile Phase 2 — Capture base locale (LIVE only)
|
|
53
|
+
|
|
54
|
+
1. `select_device` — choose the connected device (or emulator/simulator).
|
|
55
|
+
2. `appium_session_management(create)` with the app at the **base locale** (do not set `appium:language` / `appium:locale` — use the device's current locale, which should match the base test-data locale).
|
|
56
|
+
3. Navigate to the target screen using the Background navigation recipe from the `.feature` file (same as `sungen-capture-mobile` does).
|
|
57
|
+
4. `appium_get_page_source` + `appium_screenshot` — capture the accessibility tree and visual state.
|
|
58
|
+
5. For each Phase-1 candidate: verify the hardcoded text appears in the page source (check `@text`, `@content-desc`, `@label`, `@value` attributes). Drop stale candidates that don't appear.
|
|
59
|
+
|
|
60
|
+
Save state as `baseLocale = { pageSource, screenshot }`.
|
|
61
|
+
|
|
62
|
+
### Mobile Phase 3 — Switch locale (LIVE only) — device locale
|
|
63
|
+
|
|
64
|
+
There is **no URL prefix, query param, or browser storage injection on mobile**. The locale switch mechanism is always **device locale**:
|
|
65
|
+
|
|
66
|
+
1. Close the current Appium session: `appium_session_management(delete)`.
|
|
67
|
+
2. Relaunch with `appium:language` set to the language code (e.g. `ja`) and `appium:locale` set to the region code (e.g. `JP`), plus `appium:noReset: false` — this forces the app to re-run its first-launch locale detection from the OS locale, clearing any cached in-app override.
|
|
68
|
+
3. Navigate to the target screen again (same Background recipe).
|
|
69
|
+
4. `appium_get_page_source` + `appium_screenshot` — capture target-locale state.
|
|
70
|
+
5. Compare a known Phase-1 candidate text between base and target snapshots — confirm text differs.
|
|
71
|
+
|
|
72
|
+
**Optional in-app language picker**: if the app has a Settings screen with a language selector, that can be used as an alternative to relaunching. Default is device locale (above). Only use in-app picker if the user confirms it or if `noReset: false` is known ineffective for this app.
|
|
73
|
+
|
|
74
|
+
### Mobile Phases 3.5 / 3.6 — N/A on mobile (web-only)
|
|
75
|
+
|
|
76
|
+
**Skip entirely.** Phases 3.5 (browser storage diff) and 3.6 (storage verification via `addInitScript`) are Playwright / browser-only. On mobile, runtime locale is driven by `wdio resolveLocaleCaps()` reading `SUNGEN_LOCALE || SUNGEN_ENV` and setting `appium:language` / `appium:locale` caps automatically — no fixture injection needed.
|
|
77
|
+
|
|
78
|
+
### Mobile Phase 4 — Diff base ↔ target (LIVE only)
|
|
79
|
+
|
|
80
|
+
Same logic as web Phase 4 — pair each surviving candidate with its translated text from the target-locale page source. Match by element type + position in the accessibility tree. Mark unmatched candidates as "needs manual".
|
|
81
|
+
|
|
82
|
+
### Mobile Phase 5 — Confirm proposal
|
|
83
|
+
|
|
84
|
+
Same as web Phase 5. Present the proposal table; ask user to confirm / edit / cancel.
|
|
85
|
+
|
|
86
|
+
### Mobile Phase 6 — Apply files
|
|
87
|
+
|
|
88
|
+
Write **only these three outputs**:
|
|
89
|
+
|
|
90
|
+
**6a. Update `selectors/<feature>.yaml`** — replace hardcoded `name`/`value` with `{{varName}}`. (Same as web 6a.)
|
|
91
|
+
|
|
92
|
+
**6b. Update `test-data/<feature>.yaml`** — append new keys under `# === i18n: <screen> ===`. (Same as web 6b.)
|
|
93
|
+
|
|
94
|
+
**6c. Create `test-data/<feature>.<locale>.yaml`** — overlay with only changed keys. Comment header: `# Run with: SUNGEN_ENV=<locale> npm run test:mobile`. (Same as web 6c but different run command.)
|
|
95
|
+
|
|
96
|
+
**6d — SKIP (web-only)**: no Pages `{{base_path}}` changes. Mobile has no URL prefix routing.
|
|
97
|
+
|
|
98
|
+
**6f — SKIP (web-only)**: no `locale-config.json` / `locale-fixture.ts`. wdio `resolveLocaleCaps` handles device locale at runtime — no Playwright context fixture needed.
|
|
99
|
+
|
|
100
|
+
**6e. Compile**: `sungen generate --screen <name>` (or `--flow`). The appium adapter is auto-selected in a mobile project.
|
|
101
|
+
|
|
102
|
+
### Mobile Phase 7 — Hand off
|
|
103
|
+
|
|
104
|
+
Print summary (same fields as web Phase 7), then suggest:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
SUNGEN_ENV=<locale> npm run test:mobile
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Add note: *"If the device locale code differs from the overlay env name (e.g. `SUNGEN_ENV=staging-ja` but the device needs `ja`), set `SUNGEN_LOCALE=ja` — wdio `resolveLocaleCaps` reads it as an override."*
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Phase 1 — Audit selectors (web + mobile, always, no MCP)
|
|
34
115
|
|
|
35
116
|
For each `.feature` file under the screen, read the matching `selectors/<feature>.yaml`. List every entry whose `name` or `value` field contains literal text WITHOUT `{{…}}` AND is not a CSS/href selector.
|
|
36
117
|
|
|
@@ -54,7 +135,9 @@ nav kudos | name | Sun* Kudos | brand — skip
|
|
|
54
135
|
|
|
55
136
|
If zero candidates and zero `{{var}}` already in place → screen has no localizable text. Tell user, stop.
|
|
56
137
|
|
|
57
|
-
## Phase 2 — Capture base locale (LIVE only)
|
|
138
|
+
## Phase 2 — Capture base locale (web, LIVE only)
|
|
139
|
+
|
|
140
|
+
> **Mobile**: see "Mobile Phase 2" in the Mobile (Appium) variant section above.
|
|
58
141
|
|
|
59
142
|
1. Read `playwright.config.ts` for `baseURL`. Read `Path:` from `.feature` for entry path.
|
|
60
143
|
2. If screen has `@auth:<role>` tags, load `specs/.auth/<role>.json` via `browser_set_storage_state` first.
|
|
@@ -69,7 +152,9 @@ Save state in memory as `baseLocale = { url, snapshot, storage }`.
|
|
|
69
152
|
|
|
70
153
|
If page redirects to `/login` (auth blocker) → stop. Print: *"Auth blocked — cannot capture live page. Re-run with `--offline` flag, or unblock auth first."*
|
|
71
154
|
|
|
72
|
-
## Phase 3 — Switch locale (LIVE only) — detect mechanism + storage delta
|
|
155
|
+
## Phase 3 — Switch locale (web, LIVE only) — detect mechanism + storage delta
|
|
156
|
+
|
|
157
|
+
> **Mobile**: see "Mobile Phase 3" in the Mobile (Appium) variant section above. Phases 3.5 and 3.6 are web-only — skip on mobile.
|
|
73
158
|
|
|
74
159
|
Goal: identify (a) HOW to switch the app to the target locale, and (b) WHAT app-side storage state ends up holding the locale preference so a fresh BrowserContext can be primed identically without driving the UI.
|
|
75
160
|
|
|
@@ -149,7 +234,9 @@ If verification fails → drop confidence one tier, ask user.
|
|
|
149
234
|
|
|
150
235
|
Save mechanism + verified storage delta to memory for Phase 6.
|
|
151
236
|
|
|
152
|
-
## Phase 4 — Diff base ↔ target (LIVE only)
|
|
237
|
+
## Phase 4 — Diff base ↔ target (LIVE only, web + mobile)
|
|
238
|
+
|
|
239
|
+
> **Mobile**: use Appium page-source snapshots instead of Playwright snapshots. Logic is identical.
|
|
153
240
|
|
|
154
241
|
For each candidate from Phase 1 that survived Phase 2:
|
|
155
242
|
- Find the SAME element in target-locale snapshot (match by `role`+position, by `aria-label`, by neighbor structure)
|
|
@@ -183,7 +270,9 @@ If user wants to edit a row → fall through to a per-row prompt for `var name`
|
|
|
183
270
|
|
|
184
271
|
OFFLINE mode: same table but `target text` column blank — user fills via subsequent prompts or by editing the overlay file after the skill finishes.
|
|
185
272
|
|
|
186
|
-
## Phase 6 — Apply changes (always, after confirmation)
|
|
273
|
+
## Phase 6 — Apply changes (web, always, after confirmation)
|
|
274
|
+
|
|
275
|
+
> **Mobile**: follow "Mobile Phase 6" in the Mobile (Appium) variant section — only 6a/6b/6c apply. Skip 6d and 6f entirely.
|
|
187
276
|
|
|
188
277
|
For each confirmed row:
|
|
189
278
|
|
|
@@ -211,7 +300,7 @@ nav_about: 'About SAA 2025'
|
|
|
211
300
|
nav_awards: 'Awards Info'
|
|
212
301
|
```
|
|
213
302
|
|
|
214
|
-
**6d. (URL/query mechanism only) Update Pages selectors**
|
|
303
|
+
**6d. (web, URL/query mechanism only) Update Pages selectors**
|
|
215
304
|
|
|
216
305
|
URL prefix:
|
|
217
306
|
|
|
@@ -237,7 +326,9 @@ Query param mechanism: append `query_suffix` similarly.
|
|
|
237
326
|
|
|
238
327
|
UI switcher: do NOT modify Pages. Write storage delta into `specs/generated/locale-config.json` (sibling of generated `base.ts` + `locale-fixture.ts`) (6f).
|
|
239
328
|
|
|
240
|
-
**6f. Storage injection config — `specs/generated/locale-config.json` (sibling of generated `base.ts` + `locale-fixture.ts`)**
|
|
329
|
+
**6f. (web-only) Storage injection config — `specs/generated/locale-config.json` (sibling of generated `base.ts` + `locale-fixture.ts`)**
|
|
330
|
+
|
|
331
|
+
> **Mobile**: skip 6f entirely. wdio `resolveLocaleCaps` sets the device locale via Appium caps — no Playwright fixture needed.
|
|
241
332
|
|
|
242
333
|
Always write `specs/generated/locale-config.json` (sibling of generated `base.ts` + `locale-fixture.ts`) with the verified storage delta from Phase 3.5/3.6. Consumed by `specs/locale-fixture.ts` (auto-generated alongside `specs/base.ts`), which wraps Playwright's context and calls `addInitScript` + `addCookies` BEFORE the first navigation.
|
|
243
334
|
|
|
@@ -269,15 +360,21 @@ Print summary:
|
|
|
269
360
|
- N selectors converted to `{{var}}`
|
|
270
361
|
- M base keys added to `test-data/<feature>.yaml`
|
|
271
362
|
- K overlay keys written to `test-data/<feature>.<locale>.yaml`
|
|
272
|
-
- Pages selectors updated: yes/no
|
|
273
|
-
- Locale-switching mechanism: URL prefix `/en` / query `?lang=en` / UI switcher / manual
|
|
363
|
+
- Pages selectors updated: yes/no (web only; N/A on mobile)
|
|
364
|
+
- Locale-switching mechanism: URL prefix `/en` / query `?lang=en` / UI switcher / manual (web) | device locale via `appium:language`/`appium:locale` (mobile)
|
|
274
365
|
|
|
275
366
|
Suggest:
|
|
276
367
|
|
|
277
368
|
```
|
|
369
|
+
# web
|
|
278
370
|
/sungen:run-test <name> --env <locale>
|
|
371
|
+
|
|
372
|
+
# mobile
|
|
373
|
+
SUNGEN_ENV=<locale> npm run test:mobile
|
|
279
374
|
```
|
|
280
375
|
|
|
376
|
+
> **Mobile note**: if the device locale code differs from the overlay env name (e.g. `SUNGEN_ENV=staging-ja` but device needs `ja`), set `SUNGEN_LOCALE=ja` — wdio `resolveLocaleCaps` reads it as an override.
|
|
377
|
+
|
|
281
378
|
## Multi-feature screens
|
|
282
379
|
|
|
283
380
|
If the screen has multiple `.feature` files (e.g. `home.feature` + `home-modal.feature`), repeat Phase 1 → Phase 6 for each feature file with its own selectors + test-data pair. Phase 2 + 3 run **once per screen** — mechanism is the same. Phase 4 runs per feature because UI scope differs.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sun-asterisk/sungen",
|
|
3
|
-
"version": "3.2.2-beta.
|
|
3
|
+
"version": "3.2.2-beta.14",
|
|
4
4
|
"description": "Deterministic E2E Test Compiler - Gherkin + Selectors → Playwright tests",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"copy-templates": "mkdir -p dist/generators/test-generator/adapters/playwright/templates/steps && mkdir -p dist/generators/test-generator/templates && mkdir -p dist/orchestrator/templates && mkdir -p dist/dashboard/templates && cp -r src/generators/test-generator/adapters/playwright/templates/*.hbs dist/generators/test-generator/adapters/playwright/templates/ 2>/dev/null || true && cp -r src/generators/test-generator/adapters/playwright/templates/steps dist/generators/test-generator/adapters/playwright/templates/ && mkdir -p dist/generators/test-generator/adapters/appium/templates/steps && cp -r src/generators/test-generator/adapters/appium/templates/*.hbs dist/generators/test-generator/adapters/appium/templates/ 2>/dev/null || true && cp -r src/generators/test-generator/adapters/appium/templates/steps dist/generators/test-generator/adapters/appium/templates/ && cp src/generators/test-generator/templates/*.hbs dist/generators/test-generator/templates/ 2>/dev/null || true && cp -r src/orchestrator/templates/* dist/orchestrator/templates/ && cp src/dashboard/templates/index.html dist/dashboard/templates/index.html && mkdir -p dist/harness/catalog && cp src/harness/catalog/*.yaml dist/harness/catalog/",
|
|
13
13
|
"build:dashboard": "cd ../../dashboard && npm install --silent && npm run build && cd - && cp ../../dashboard/dist/index.html src/dashboard/templates/index.html",
|
|
14
14
|
"dev": "tsx src/cli/index.ts",
|
|
15
|
-
"test": "tsx tests/golden/run.ts && tsx tests/audit/run.ts && tsx tests/ingest/run.ts && tsx tests/eval/run.ts && tsx tests/exporter/run.ts && tsx tests/exporter/feature-parser-category.run.ts && tsx tests/exporter/api-detail-sheet.run.ts && tsx tests/api-runtime/base-path-url-join.run.ts && tsx tests/capabilities/run.ts && tsx tests/openapi/run.ts && tsx tests/packaging/run.ts && tsx tests/journey/run.ts",
|
|
15
|
+
"test": "tsx tests/golden/run.ts && tsx tests/audit/run.ts && tsx tests/ingest/run.ts && tsx tests/eval/run.ts && tsx tests/exporter/run.ts && tsx tests/exporter/feature-parser-category.run.ts && tsx tests/exporter/api-detail-sheet.run.ts && tsx tests/exporter/mobile-app-id.run.ts && tsx tests/api-runtime/base-path-url-join.run.ts && tsx tests/capabilities/run.ts && tsx tests/openapi/run.ts && tsx tests/packaging/run.ts && tsx tests/generate-hint/run.ts && tsx tests/template-assertion/run.ts && tsx tests/journey/run.ts",
|
|
16
16
|
"test:update": "tsx tests/golden/run.ts --update && tsx tests/audit/run.ts --update && tsx tests/ingest/run.ts --update",
|
|
17
17
|
"prepublishOnly": "npm run build:dashboard && npm run build"
|
|
18
18
|
},
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"node": ">=18.0.0"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@sungen/driver-ui": "3.2.2-beta.
|
|
36
|
+
"@sungen/driver-ui": "3.2.2-beta.14",
|
|
37
37
|
"@anthropic-ai/sdk": "^0.71.0",
|
|
38
38
|
"@babel/parser": "^7.28.5",
|
|
39
39
|
"@babel/traverse": "^7.28.5",
|
|
@@ -10,8 +10,12 @@
|
|
|
10
10
|
* load first, then `LOCAL_DRIVERS` (db → api), then `core` — i.e. the historical ui → db → api → core
|
|
11
11
|
* composition order, so pattern composition / compiled output is byte-identical.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* An OPT-IN driver that isn't installed is simply skipped (capabilities are opt-in). A BUNDLED
|
|
14
|
+
* driver (a core dependency, e.g. `@sungen/driver-ui`) that fails to resolve is NOT opt-in — it
|
|
15
|
+
* signals a broken/incomplete install, so it warns LOUDLY instead of degrading silently to
|
|
16
|
+
* "Unrecognized step pattern". Errors thrown *inside* a present driver propagate — only a genuinely
|
|
17
|
+
* absent package is swallowed (resolve-then-require). Nothing here throws: the CLI must keep working
|
|
18
|
+
* (`--version`, `capability remove`) even when a driver is missing/broken.
|
|
15
19
|
*/
|
|
16
20
|
import { createRequire } from 'module';
|
|
17
21
|
import * as path from 'path';
|
|
@@ -25,6 +29,15 @@ import { LOCAL_DRIVERS, registerCoreCapability } from './builtins';
|
|
|
25
29
|
*/
|
|
26
30
|
const EXTERNAL_DRIVERS = ['@sungen/driver-ui', '@sungen/driver-db', '@sungen/driver-api', '@sungen/driver-mobile'];
|
|
27
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Bundled (non-opt-in) drivers: shipped as a core dependency (`drivers.yaml` `bundled: true`), so the
|
|
34
|
+
* project gets them without `sungen capability add`. If one fails to resolve it is NOT a declined
|
|
35
|
+
* opt-in — it is a broken install (e.g. a dev workspace that skipped `npm install`, so the workspace
|
|
36
|
+
* symlink for the package is missing). Surface it LOUDLY: otherwise the entire UI step vocabulary is
|
|
37
|
+
* absent and every step silently compiles to "Unrecognized step pattern" with no clue why.
|
|
38
|
+
*/
|
|
39
|
+
const BUNDLED_DRIVERS = new Set(['@sungen/driver-ui']);
|
|
40
|
+
|
|
28
41
|
function loadExternalDriver(name: string): void {
|
|
29
42
|
// Resolve from the user's PROJECT first, then from core's own location. Opt-in drivers
|
|
30
43
|
// (`sungen capability add db|api`) install into the PROJECT's node_modules — a globally-installed
|
|
@@ -51,7 +64,20 @@ function loadExternalDriver(name: string): void {
|
|
|
51
64
|
}
|
|
52
65
|
return; // found at this anchor (loaded or warned) — don't fall through to other anchors
|
|
53
66
|
}
|
|
54
|
-
//
|
|
67
|
+
// Not resolvable at ANY anchor.
|
|
68
|
+
if (BUNDLED_DRIVERS.has(name)) {
|
|
69
|
+
// A bundled default is missing → broken install, not a declined opt-in. Warn loudly so the user
|
|
70
|
+
// sees the real cause instead of a wall of "Unrecognized step pattern".
|
|
71
|
+
console.warn(
|
|
72
|
+
`⚠ sungen: the bundled driver "${name}" could not be resolved — its step patterns are MISSING, ` +
|
|
73
|
+
`so steps will compile to "// Unrecognized step pattern".\n` +
|
|
74
|
+
` This means the install is incomplete. Fix:\n` +
|
|
75
|
+
` • in an app project: run \`npm install\`\n` +
|
|
76
|
+
` • in the sungen monorepo: run \`npm install\` at the repo ROOT (bootstraps the workspace symlink for ${name})`,
|
|
77
|
+
);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// Opt-in driver not installed — capabilities are opt-in, so skip silently.
|
|
55
81
|
}
|
|
56
82
|
|
|
57
83
|
export function discoverAndRegisterCapabilities(): void {
|
|
@@ -7,6 +7,7 @@ export function registerAddFlowCommand(program: Command): void {
|
|
|
7
7
|
.requiredOption('--flow <name>', 'Flow name')
|
|
8
8
|
.option('-p, --path <path>', 'Starting page route path (e.g. /login)')
|
|
9
9
|
.option('-d, --description <text>', 'Flow description')
|
|
10
|
+
.option('--reach <steps>', 'Mobile navigation recipe for the flow\'s starting screen: a ">"-separated list of nav steps from app launch, e.g. "tap [My Page] tab > tap [Settings] row". Scaffolded into the Background. Ignored (with a warning) on web projects.')
|
|
10
11
|
.action(async (options) => {
|
|
11
12
|
try {
|
|
12
13
|
const { FlowManager } = require('../../orchestrator/flow-manager');
|
|
@@ -16,6 +17,7 @@ export function registerAddFlowCommand(program: Command): void {
|
|
|
16
17
|
name: options.flow,
|
|
17
18
|
path: options.path,
|
|
18
19
|
description: options.description,
|
|
20
|
+
reach: options.reach,
|
|
19
21
|
});
|
|
20
22
|
} catch (error) {
|
|
21
23
|
console.error('Error:', error instanceof Error ? error.message : error);
|
package/src/cli/commands/add.ts
CHANGED
|
@@ -11,6 +11,8 @@ export function registerAddCommand(program: Command): void {
|
|
|
11
11
|
.option('-c, --capture', 'Auto-capture a live-page screenshot to requirements/ui/ (requires --path)')
|
|
12
12
|
.option('-f, --feature <name>', 'Add additional feature file to existing screen')
|
|
13
13
|
.option('-d, --description <text>', 'Screen description')
|
|
14
|
+
.option('--platform <platform>', 'Optional per-OS tag: android | ios. Emits @platform:android/ios on the stub for OS-specific screens. Does NOT affect directory routing (always qa/screens/). Omit for cross-platform mobile or web.')
|
|
15
|
+
.option('--reach <steps>', 'Mobile navigation recipe (in-app web-path analog): a ">"-separated list of nav steps from app launch to this screen, e.g. "tap [My Page] tab > tap [Settings] row". Scaffolded into the Background; capture-mobile replays it. Ignored (with a warning) on web projects.')
|
|
14
16
|
.option('--figma <url>', "Figma share URL — fetch design node and generate spec_figma.md + ui/*.png. QUOTE the URL ('...'): the '&' in Figma links backgrounds the shell otherwise.")
|
|
15
17
|
.option('--refresh', 'Bypass Figma cache and re-fetch from API (use with --figma)')
|
|
16
18
|
.option('--scale <n>', 'PNG export scale factor (default: 2)', parseFloat, 2)
|
|
@@ -38,6 +40,8 @@ export function registerAddCommand(program: Command): void {
|
|
|
38
40
|
capture: options.capture,
|
|
39
41
|
feature: options.feature,
|
|
40
42
|
description: options.description,
|
|
43
|
+
platform: options.platform,
|
|
44
|
+
reach: options.reach,
|
|
41
45
|
});
|
|
42
46
|
} else {
|
|
43
47
|
console.log(`Screen "${options.screen}" already exists — refreshing Figma assets only.`);
|
|
@@ -8,12 +8,34 @@ import {
|
|
|
8
8
|
} from '../../harness/capability';
|
|
9
9
|
import { buildPlan, MANUAL_REASONS } from '../../harness/capability-plan';
|
|
10
10
|
import { adapterRegistry } from '../../generators/test-generator/adapters';
|
|
11
|
+
import { emitMobileRuntime, installMobileDriver } from '../../orchestrator/mobile-runtime-scaffolder';
|
|
11
12
|
|
|
12
13
|
/** The running CLI's (core) version — opt-in drivers install in lockstep with it (not `@latest`). */
|
|
13
14
|
function coreVersion(): string {
|
|
14
15
|
try { return require('../../../package.json').version || 'latest'; } catch { return 'latest'; }
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Add `test:mobile` to the project's package.json scripts (if absent).
|
|
20
|
+
* Idempotent — skips if the script is already set.
|
|
21
|
+
* Returns true if the script was added, false if already present or no package.json.
|
|
22
|
+
*/
|
|
23
|
+
function addMobileScript(cwd: string): boolean {
|
|
24
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
25
|
+
if (!fs.existsSync(pkgPath)) return false;
|
|
26
|
+
let pkg: Record<string, any>;
|
|
27
|
+
try {
|
|
28
|
+
pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (!pkg.scripts || typeof pkg.scripts !== 'object') pkg.scripts = {};
|
|
33
|
+
if (pkg.scripts['test:mobile']) return false;
|
|
34
|
+
pkg.scripts['test:mobile'] = 'wdio run wdio.conf.ts';
|
|
35
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
17
39
|
/**
|
|
18
40
|
* Verify a capability driver (db/api/…) installed into the PROJECT exposes the capability SPI entry
|
|
19
41
|
* (`sungenDriver.register`). Resolved from the project's node_modules so it works with a globally
|
|
@@ -137,13 +159,33 @@ export function registerCapabilityCommand(program: Command): void {
|
|
|
137
159
|
.command('add <driver>')
|
|
138
160
|
.description('Enable a driver: install its package (if external) + record it in qa/capabilities.yaml')
|
|
139
161
|
.option('--skip-install', 'Only record in the profile; do not run npm install')
|
|
140
|
-
.
|
|
162
|
+
.option('--force', 'Switch the project platform even if a different one is already set (sungen is one platform per project)')
|
|
163
|
+
.action((driver: string, o: { skipInstall?: boolean; force?: boolean }) => {
|
|
141
164
|
try {
|
|
142
165
|
const cwd = process.cwd();
|
|
143
166
|
const meta = driverMeta(driver);
|
|
144
167
|
if (!meta) {
|
|
145
168
|
throw new Error(`Unknown driver "${driver}". Known: ${Object.keys(loadDriverCatalog()).join(', ')}`);
|
|
146
169
|
}
|
|
170
|
+
|
|
171
|
+
// Guard: a platform driver (web/mobile) sets the project's SINGLE platform. If a DIFFERENT
|
|
172
|
+
// platform is already set, adding this one would silently FLIP it (e.g. web → mobile) and make
|
|
173
|
+
// the existing feature files compile for the wrong runtime. Refuse unless --force, and point at
|
|
174
|
+
// the correct path for a web+mobile repo (two separate project folders).
|
|
175
|
+
const current = readCapabilities(cwd);
|
|
176
|
+
if (meta.kind === 'platform' && current.platform && current.platform !== driver && !o.force) {
|
|
177
|
+
const initHint = driver === 'mobile' ? ' --mobile --app-package <pkg>' : ' --base-url <url>';
|
|
178
|
+
console.error(
|
|
179
|
+
`✗ This project's platform is already "${current.platform}". Adding "${driver}" would SWITCH it to "${driver}".\n` +
|
|
180
|
+
` sungen is ONE platform per project — this is NOT "${current.platform} + ${driver}" together.\n` +
|
|
181
|
+
` • Want BOTH web and mobile? Keep this project and create a SEPARATE folder for ${driver}:\n` +
|
|
182
|
+
` sungen init ${driver}-tests${initHint}\n` +
|
|
183
|
+
` • Really converting ${current.platform} → ${driver}? Re-run with --force\n` +
|
|
184
|
+
` (your existing ${current.platform} feature files would then compile for ${driver}).`,
|
|
185
|
+
);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
|
|
147
189
|
const adapterName = meta.adapter || meta.id;
|
|
148
190
|
const bundled = adapterRegistry.hasAdapter(adapterName); // e.g. `web` is bundled in Phase 2a
|
|
149
191
|
|
|
@@ -176,6 +218,27 @@ export function registerCapabilityCommand(program: Command): void {
|
|
|
176
218
|
const platform = meta.kind === 'platform' ? driver : profile.platform;
|
|
177
219
|
writeCapabilities(cwd, { platform, enabled });
|
|
178
220
|
console.log(`✓ capability "${driver}" enabled${meta.kind === 'platform' ? ` (platform=${driver})` : ''}.`);
|
|
221
|
+
|
|
222
|
+
// Emit the mobile runtime harness when the mobile platform driver is added.
|
|
223
|
+
// Idempotent: emitMobileRuntime skips files that already exist.
|
|
224
|
+
if (platform === 'mobile') {
|
|
225
|
+
console.log('');
|
|
226
|
+
console.log('🔧 Emitting mobile runtime harness...');
|
|
227
|
+
const result = emitMobileRuntime(cwd, { enabled: true });
|
|
228
|
+
for (const f of result.created) console.log(` ✓ created ${f}`);
|
|
229
|
+
for (const f of result.skipped) console.log(` – skipped ${f} (already exists)`);
|
|
230
|
+
|
|
231
|
+
// Add test:mobile script to package.json (idempotent)
|
|
232
|
+
if (addMobileScript(cwd)) {
|
|
233
|
+
console.log(' ✓ created package.json scripts[test:mobile]');
|
|
234
|
+
} else {
|
|
235
|
+
console.log(' – skipped package.json scripts[test:mobile] (already exists)');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Best-effort driver install — non-fatal so capability add still succeeds offline.
|
|
239
|
+
installMobileDriver(cwd);
|
|
240
|
+
}
|
|
241
|
+
|
|
179
242
|
console.log('');
|
|
180
243
|
} catch (e) {
|
|
181
244
|
console.error('Error:', e instanceof Error ? e.message : e);
|