@sun-asterisk/sungen 3.2.3-beta.3 → 3.2.4
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/feedback.d.ts +11 -0
- package/dist/cli/commands/feedback.d.ts.map +1 -1
- package/dist/cli/commands/feedback.js +154 -57
- package/dist/cli/commands/feedback.js.map +1 -1
- package/dist/cli/commands/login.d.ts +19 -0
- package/dist/cli/commands/login.d.ts.map +1 -0
- package/dist/cli/commands/login.js +76 -0
- package/dist/cli/commands/login.js.map +1 -0
- package/dist/cli/commands/qa-feedback.d.ts +14 -0
- package/dist/cli/commands/qa-feedback.d.ts.map +1 -0
- package/dist/cli/commands/qa-feedback.js +83 -0
- package/dist/cli/commands/qa-feedback.js.map +1 -0
- package/dist/cli/index.js +4 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
- package/dist/dashboard/snapshot-builder.js +108 -33
- package/dist/dashboard/snapshot-builder.js.map +1 -1
- package/dist/dashboard/templates/index.html +23 -23
- package/dist/dashboard/types.d.ts +4 -0
- package/dist/dashboard/types.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.js +11 -4
- package/dist/exporters/csv-exporter.js.map +1 -1
- package/dist/exporters/feature-parser.d.ts +4 -0
- package/dist/exporters/feature-parser.d.ts.map +1 -1
- package/dist/exporters/feature-parser.js +7 -2
- package/dist/exporters/feature-parser.js.map +1 -1
- package/dist/exporters/json-exporter.d.ts.map +1 -1
- package/dist/exporters/json-exporter.js +11 -3
- package/dist/exporters/json-exporter.js.map +1 -1
- package/dist/exporters/playwright-report-parser.d.ts +21 -2
- package/dist/exporters/playwright-report-parser.d.ts.map +1 -1
- package/dist/exporters/playwright-report-parser.js +52 -12
- package/dist/exporters/playwright-report-parser.js.map +1 -1
- package/dist/exporters/scenario-merger.d.ts.map +1 -1
- package/dist/exporters/scenario-merger.js +50 -2
- package/dist/exporters/scenario-merger.js.map +1 -1
- package/dist/exporters/spec-parser.js +1 -1
- package/dist/exporters/spec-parser.js.map +1 -1
- package/dist/exporters/test-data-resolver.d.ts +7 -0
- package/dist/exporters/test-data-resolver.d.ts.map +1 -1
- package/dist/exporters/test-data-resolver.js +13 -0
- package/dist/exporters/test-data-resolver.js.map +1 -1
- package/dist/exporters/types.d.ts +6 -0
- package/dist/exporters/types.d.ts.map +1 -1
- package/dist/exporters/xlsx-exporter.d.ts.map +1 -1
- package/dist/exporters/xlsx-exporter.js +131 -42
- package/dist/exporters/xlsx-exporter.js.map +1 -1
- package/dist/generators/gherkin-parser/index.d.ts +15 -0
- package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
- package/dist/generators/gherkin-parser/index.js +33 -1
- package/dist/generators/gherkin-parser/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/check-action.hbs +29 -8
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/scroll-action.hbs +21 -17
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/toggle-action.hbs +3 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/uncheck-action.hbs +26 -6
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/is-hidden-assertion.hbs +3 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +14 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/tap-top-action.hbs +3 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +9 -2
- package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +115 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +1 -1
- package/dist/harness/script-check.d.ts.map +1 -1
- package/dist/harness/script-check.js +10 -5
- package/dist/harness/script-check.js.map +1 -1
- package/dist/orchestrator/ai-skills/writer.d.ts.map +1 -1
- package/dist/orchestrator/ai-skills/writer.js +46 -4
- package/dist/orchestrator/ai-skills/writer.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/add-flow.md +52 -5
- package/dist/orchestrator/templates/ai-src/commands/create-test.md +7 -0
- package/dist/orchestrator/templates/ai-src/commands/feedback.md +6 -6
- package/dist/orchestrator/templates/ai-src/commands/locale.md +26 -4
- package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +5 -4
- package/dist/orchestrator/templates/wdio.conf.ts +39 -3
- package/dist/tools/sungen-api/edit-in-editor.d.ts +15 -0
- package/dist/tools/sungen-api/edit-in-editor.d.ts.map +1 -0
- package/dist/tools/sungen-api/edit-in-editor.js +85 -0
- package/dist/tools/sungen-api/edit-in-editor.js.map +1 -0
- package/dist/tools/sungen-api/feedback-client.d.ts +24 -0
- package/dist/tools/sungen-api/feedback-client.d.ts.map +1 -0
- package/dist/tools/sungen-api/feedback-client.js +53 -0
- package/dist/tools/sungen-api/feedback-client.js.map +1 -0
- package/dist/tools/sungen-api/read-stdin.d.ts +6 -0
- package/dist/tools/sungen-api/read-stdin.d.ts.map +1 -0
- package/dist/tools/sungen-api/read-stdin.js +19 -0
- package/dist/tools/sungen-api/read-stdin.js.map +1 -0
- package/dist/tools/sungen-auth/api-base-url.d.ts +14 -0
- package/dist/tools/sungen-auth/api-base-url.d.ts.map +1 -0
- package/dist/tools/sungen-auth/api-base-url.js +28 -0
- package/dist/tools/sungen-auth/api-base-url.js.map +1 -0
- package/dist/tools/sungen-auth/credentials-store.d.ts +19 -0
- package/dist/tools/sungen-auth/credentials-store.d.ts.map +1 -0
- package/dist/tools/sungen-auth/credentials-store.js +94 -0
- package/dist/tools/sungen-auth/credentials-store.js.map +1 -0
- package/dist/tools/sungen-auth/login-flow.d.ts +33 -0
- package/dist/tools/sungen-auth/login-flow.d.ts.map +1 -0
- package/dist/tools/sungen-auth/login-flow.js +107 -0
- package/dist/tools/sungen-auth/login-flow.js.map +1 -0
- package/dist/tools/sungen-auth/loopback-server.d.ts +20 -0
- package/dist/tools/sungen-auth/loopback-server.d.ts.map +1 -0
- package/dist/tools/sungen-auth/loopback-server.js +132 -0
- package/dist/tools/sungen-auth/loopback-server.js.map +1 -0
- package/dist/tools/sungen-auth/open-browser.d.ts +10 -0
- package/dist/tools/sungen-auth/open-browser.d.ts.map +1 -0
- package/dist/tools/sungen-auth/open-browser.js +43 -0
- package/dist/tools/sungen-auth/open-browser.js.map +1 -0
- package/dist/tools/sungen-auth/pkce.d.ts +13 -0
- package/dist/tools/sungen-auth/pkce.d.ts.map +1 -0
- package/dist/tools/sungen-auth/pkce.js +27 -0
- package/dist/tools/sungen-auth/pkce.js.map +1 -0
- package/dist/tools/sungen-auth/sungen-auth-types.d.ts +41 -0
- package/dist/tools/sungen-auth/sungen-auth-types.d.ts.map +1 -0
- package/dist/tools/sungen-auth/sungen-auth-types.js +7 -0
- package/dist/tools/sungen-auth/sungen-auth-types.js.map +1 -0
- package/package.json +3 -3
- package/src/cli/commands/feedback.ts +123 -51
- package/src/cli/commands/login.ts +75 -0
- package/src/cli/commands/qa-feedback.ts +77 -0
- package/src/cli/index.ts +4 -0
- package/src/dashboard/snapshot-builder.ts +100 -27
- package/src/dashboard/templates/index.html +23 -23
- package/src/dashboard/types.ts +5 -1
- package/src/exporters/csv-exporter.ts +10 -4
- package/src/exporters/feature-parser.ts +7 -2
- package/src/exporters/json-exporter.ts +11 -3
- package/src/exporters/playwright-report-parser.ts +50 -11
- package/src/exporters/scenario-merger.ts +49 -3
- package/src/exporters/spec-parser.ts +1 -1
- package/src/exporters/test-data-resolver.ts +13 -0
- package/src/exporters/types.ts +6 -0
- package/src/exporters/xlsx-exporter.ts +155 -49
- package/src/generators/gherkin-parser/index.ts +44 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/check-action.hbs +29 -8
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/scroll-action.hbs +21 -17
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/toggle-action.hbs +3 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/uncheck-action.hbs +26 -6
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/is-hidden-assertion.hbs +3 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +14 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/tap-top-action.hbs +3 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +9 -2
- package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +115 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +1 -1
- package/src/harness/script-check.ts +10 -5
- package/src/orchestrator/ai-skills/writer.ts +39 -4
- package/src/orchestrator/templates/ai-src/commands/add-flow.md +52 -5
- package/src/orchestrator/templates/ai-src/commands/create-test.md +7 -0
- package/src/orchestrator/templates/ai-src/commands/feedback.md +6 -6
- package/src/orchestrator/templates/ai-src/commands/locale.md +26 -4
- package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +5 -4
- package/src/orchestrator/templates/wdio.conf.ts +39 -3
- package/src/tools/sungen-api/edit-in-editor.ts +48 -0
- package/src/tools/sungen-api/feedback-client.ts +62 -0
- package/src/tools/sungen-api/read-stdin.ts +15 -0
- package/src/tools/sungen-auth/api-base-url.ts +23 -0
- package/src/tools/sungen-auth/credentials-store.ts +57 -0
- package/src/tools/sungen-auth/login-flow.ts +135 -0
- package/src/tools/sungen-auth/loopback-server.ts +113 -0
- package/src/tools/sungen-auth/open-browser.ts +42 -0
- package/src/tools/sungen-auth/pkce.ts +27 -0
- package/src/tools/sungen-auth/sungen-auth-types.ts +45 -0
package/dist/generators/test-generator/adapters/appium/templates/steps/actions/scroll-action.hbs
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
if (driver.isIOS) {
|
|
2
|
-
// iOS (XCUITest) has no UiScrollable.
|
|
3
|
-
// below-fold element
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
for (let __i = 0; __i < 10 && !(await __t.isDisplayed().catch(() => false)); __i++) {
|
|
9
|
-
await driver.execute('mobile: swipe', { direction: 'up' });
|
|
10
|
-
await driver.pause(400); // settle: let the app render between swipes (don't flood execute/sync)
|
|
11
|
-
}
|
|
2
|
+
// iOS (XCUITest) has no UiScrollable. Delegate to __ensureVisible: geometry-gated small drags
|
|
3
|
+
// (a below-fold Flutter element stays in the tree with a 0x0 rect and isDisplayed() is unreliable
|
|
4
|
+
// on XCUITest, so a full-screen-swipe loop gated on isDisplayed overshoots the target — it swiped
|
|
5
|
+
// clean past section headers on the converter Settings screen). NOTE: this does NOT fix an app
|
|
6
|
+
// that crashes mid-scroll on its own — that's an app-stability issue, not codegen (issue #394).
|
|
7
|
+
await __ensureVisible({{> appium-selector-expr}});
|
|
12
8
|
} else {
|
|
9
|
+
const __t = $({{> appium-selector-expr}});
|
|
13
10
|
{{#if (eq strategy 'accessibility-id')}}
|
|
14
|
-
//
|
|
15
|
-
// overshoot (which would drop a virtualized Flutter item from the a11y tree).
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
await driver.execute('mobile: scrollGesture', { left: 60, top: 420, width: 960, height: 1320, direction: 'down', percent: 0.6 });
|
|
11
|
+
// Optional first try: UiScrollable scrolls until the target's content-desc is on screen, then
|
|
12
|
+
// stops — no overshoot (which would drop a virtualized Flutter item from the a11y tree). This
|
|
13
|
+
// no-ops (rather than throwing past callers) when the app exposes no `scrollable(true)` node —
|
|
14
|
+
// common on Flutter/Compose, where the scroll view isn't a native Android ScrollView — so it
|
|
15
|
+
// can't be relied on alone; the bounded loop below is what actually guarantees reachability.
|
|
16
|
+
await $('android=new UiScrollable(new UiSelector().scrollable(true)).scrollIntoView(new UiSelector().descriptionContains("{{escapeQuotes value}}"))').catch(() => {}); // no scrollable node — fall through to the gesture loop
|
|
21
17
|
{{/if}}
|
|
18
|
+
// Bounded scroll-gesture loop (mirrors the iOS branch above): a single 0.6-percent scroll can
|
|
19
|
+
// land a near item but not a deeper one below the fold, so keep scrolling down until the target
|
|
20
|
+
// is displayed. Capped (10) so a missing target can't loop forever; a short pause between scrolls
|
|
21
|
+
// lets a virtualized Flutter/RN list render before the next gesture.
|
|
22
|
+
for (let __i = 0; __i < 10 && !(await __t.isDisplayed().catch(() => false)); __i++) {
|
|
23
|
+
await driver.execute('mobile: scrollGesture', { left: 60, top: 420, width: 960, height: 1320, direction: 'down', percent: 0.6 });
|
|
24
|
+
await driver.pause(400); // settle: let the app render between scrolls (don't flood execute/sync)
|
|
25
|
+
}
|
|
22
26
|
}
|
package/dist/generators/test-generator/adapters/appium/templates/steps/actions/toggle-action.hbs
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
on/off state idempotently instead. --}}
|
|
7
7
|
await (async () => {
|
|
8
8
|
const e = await {{> locator}};
|
|
9
|
+
// Fail loud with the REAL cause when the element never resolved — getElementRect(undefined)
|
|
10
|
+
// otherwise throws a cryptic `Malformed type for "elementId"` that masks the not-found (#430).
|
|
11
|
+
if (!e.elementId) throw new Error((e as any).error?.message || `Element not found: ${e.selector}`);
|
|
9
12
|
const r = await driver.getElementRect(e.elementId);
|
|
10
13
|
if (r && r.width) {
|
|
11
14
|
// Trailing switch: ~right edge, vertical centre. The inset clears the label/track padding while
|
package/dist/generators/test-generator/adapters/appium/templates/steps/actions/uncheck-action.hbs
CHANGED
|
@@ -1,8 +1,28 @@
|
|
|
1
|
-
{{!-- WebdriverIO/Appium has no .uncheck().
|
|
2
|
-
|
|
1
|
+
{{!-- WebdriverIO/Appium has no .uncheck(). Ensure the toggle ends up OFF: tap only if currently on,
|
|
2
|
+
so the step is idempotent under repeat runs / persisted `noReset` state. State read + tap
|
|
3
|
+
method mirror check-action.hbs (see there for why): platform-correct attribute read
|
|
4
|
+
(`checked`/`value`) with an isSelected() fallback, then a trailing-edge coordinate tap rather
|
|
5
|
+
than a centre .click() (a centre tap on a full-width composite row hits the label, not the
|
|
6
|
+
switch). --}}
|
|
7
|
+
await (async () => {
|
|
3
8
|
const __cb = await {{> locator}};
|
|
9
|
+
// Fail loud with the REAL cause when the element never resolved — getElementRect(undefined)
|
|
10
|
+
// otherwise throws a cryptic `Malformed type for "elementId"` that masks the not-found (#430).
|
|
11
|
+
if (!__cb.elementId) throw new Error((__cb as any).error?.message || `Element not found: ${__cb.selector}`);
|
|
4
12
|
let __on = false;
|
|
5
|
-
try { __on = await __cb.
|
|
6
|
-
catch { try { __on =
|
|
7
|
-
if (__on)
|
|
8
|
-
|
|
13
|
+
try { __on = driver.isIOS ? (await __cb.getAttribute('value')) === '1' : (await __cb.getAttribute('checked')) === 'true'; }
|
|
14
|
+
catch { try { __on = await __cb.isSelected(); } catch { /* state unreadable — leave as-is */ } }
|
|
15
|
+
if (!__on) return;
|
|
16
|
+
|
|
17
|
+
const r = await driver.getElementRect(__cb.elementId);
|
|
18
|
+
if (r && r.width) {
|
|
19
|
+
// Trailing switch: ~right edge, vertical centre. The inset clears the label/track padding while
|
|
20
|
+
// staying on the switch even on a full-width composite row.
|
|
21
|
+
const x = Math.round(r.x + r.width - Math.max(48, r.width * 0.08));
|
|
22
|
+
const y = Math.round(r.y + r.height / 2);
|
|
23
|
+
if (driver.isIOS) { await driver.execute('mobile: tap', { x, y }); }
|
|
24
|
+
else { await driver.execute('mobile: clickGesture', { x, y }); }
|
|
25
|
+
} else {
|
|
26
|
+
await __cb.click();
|
|
27
|
+
}
|
|
28
|
+
})();
|
|
@@ -1 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
{{!-- plain=true: no scroll-into-view. An element asserted hidden is expected absent, so hunting for
|
|
2
|
+
it by scrolling is pointless and would strand later same-scenario steps that sit above. --}}
|
|
3
|
+
await expect({{> locator plain=true}}).not.toBeDisplayed();
|
|
@@ -1 +1,15 @@
|
|
|
1
|
+
{{!-- iOS: XCUITest's `visible` attribute false-negatives on real, on-screen elements (snapshot
|
|
2
|
+
artifact, amplified on Flutter) — the reliable oracle there is GEOMETRY (an off-screen
|
|
3
|
+
Flutter element keeps a 0x0 rect). Simple-case selectors route through __assertVisible
|
|
4
|
+
(scrolls into view, then asserts existence + a real on-screen rect on iOS; plain
|
|
5
|
+
toBeDisplayed on Android). Parent/scope/nth chains keep toBeDisplayed() — those are
|
|
6
|
+
container-scoped lookups the bare-selector helper can't express. --}}
|
|
7
|
+
{{~#if parentLocator~}}
|
|
1
8
|
await expect({{> locator}}).toBeDisplayed();
|
|
9
|
+
{{~else if scope~}}
|
|
10
|
+
await expect({{> locator}}).toBeDisplayed();
|
|
11
|
+
{{~else if nth~}}
|
|
12
|
+
await expect({{> locator}}).toBeDisplayed();
|
|
13
|
+
{{~else~}}
|
|
14
|
+
await __assertVisible({{> appium-selector-expr}});
|
|
15
|
+
{{~/if}}
|
package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/tap-top-action.hbs
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
await (async () => {
|
|
2
2
|
const e = await {{> locator}};
|
|
3
|
+
// Fail loud with the REAL cause when the element never resolved — getElementRect(undefined)
|
|
4
|
+
// otherwise throws a cryptic `Malformed type for "elementId"` that masks the not-found (#430).
|
|
5
|
+
if (!e.elementId) throw new Error((e as any).error?.message || `Element not found: ${e.selector}`);
|
|
3
6
|
// Visible-point tap: tap near the TOP of the element (its centre may sit under a floating bottom
|
|
4
7
|
// bar). getElementRect is W3C-standard → works on BOTH Android and iOS (the old `bounds` attribute
|
|
5
8
|
// is Android-only and throws on iOS).
|
|
@@ -4,5 +4,12 @@
|
|
|
4
4
|
YAML) is the fallback parent. Note: a child xpath beginning with `//` is ABSOLUTE in Appium even
|
|
5
5
|
when chained — use accessibility-id / -android uiautomator children for true scoping, or `.//`.
|
|
6
6
|
The selector argument comes from appium-selector-expr (it carries its OWN quotes, or a per-platform
|
|
7
|
-
`driver.isIOS ? … : …` ternary), so it is NOT wrapped in quotes here.
|
|
8
|
-
|
|
7
|
+
`driver.isIOS ? … : …` ternary), so it is NOT wrapped in quotes here.
|
|
8
|
+
Simple case (no parent/scope/nth) resolves via `__ensureVisible(...)` — Playwright-style implicit
|
|
9
|
+
scroll-into-view, so a below-fold / nav-occluded element is scrolled into the safe band before any
|
|
10
|
+
find/tap/assert (Appium has no auto-scroll). Parent/scope/nth chains keep the plain `$()` — those
|
|
11
|
+
resolve within a container / by index where a blind scroll doesn't apply.
|
|
12
|
+
`plain=true` (passed by is-hidden-assertion) also forces the bare `$()`: an element asserted HIDDEN
|
|
13
|
+
is expected to be absent, so scroll-hunting for it is both pointless and harmful — the 12x down-scroll
|
|
14
|
+
would drag the screen to the bottom and strand later same-scenario steps that sit above. --}}
|
|
15
|
+
{{~#if parentLocator~}}{{parentLocator}}.{{#if nth}}$$({{> appium-selector-expr}})[{{nth}}]{{else}}$({{> appium-selector-expr}}){{/if}}{{~else~}}{{#if scope}}$('~{{escapeQuotes scope}}').{{#if nth}}$$({{> appium-selector-expr}})[{{nth}}]{{else}}$({{> appium-selector-expr}}){{/if}}{{else}}{{#if nth}}$$({{> appium-selector-expr}})[{{nth}}]{{else}}{{#if plain}}$({{> appium-selector-expr}}){{else}}(await __ensureVisible({{> appium-selector-expr}})){{/if}}{{/if}}{{/if}}{{~/if~}}
|
|
@@ -5,6 +5,117 @@
|
|
|
5
5
|
|
|
6
6
|
const testData = TestDataLoader.load('{{screenName}}', '{{featureFileName}}');
|
|
7
7
|
{{/if}}
|
|
8
|
+
|
|
9
|
+
// Auto scroll-into-view before EVERY element access — the mobile analogue of Playwright's implicit
|
|
10
|
+
// scrollIntoView (Appium/WDIO has none). Fixes two Flutter/native failure modes at once:
|
|
11
|
+
// (A) lazy-render / zero-rect: an item below the fold is NOT usable (absent from the Android a11y
|
|
12
|
+
// tree; present-but-0x0 on iOS), so find/tap/assert fail even though scrolling would reveal it;
|
|
13
|
+
// (B) floating bottom-nav occlusion (Android): a row near the fold is "displayed" but its tap point
|
|
14
|
+
// sits behind the nav bar, so a coordinate tap misses.
|
|
15
|
+
// Already-visible elements return immediately (no scroll, no cost). Returns $(selector) even when
|
|
16
|
+
// never found so the caller's own step surfaces the real failure. The `selector` is re-queried each
|
|
17
|
+
// iteration (post-scroll elements go stale). The readiness gate and gesture differ per OS — each
|
|
18
|
+
// runtime lies differently (see the per-branch notes below).
|
|
19
|
+
//
|
|
20
|
+
// Android gates on isDisplayed() (reliable on UiAutomator2) + rect clearing the 0.82·H nav band,
|
|
21
|
+
// and scrolls with mobile: scrollGesture — NOT UiScrollable, which no-ops on Flutter (no native
|
|
22
|
+
// ScrollView).
|
|
23
|
+
//
|
|
24
|
+
// iOS gates on GEOMETRY, and scrolls with small drags. XCUITest reports a below-fold Flutter
|
|
25
|
+
// element as rect 0x0 + displayed:false while keeping it in the tree, and isDisplayed() also
|
|
26
|
+
// false-negatives on genuinely visible elements (snapshot artifact) — so visibility booleans are
|
|
27
|
+
// unusable as a scroll gate on iOS. An element is "usable" iff its rect is REAL (width/height > 0)
|
|
28
|
+
// with the top edge on screen; the width/height check is essential — a 0x0 rect has y=0, which a
|
|
29
|
+
// naive `y >= 0` gate mistakes for "at the top of the screen" (that exact bug shipped once: it
|
|
30
|
+
// skipped the scroll AND returned a stale 0x0 handle whose click went nowhere). Scrolling uses
|
|
31
|
+
// `mobile: scroll {direction}` — native XCTest paged scroll, the only primitive that worked in a
|
|
32
|
+
// live probe matrix on Flutter: `mobile: scroll {toVisible}` throws (WDA needs a scrollable
|
|
33
|
+
// ancestor with visible children, but Flutter exposes content as SIBLINGS of the ScrollView),
|
|
34
|
+
// `mobile: swipe` flings with momentum past the target, and dragFromToForDuration / W3C pointer
|
|
35
|
+
// drags corrupt the a11y tree (targets vanish from it entirely).
|
|
36
|
+
async function __ensureVisible(selector: string): Promise<WebdriverIO.Element> {
|
|
37
|
+
let winH = 2000;
|
|
38
|
+
try { const s = await driver.getWindowSize(); winH = s.height; } catch { /* keep default */ }
|
|
39
|
+
if (driver.isIOS) {
|
|
40
|
+
// Phase 1 — wait for the element to ENTER the tree WITHOUT gesturing. Right after the
|
|
41
|
+
// relaunch-per-scenario beforeEach nothing is rendered yet, and dragging a still-launching
|
|
42
|
+
// app makes XCUITest block on quiescence per gesture — enough to blow the mocha hook
|
|
43
|
+
// timeout (seen live). A below-fold Flutter element is IN the tree from the start (just
|
|
44
|
+
// 0x0), so in the genuine scroll case this wait returns instantly.
|
|
45
|
+
try { await (await $(selector)).waitForExist({ timeout: 15000 }); } catch { /* virtualized far item or missing → still try scrolling below */ }
|
|
46
|
+
for (let __i = 0; __i < 8; __i++) {
|
|
47
|
+
let existed = false;
|
|
48
|
+
try {
|
|
49
|
+
const el = await $(selector);
|
|
50
|
+
if (await el.isExisting()) {
|
|
51
|
+
existed = true;
|
|
52
|
+
const r = await driver.getElementRect(el.elementId);
|
|
53
|
+
if (r.width > 0 && r.height > 0 && r.y >= 0 && r.y < winH) return el; // real rect, on screen → usable
|
|
54
|
+
}
|
|
55
|
+
} catch { /* not in the tree yet → keep scrolling */ }
|
|
56
|
+
// Render grace — do NOT scroll on the first two passes when the element is in the tree but
|
|
57
|
+
// still 0x0: right after a relaunch Flutter briefly reports even TOP-OF-SCREEN elements as
|
|
58
|
+
// 0x0 while it renders, and a paged scroll fired in that window scrolls them clean off the
|
|
59
|
+
// top, where a down-only loop can never recover them (seen live as a random ~2-cases-per-run
|
|
60
|
+
// flake: "APP PREFERENCES rect 0x0"). A genuinely below-fold element just costs ~1s extra.
|
|
61
|
+
if (existed && __i < 2) { await driver.pause(500); continue; }
|
|
62
|
+
// `mobile: scroll {direction}` = native XCTest paged scroll — the ONLY primitive that moves
|
|
63
|
+
// this Flutter list deterministically (probed live): `mobile: swipe` flings past the target
|
|
64
|
+
// (momentum), and dragFromToForDuration / W3C pointer drags corrupt the a11y tree (targets
|
|
65
|
+
// vanish entirely). The page step is ~0.9 viewport with built-in overlap, so a below-fold
|
|
66
|
+
// element can never be jumped over — it lands near the top of the next page.
|
|
67
|
+
await driver.execute('mobile: scroll', { direction: 'down' })
|
|
68
|
+
.catch(() => driver.execute('mobile: swipe', { direction: 'up' }));
|
|
69
|
+
await driver.pause(400); // settle: let Flutter render the newly-exposed band
|
|
70
|
+
}
|
|
71
|
+
return $(selector);
|
|
72
|
+
}
|
|
73
|
+
const safeBottom = Math.round(winH * 0.82); // above the floating bottom-nav band
|
|
74
|
+
let lastY: number | null = null;
|
|
75
|
+
for (let __i = 0; __i < 12; __i++) {
|
|
76
|
+
let y: number | null = null;
|
|
77
|
+
try {
|
|
78
|
+
const el = await $(selector);
|
|
79
|
+
if (await el.isDisplayed()) {
|
|
80
|
+
const r = await driver.getElementRect(el.elementId);
|
|
81
|
+
if (r.y + r.height <= safeBottom) return el; // rendered AND fully above the floating bottom nav → done
|
|
82
|
+
y = r.y;
|
|
83
|
+
}
|
|
84
|
+
} catch { /* off-screen / not in tree yet → keep scrolling */ }
|
|
85
|
+
// No-progress break: the element is on screen but a prior scroll didn't move it — it's a FIXED
|
|
86
|
+
// element (e.g. a bottom-nav tab, intentionally in the nav band) or we've hit the end of the
|
|
87
|
+
// scrollable. Stop rather than burn the whole budget; the caller acts on it where it is.
|
|
88
|
+
if (y !== null && lastY !== null && Math.abs(y - lastY) < 2) return $(selector);
|
|
89
|
+
lastY = y;
|
|
90
|
+
await driver.execute('mobile: scrollGesture', { left: 60, top: 420, width: 960, height: 1320, direction: 'down', percent: 0.5 });
|
|
91
|
+
await driver.pause(300);
|
|
92
|
+
}
|
|
93
|
+
return $(selector);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Visibility oracle for simple-case `see [X]` assertions. Android: WDIO's toBeDisplayed()
|
|
97
|
+
// (reliable on UiAutomator2). iOS: XCUITest's `visible` attribute false-negatives on real,
|
|
98
|
+
// on-screen elements (snapshot artifact — seen live even on a screen title every prior run
|
|
99
|
+
// passed), so assert GEOMETRY instead: after __ensureVisible has scrolled the target toward
|
|
100
|
+
// view, the element must exist with a real rect (w,h > 0) whose top edge is on screen. On
|
|
101
|
+
// Flutter an off-screen element keeps a 0x0 rect, so this is exactly the "user can see it"
|
|
102
|
+
// boundary. Re-queries fresh + retries briefly to absorb render lag.
|
|
103
|
+
async function __assertVisible(selector: string): Promise<void> {
|
|
104
|
+
const el = await __ensureVisible(selector);
|
|
105
|
+
if (!driver.isIOS) { await expect(el).toBeDisplayed(); return; }
|
|
106
|
+
let winH = 2000;
|
|
107
|
+
try { const s = await driver.getWindowSize(); winH = s.height; } catch { /* keep default */ }
|
|
108
|
+
let r = { x: 0, y: 0, width: 0, height: 0 };
|
|
109
|
+
for (let __i = 0; __i < 3; __i++) {
|
|
110
|
+
try {
|
|
111
|
+
const e2 = await $(selector);
|
|
112
|
+
if (await e2.isExisting()) r = await driver.getElementRect(e2.elementId);
|
|
113
|
+
} catch { /* not in the tree */ }
|
|
114
|
+
if (r.width > 0 && r.height > 0 && r.y >= 0 && r.y < winH) return;
|
|
115
|
+
await driver.pause(500);
|
|
116
|
+
}
|
|
117
|
+
throw new Error(`Expect ${JSON.stringify(selector)} to be displayed — iOS geometry oracle: rect=${JSON.stringify(r)} is not on screen (0x0 = not rendered / scrolled out)`);
|
|
118
|
+
}
|
|
8
119
|
{{#if singleAuthRole}}
|
|
9
120
|
import * as fs from 'node:fs';
|
|
10
121
|
import * as path from 'node:path';
|
|
@@ -151,6 +262,10 @@ describe('{{featureName}}', () => {
|
|
|
151
262
|
beforeEach(async () => {
|
|
152
263
|
const __APP_PKG__ = __resolveAppId();
|
|
153
264
|
try { await driver.terminateApp(__APP_PKG__); } catch { /* app may not be running yet */ }
|
|
265
|
+
// Let the termination fully land before relaunching — activateApp racing a still-dying
|
|
266
|
+
// process can RESUME the old instance (prior scenario's scroll position and all) instead
|
|
267
|
+
// of cold-starting, which strands top-of-screen elements above the viewport.
|
|
268
|
+
await driver.pause(500);
|
|
154
269
|
await driver.activateApp(__APP_PKG__);
|
|
155
270
|
// Restore portrait so a prior scenario's `rotate to landscape` can't leak into this one — the
|
|
156
271
|
// app relaunch above resets in-app state, but device orientation is OS-level and persists.
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
{ const tableRow = {{> locator}}.getByRole('row').filter({ hasText: '{{escapeQuotes filterValue}}' });
|
|
2
|
-
await
|
|
2
|
+
await {{{actionLocator}}}.{{action}}(); }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"script-check.d.ts","sourceRoot":"","sources":["../../src/harness/script-check.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;IACvC,UAAU,EAAE,MAAM,EAAE,CAAC;IAErB,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9C,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,EAAE,CAgBtF;AAID;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC;;;cAEpD,MAAM;cAAQ,MAAM;;EAwBhD;
|
|
1
|
+
{"version":3,"file":"script-check.d.ts","sourceRoot":"","sources":["../../src/harness/script-check.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;IACvC,UAAU,EAAE,MAAM,EAAE,CAAC;IAErB,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9C,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,EAAE,CAgBtF;AAID;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC;;;cAEpD,MAAM;cAAQ,MAAM;;EAwBhD;AAsBD,4EAA4E;AAC5E,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC;AA6BjD,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA0GtH"}
|
|
@@ -122,10 +122,11 @@ function analyzeFaithfulness(specSrc, automatedTitles) {
|
|
|
122
122
|
return { assertionlessTests, hollowSteps };
|
|
123
123
|
}
|
|
124
124
|
function extractTestTitles(specSrc) {
|
|
125
|
-
// Count real test cases only
|
|
126
|
-
//
|
|
125
|
+
// Count real test cases only. Web/Playwright specs use test(...) (+ .only/.skip/.fixme);
|
|
126
|
+
// mobile/Appium specs use mocha it(...) (#429 — matching only test() reported EVERY mobile
|
|
127
|
+
// scenario as MISSING). Exclude describe / hooks (not test cases).
|
|
127
128
|
const titles = [];
|
|
128
|
-
const re = /\
|
|
129
|
+
const re = /\b(?:test|it)(?:\.(?:only|skip|fixme))?\(\s*(['"`])([^'"`]+)\1/g;
|
|
129
130
|
let m;
|
|
130
131
|
while ((m = re.exec(specSrc)))
|
|
131
132
|
titles.push(m[2].trim());
|
|
@@ -210,9 +211,13 @@ async function runScriptCheck(screenDir, screenName, kind) {
|
|
|
210
211
|
const qaSourceDir = path.join(process.cwd(), 'qa', kind === 'flow' ? 'flows' : kind === 'api' ? 'api' : 'screens');
|
|
211
212
|
// api units derive their unit id (api/<area>) from the feature path — like `generate --api`;
|
|
212
213
|
// screen/flow pass screenName + flowMode explicitly (unchanged → byte-identical regenerate).
|
|
214
|
+
// Regenerate with the SAME framework the committed spec was compiled with — a mobile spec
|
|
215
|
+
// (imports '@wdio/globals') was generated by the appium adapter; regenerating it with the
|
|
216
|
+
// playwright adapter guaranteed a false DRIFT on every mobile screen (#429).
|
|
217
|
+
const framework = specSrc.includes('@wdio/globals') ? 'appium' : 'playwright';
|
|
213
218
|
const gen = kind === 'api'
|
|
214
|
-
? new CodeGenerator({ framework
|
|
215
|
-
: new CodeGenerator({ framework
|
|
219
|
+
? new CodeGenerator({ framework, runtimeData: true })
|
|
220
|
+
: new CodeGenerator({ framework, screenName, runtimeData: true, flowMode: kind === 'flow' });
|
|
216
221
|
await gen.generateAllTests(qaSourceDir, tmp, [featurePath]);
|
|
217
222
|
const fresh = findSpec(tmp, screenName, kind);
|
|
218
223
|
if (fresh) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"script-check.js","sourceRoot":"","sources":["../../src/harness/script-check.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,8CAgBC;AASD,kDA0BC;
|
|
1
|
+
{"version":3,"file":"script-check.js","sourceRoot":"","sources":["../../src/harness/script-check.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/ai-skills/writer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/ai-skills/writer.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAGpC,MAAM,WAAW,YAAY;IAAG,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;AACpF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAC;IAClE,cAAc,EAAE,MAAM,EAAE,CAAC;IAAC,gBAAgB,EAAE,MAAM,EAAE,CAAC;CACtD;AA2BD,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,YAAY,GAAG,WAAW,CA4E/F"}
|
|
@@ -39,8 +39,9 @@ exports.applyOutputs = applyOutputs;
|
|
|
39
39
|
*
|
|
40
40
|
* managed (skills/commands): ALWAYS overwritten (version-managed, no rollback)
|
|
41
41
|
* config (CLAUDE.md/AGENTS.md/copilot-instructions): skip-if-exists unless `force`
|
|
42
|
-
* orphans: managed files written last run but not this run are DELETED
|
|
43
|
-
*
|
|
42
|
+
* orphans: managed files written last run but not this run are DELETED — but ONLY within the
|
|
43
|
+
* assistants built THIS run (scoped by namespace), so building one assistant never wipes
|
|
44
|
+
* another's files (manifest-derived; replaces the hand-curated STALE list — decision T2)
|
|
44
45
|
* detect-and-warn: if a managed file on disk was hand-edited since we wrote it, warn
|
|
45
46
|
* dry-run: report adds/overwrites/skips/deletes/warnings without touching disk
|
|
46
47
|
*
|
|
@@ -51,6 +52,22 @@ const path = __importStar(require("path"));
|
|
|
51
52
|
const crypto = __importStar(require("crypto"));
|
|
52
53
|
const MANIFEST_REL = path.join('.sungen', 'manifest.json');
|
|
53
54
|
const sha = (s) => crypto.createHash('sha256').update(s).digest('hex');
|
|
55
|
+
/**
|
|
56
|
+
* Which assistant a managed/config output path belongs to, by namespace prefix. Scopes orphan
|
|
57
|
+
* cleanup + manifest retention to the assistants built THIS run, so `sungen init --assistant codex`
|
|
58
|
+
* never deletes a previously-installed `.claude/` (and vice versa). Returns null for a path we
|
|
59
|
+
* can't attribute — treated as out-of-scope, so it is never auto-deleted.
|
|
60
|
+
*/
|
|
61
|
+
function adapterOf(relPath) {
|
|
62
|
+
const p = relPath.replace(/\\/g, '/');
|
|
63
|
+
if (p === 'CLAUDE.md' || p.startsWith('.claude/'))
|
|
64
|
+
return 'claude';
|
|
65
|
+
if (p === 'AGENTS.md' || p.startsWith('.agents/'))
|
|
66
|
+
return 'codex';
|
|
67
|
+
if (p === '.github/copilot-instructions.md' || p.startsWith('.github/'))
|
|
68
|
+
return 'copilot';
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
54
71
|
function readManifest(cwd) {
|
|
55
72
|
const p = path.join(cwd, MANIFEST_REL);
|
|
56
73
|
if (!fs.existsSync(p))
|
|
@@ -101,10 +118,24 @@ function applyOutputs(cwd, outputs, opts) {
|
|
|
101
118
|
write(f.outputPath, f.content);
|
|
102
119
|
newManaged[f.outputPath] = sha(f.content);
|
|
103
120
|
}
|
|
104
|
-
//
|
|
121
|
+
// Assistants this run actually produced output for. Orphan cleanup is scoped to THESE namespaces
|
|
122
|
+
// only — a path belonging to an assistant we did NOT build this run (e.g. `.claude/*` while running
|
|
123
|
+
// `--assistant codex`) is not ours to touch, so init/update is ADDITIVE across assistants instead
|
|
124
|
+
// of wiping the previously-installed one.
|
|
125
|
+
const scoped = new Set();
|
|
126
|
+
for (const f of outputs) {
|
|
127
|
+
const a = adapterOf(f.outputPath);
|
|
128
|
+
if (a)
|
|
129
|
+
scoped.add(a);
|
|
130
|
+
}
|
|
131
|
+
const inScope = (p) => { const a = adapterOf(p); return a !== null && scoped.has(a); };
|
|
132
|
+
// Orphan cleanup: managed files we wrote last time but not now — but ONLY within the assistants
|
|
133
|
+
// built this run. Other assistants' managed files are preserved (this is the cross-assistant fix).
|
|
105
134
|
for (const oldPath of Object.keys(prev.managed)) {
|
|
106
135
|
if (newManaged[oldPath])
|
|
107
136
|
continue;
|
|
137
|
+
if (!inScope(oldPath))
|
|
138
|
+
continue; // belongs to an assistant not built this run → keep it
|
|
108
139
|
r.deletedOrphans.push(oldPath);
|
|
109
140
|
if (!opts.dryRun) {
|
|
110
141
|
const abs = path.join(cwd, oldPath);
|
|
@@ -120,9 +151,20 @@ function applyOutputs(cwd, outputs, opts) {
|
|
|
120
151
|
}
|
|
121
152
|
}
|
|
122
153
|
if (!opts.dryRun) {
|
|
154
|
+
// Retain manifest entries for assistants NOT built this run so their files stay tracked (and get
|
|
155
|
+
// orphan-cleaned correctly on a later run that DOES target them); merge this run's entries on top.
|
|
156
|
+
const mergedManaged = {};
|
|
157
|
+
for (const [p, h] of Object.entries(prev.managed)) {
|
|
158
|
+
if (newManaged[p])
|
|
159
|
+
continue; // rewritten this run → newManaged carries the fresh hash
|
|
160
|
+
if (inScope(p))
|
|
161
|
+
continue; // orphan within a built assistant → dropped (deleted above)
|
|
162
|
+
mergedManaged[p] = h; // other assistant (or unclassified) → keep tracking
|
|
163
|
+
}
|
|
164
|
+
Object.assign(mergedManaged, newManaged);
|
|
123
165
|
const mPath = path.join(cwd, MANIFEST_REL);
|
|
124
166
|
fs.mkdirSync(path.dirname(mPath), { recursive: true });
|
|
125
|
-
fs.writeFileSync(mPath, JSON.stringify({ version: opts.version, managed:
|
|
167
|
+
fs.writeFileSync(mPath, JSON.stringify({ version: opts.version, managed: mergedManaged }, null, 2));
|
|
126
168
|
}
|
|
127
169
|
return r;
|
|
128
170
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"writer.js","sourceRoot":"","sources":["../../../src/orchestrator/ai-skills/writer.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"writer.js","sourceRoot":"","sources":["../../../src/orchestrator/ai-skills/writer.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: add-flow
|
|
3
3
|
description: "Add a new Sungen flow — scaffolds directories for E2E cross-screen testing, helps fill spec.md, and can capture visuals via the capture skills"
|
|
4
|
-
argument-hint: [flow-name] [--path <start-url>]
|
|
4
|
+
argument-hint: "[flow-name] [--path <start-url>] [--reach <steps>]"
|
|
5
5
|
order: 10
|
|
6
|
-
claude-tools: "Read, Grep, Bash, Glob, Edit, Write, AskUserQuestion, mcp__playwright__browser_navigate, mcp__playwright__browser_take_screenshot, mcp__playwright__browser_snapshot, mcp__figma__get_design_context, mcp__figma__get_variable_defs, mcp__figma__get_screenshot"
|
|
6
|
+
claude-tools: "Read, Grep, Bash, Glob, Edit, Write, AskUserQuestion, mcp__playwright__browser_navigate, mcp__playwright__browser_take_screenshot, mcp__playwright__browser_snapshot, mcp__figma__get_design_context, mcp__figma__get_variable_defs, mcp__figma__get_screenshot, mcp__appium-mcp__select_device, mcp__appium-mcp__appium_session_management, mcp__appium-mcp__generate_locators, mcp__appium-mcp__appium_get_page_source, mcp__appium-mcp__appium_find_element, mcp__appium-mcp__appium_gesture, mcp__appium-mcp__appium_screenshot"
|
|
7
7
|
copilot-tools: "[vscode, execute, read, agent, edit, search, todo]"
|
|
8
8
|
codex-trigger: "Run when the user asks to add a NEW cross-screen end-to-end flow. Step 1 (flow variant). Do NOT use for a single screen, creating test cases, or running tests."
|
|
9
9
|
---
|
|
@@ -13,6 +13,13 @@ codex-trigger: "Run when the user asks to add a NEW cross-screen end-to-end flow
|
|
|
13
13
|
{{/cap}}
|
|
14
14
|
You are adding a new Sungen flow for E2E cross-screen test generation.
|
|
15
15
|
|
|
16
|
+
## Platform detection (do this FIRST)
|
|
17
|
+
|
|
18
|
+
Read `qa/capabilities.yaml` and check the `platform` field.
|
|
19
|
+
|
|
20
|
+
- If `platform: mobile` → follow the **mobile path** in each step below.
|
|
21
|
+
- If `platform: web` or field absent → follow the **web path** (all steps unchanged).
|
|
22
|
+
|
|
16
23
|
## Parameters
|
|
17
24
|
|
|
18
25
|
{{#cap parallel-subagents}}
|
|
@@ -22,12 +29,13 @@ Parse from `$ARGUMENTS`:
|
|
|
22
29
|
{{^cap parallel-subagents}}
|
|
23
30
|
- **flow** — ${input:flow:flow name (e.g., award-submission, user-onboarding)}
|
|
24
31
|
{{/cap}}
|
|
25
|
-
- **--path \<url\>** — starting page URL path (default: `/login`)
|
|
32
|
+
- **--path \<url\>** — starting page URL path for web (default: `/login`). Not used on mobile.
|
|
33
|
+
- **--reach \<steps\>** — (mobile only) navigation recipe for the flow's first 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.
|
|
26
34
|
- **--description \<text\>** — flow description (optional)
|
|
27
35
|
|
|
28
36
|
{{#cap parallel-subagents}}
|
|
29
37
|
If **flow** is missing, ask: "What is the flow name? (e.g., `award-submission`, `user-onboarding`)"
|
|
30
|
-
If **path** is missing, ask
|
|
38
|
+
If **path** is missing (web) or **--reach** is missing (mobile), ask for it accordingly — e.g. "What is the starting URL path? (e.g., `/login`)" for web, or "How do you reach the first screen from app launch? (e.g., `tap [My Page] tab > tap [Settings] row`)" for mobile.
|
|
31
39
|
|
|
32
40
|
{{/cap}}
|
|
33
41
|
## Steps
|
|
@@ -37,6 +45,7 @@ If **path** is missing, ask: "What is the starting URL path? (e.g., `/login`)"
|
|
|
37
45
|
{{^cap parallel-subagents}}
|
|
38
46
|
Run with #tool:terminal:
|
|
39
47
|
{{/cap}}
|
|
48
|
+
**Web:**
|
|
40
49
|
```bash
|
|
41
50
|
{{#cap parallel-subagents}}
|
|
42
51
|
sungen add-flow --flow <name> --path <path>
|
|
@@ -46,6 +55,18 @@ sungen add-flow --flow ${input:flow} --path ${input:path}
|
|
|
46
55
|
{{/cap}}
|
|
47
56
|
```
|
|
48
57
|
|
|
58
|
+
**Mobile** (`platform: mobile` in `qa/capabilities.yaml`):
|
|
59
|
+
```bash
|
|
60
|
+
{{#cap parallel-subagents}}
|
|
61
|
+
sungen add-flow --flow <name> [--reach "tap [My Page] tab > tap [Settings] row"]
|
|
62
|
+
# Directories are always qa/flows/<name>/ — there is no qa/mobile/ tree.
|
|
63
|
+
{{/cap}}
|
|
64
|
+
{{^cap parallel-subagents}}
|
|
65
|
+
sungen add-flow --flow ${input:flow} [--reach "tap [My Page] tab > tap [Settings] row"]
|
|
66
|
+
# Directories are always qa/flows/${input:flow}/ — there is no qa/mobile/ tree.
|
|
67
|
+
{{/cap}}
|
|
68
|
+
```
|
|
69
|
+
|
|
49
70
|
This creates:
|
|
50
71
|
```
|
|
51
72
|
{{#cap parallel-subagents}}
|
|
@@ -76,6 +97,26 @@ Record the screen list — you will need it for:
|
|
|
76
97
|
|
|
77
98
|
### 2. Capture visual source
|
|
78
99
|
|
|
100
|
+
**Mobile path** (`platform: mobile`):
|
|
101
|
+
|
|
102
|
+
{{#cap parallel-subagents}}
|
|
103
|
+
Use `AskUserQuestion`: *"Capture a live screen via Appium MCP now?"*
|
|
104
|
+
{{/cap}}
|
|
105
|
+
{{^cap parallel-subagents}}
|
|
106
|
+
Ask: *"Capture a live screen via Appium MCP now?"*
|
|
107
|
+
{{/cap}}
|
|
108
|
+
- **Yes, capture now** (Recommended) — invoke the `sungen-capture-mobile` skill (Appium MCP) for the first screen of the flow. It replays the `--reach` nav recipe from the `Background:`, scans the locator tree, and saves a screenshot to `requirements/ui/`. Repeat for each screen in the flow as needed.
|
|
109
|
+
{{#cap parallel-subagents}}
|
|
110
|
+
- **Skip** — user will capture later via `/sungen:run-test` or manually.
|
|
111
|
+
{{/cap}}
|
|
112
|
+
{{^cap parallel-subagents}}
|
|
113
|
+
- **Skip** — user will capture later via `/sungen-run-test` or manually.
|
|
114
|
+
{{/cap}}
|
|
115
|
+
|
|
116
|
+
Do NOT invoke `sungen-capture` (web/Figma) for a mobile project — use `sungen-capture-mobile` only.
|
|
117
|
+
|
|
118
|
+
**Web path:**
|
|
119
|
+
|
|
79
120
|
{{#cap parallel-subagents}}
|
|
80
121
|
Use `AskUserQuestion`: *"Pick a visual source for this flow's screens:"*
|
|
81
122
|
{{/cap}}
|
|
@@ -145,5 +186,11 @@ If user picks `/sungen:create-test`, **you MUST use the Skill tool** to invoke i
|
|
|
145
186
|
- YAML keys must be **quoted** due to colon: `"login:submit":`
|
|
146
187
|
- Test data namespaced by phase: `login.email`, `submission.nominee`
|
|
147
188
|
- `@flow` tag required at feature level
|
|
148
|
-
- `Background:` should only contain the starting
|
|
189
|
+
- `Background:` should only contain the starting navigation — the URL path (web) or the `--reach` nav recipe (mobile)
|
|
149
190
|
- Each scenario = one phase of the journey
|
|
191
|
+
{{#cap parallel-subagents}}
|
|
192
|
+
- Mobile flows are tagged `@platform:mobile` and run via `/sungen:run-test <flow>` (WebdriverIO, not Playwright)
|
|
193
|
+
{{/cap}}
|
|
194
|
+
{{^cap parallel-subagents}}
|
|
195
|
+
- Mobile flows are tagged `@platform:mobile` and run via `/sungen-run-test <flow>` (WebdriverIO, not Playwright)
|
|
196
|
+
{{/cap}}
|
|
@@ -20,6 +20,13 @@ If `spec_figma.md` exists OR the user provides a Figma URL for the PAT flow:
|
|
|
20
20
|
**Input**: Screen or flow name (e.g., `/sungen-create-test admin-users`).
|
|
21
21
|
{{/cap}}
|
|
22
22
|
|
|
23
|
+
## Platform detection (do this FIRST)
|
|
24
|
+
|
|
25
|
+
Read `qa/capabilities.yaml` and check the `platform` field.
|
|
26
|
+
|
|
27
|
+
- If `platform: mobile` → the visual-capture step (Step 3, "Auto-detect visual source") uses the **`sungen-capture-mobile`** skill (Appium MCP live capture) **instead of** the web `sungen-capture` (figma/live). Scenarios use mobile gesture vocabulary from `sungen-mobile-gestures` (tap, swipe, long-press — not click/hover) and `accessibility-id`-first selectors; mobile-specific steps (permission dialogs, clipboard, geolocation) are valid Gherkin, not errors. The `Background:` is a **navigation recipe** (see `add-screen --reach`), not a URL path. Everything else — viewpoints, `sungen-tc-generation`, the harness/audit loop — is platform-agnostic and unchanged.
|
|
28
|
+
- If `platform: web` or field absent → follow the **web path** (all steps below, unchanged) — Figma/live capture via `sungen-capture` as documented in Step 3.
|
|
29
|
+
|
|
23
30
|
## Role
|
|
24
31
|
|
|
25
32
|
{{#cap parallel-subagents}}
|