@sun-asterisk/sungen 3.2.3 → 3.2.5
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/dashboard.js +2 -2
- package/dist/cli/commands/dashboard.js.map +1 -1
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +22 -5
- package/dist/cli/commands/delivery.js.map +1 -1
- 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 +3 -0
- package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
- package/dist/dashboard/snapshot-builder.js +155 -52
- package/dist/dashboard/snapshot-builder.js.map +1 -1
- package/dist/dashboard/templates/index.html +23 -23
- package/dist/dashboard/types.d.ts +8 -0
- package/dist/dashboard/types.d.ts.map +1 -1
- package/dist/exporters/api-testcase-formatter.d.ts +55 -0
- package/dist/exporters/api-testcase-formatter.d.ts.map +1 -0
- package/dist/exporters/api-testcase-formatter.js +304 -0
- package/dist/exporters/api-testcase-formatter.js.map +1 -0
- package/dist/exporters/csv-exporter.d.ts +13 -1
- package/dist/exporters/csv-exporter.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.js +35 -6
- 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 +13 -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/generators/test-generator/patterns/index.d.ts +12 -0
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +21 -1
- package/dist/generators/test-generator/patterns/index.js.map +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/project-initializer.d.ts +14 -0
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +58 -0
- package/dist/orchestrator/project-initializer.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 +4 -3
- package/src/cli/commands/dashboard.ts +2 -2
- package/src/cli/commands/delivery.ts +24 -6
- 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 +146 -46
- package/src/dashboard/templates/index.html +23 -23
- package/src/dashboard/types.ts +10 -2
- package/src/exporters/api-testcase-formatter.ts +325 -0
- package/src/exporters/csv-exporter.ts +46 -7
- 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 +13 -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/generators/test-generator/patterns/index.ts +21 -1
- package/src/harness/script-check.ts +10 -5
- package/src/orchestrator/ai-skills/writer.ts +39 -4
- package/src/orchestrator/project-initializer.ts +59 -0
- 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
|
@@ -44,6 +44,9 @@ const DEFAULT_BASE_LOCALE = 'vi';
|
|
|
44
44
|
|
|
45
45
|
export interface DashboardTarget {
|
|
46
46
|
name: string;
|
|
47
|
+
kind: 'screen' | 'flow' | 'api';
|
|
48
|
+
/** Derived convenience (`kind === 'flow'`) — kept for the flow-specific
|
|
49
|
+
* branches that predate the api tier. */
|
|
47
50
|
isFlow: boolean;
|
|
48
51
|
}
|
|
49
52
|
|
|
@@ -85,6 +88,23 @@ export function buildDashboardSnapshot(options: BuildSnapshotOptions): Dashboard
|
|
|
85
88
|
// Per-target build
|
|
86
89
|
// ----------------------------------------------------------------------------
|
|
87
90
|
|
|
91
|
+
/**
|
|
92
|
+
* True when this is a mobile project (`platform: mobile` in qa/capabilities.yaml).
|
|
93
|
+
* Mobile projects run per-OS (android/ios) — the dashboard surfaces those on a
|
|
94
|
+
* distinct platform axis (one FeatureSnapshot per OS) rather than as locale tabs.
|
|
95
|
+
* Mirrors `isMobileProject` in src/cli/commands/delivery.ts.
|
|
96
|
+
*/
|
|
97
|
+
function isMobileProject(cwd: string): boolean {
|
|
98
|
+
try {
|
|
99
|
+
const p = path.join(cwd, 'qa', 'capabilities.yaml');
|
|
100
|
+
if (!fs.existsSync(p)) return false;
|
|
101
|
+
const parsed = parseYaml(fs.readFileSync(p, 'utf-8')) as { platform?: string } | null;
|
|
102
|
+
return parsed?.platform === 'mobile';
|
|
103
|
+
} catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
88
108
|
function buildOneScreen(
|
|
89
109
|
cwd: string,
|
|
90
110
|
target: DashboardTarget,
|
|
@@ -105,28 +125,49 @@ function buildOneScreen(
|
|
|
105
125
|
const specMdFile = path.join(base, 'requirements', 'spec.md');
|
|
106
126
|
const specLink = fs.existsSync(specMdFile) ? path.relative(cwd, specMdFile) : undefined;
|
|
107
127
|
|
|
128
|
+
const mobile = isMobileProject(cwd);
|
|
108
129
|
const features: FeatureSnapshot[] = [];
|
|
109
130
|
for (const featureBaseName of featureBaseNames) {
|
|
110
|
-
|
|
111
|
-
if (fs2) features.push(fs2);
|
|
131
|
+
features.push(...buildOneFeature(cwd, target, featureBaseName, env, specLink, mobile));
|
|
112
132
|
}
|
|
113
133
|
if (features.length === 0) return null;
|
|
114
134
|
|
|
115
135
|
// Flat scenarios across all features (each scenario already carries its
|
|
116
136
|
// own `featureBaseName`, so consumers that want to group can do so cheaply).
|
|
137
|
+
// For mobile, per-OS features flatten here so the Compare view can key each
|
|
138
|
+
// scenario by (tcId, platform) via its OS-precise testEnvironment.
|
|
117
139
|
const scenarios = features.flatMap((f) => f.scenarios);
|
|
118
|
-
const label = target.
|
|
119
|
-
|
|
140
|
+
const label = target.kind === 'flow' ? `flow/${target.name}`
|
|
141
|
+
: target.kind === 'api' ? `api/${target.name}`
|
|
142
|
+
: target.name;
|
|
143
|
+
|
|
144
|
+
// Screen-level platform axis: 'mobile' when any feature is OS-tagged; the
|
|
145
|
+
// union of OSes that actually produced a result file this run.
|
|
146
|
+
const execSet = new Set<'android' | 'ios'>();
|
|
147
|
+
let anyPlatform = false;
|
|
148
|
+
for (const f of features) {
|
|
149
|
+
if (f.platform) anyPlatform = true;
|
|
150
|
+
for (const p of f.executedPlatforms ?? []) execSet.add(p);
|
|
151
|
+
}
|
|
152
|
+
const executedPlatforms = execSet.size > 0
|
|
153
|
+
? (['android', 'ios'] as const).filter((p) => execSet.has(p))
|
|
154
|
+
: undefined;
|
|
155
|
+
// Representative feature for the back-compat top-level fields: prefer Android
|
|
156
|
+
// (deterministic, matches delivery's summary-row preference).
|
|
157
|
+
const primary = features.find((f) => f.platform === 'android') ?? features[0];
|
|
120
158
|
|
|
121
159
|
return {
|
|
122
160
|
name: label,
|
|
123
161
|
isFlow: target.isFlow,
|
|
162
|
+
kind: target.kind,
|
|
163
|
+
platform: anyPlatform ? 'mobile' : undefined,
|
|
124
164
|
featureName: primary.featureName,
|
|
125
165
|
featurePath: primary.featurePath,
|
|
126
166
|
specLink,
|
|
127
167
|
summary: rollupScreenSummary(features),
|
|
128
168
|
scenarios,
|
|
129
169
|
features,
|
|
170
|
+
executedPlatforms,
|
|
130
171
|
};
|
|
131
172
|
}
|
|
132
173
|
|
|
@@ -141,14 +182,15 @@ function buildOneFeature(
|
|
|
141
182
|
featureBaseName: string,
|
|
142
183
|
env: EnvironmentInfo,
|
|
143
184
|
specLink: string | undefined,
|
|
144
|
-
|
|
185
|
+
mobile: boolean,
|
|
186
|
+
): FeatureSnapshot[] {
|
|
145
187
|
const base = qaDir(cwd, target);
|
|
146
188
|
const genBase = generatedDir(cwd, target);
|
|
147
189
|
const featureFile = path.join(base, 'features', `${featureBaseName}.feature`);
|
|
148
190
|
const testDataFile = path.join(base, 'test-data', `${featureBaseName}.yaml`);
|
|
149
191
|
const specFile = path.join(genBase, `${featureBaseName}.spec.ts`);
|
|
150
192
|
|
|
151
|
-
if (!fs.existsSync(featureFile)) return
|
|
193
|
+
if (!fs.existsSync(featureFile)) return [];
|
|
152
194
|
|
|
153
195
|
const feature = parseFeatureMetadata(featureFile);
|
|
154
196
|
const spec = fs.existsSync(specFile)
|
|
@@ -168,27 +210,16 @@ function buildOneFeature(
|
|
|
168
210
|
// Discover locale variants by scanning <feature>-test-result*.json files.
|
|
169
211
|
// Always includes base ('') as the first entry, even if its results file
|
|
170
212
|
// is missing — UI still needs to render the feature.
|
|
171
|
-
// NOTE (mobile): for a `platform: mobile` project the "locale" variants are
|
|
172
|
-
// actually the per-OS runs (android / ios) — the dashboard reuses the locale
|
|
173
|
-
// switcher to surface them as tabs. The env column per tab is OS-precise (see
|
|
174
|
-
// resolvePlatformAppId below), but the top-level featurePath (base variant)
|
|
175
|
-
// defaults to the Android id. Delivery has a dedicated per-OS path; aligning the
|
|
176
|
-
// dashboard onto a distinct "platform" axis is a possible follow-up.
|
|
177
213
|
const variants = discoverLocaleVariants(genBase, featureBaseName);
|
|
178
214
|
|
|
179
|
-
// Build per-
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
const locales: LocaleSnapshot[] = [];
|
|
183
|
-
let baseBuilt: { scenarios: ScenarioSnapshot[]; summary: ScreenSummaryStats; featureName: string; featurePath?: string; specLink?: string } | null = null;
|
|
184
|
-
|
|
185
|
-
for (const variant of variants) {
|
|
215
|
+
// Build one per-variant ScreenSnapshot (test-data overlay + results + OS-precise
|
|
216
|
+
// env column). Reused by both the web/i18n (locale-axis) and mobile (platform-axis) paths.
|
|
217
|
+
const buildVariant = (variant: FeatureLocaleVariant) => {
|
|
186
218
|
const testData = fs.existsSync(testDataFile)
|
|
187
219
|
? loadTestData(testDataFile, variant.locale || null)
|
|
188
220
|
: {};
|
|
189
221
|
const results = variant.resultsPath ? loadPlaywrightReport(variant.resultsPath) : null;
|
|
190
|
-
|
|
191
|
-
const built = buildScreenSnapshot({
|
|
222
|
+
return buildScreenSnapshot({
|
|
192
223
|
screen: featureBaseName,
|
|
193
224
|
isFlow: target.isFlow,
|
|
194
225
|
featureName: feature.featureName,
|
|
@@ -203,17 +234,65 @@ function buildOneFeature(
|
|
|
203
234
|
featureBaseName,
|
|
204
235
|
selectorKeyMap,
|
|
205
236
|
});
|
|
237
|
+
};
|
|
238
|
+
type Built = ReturnType<typeof buildVariant>;
|
|
239
|
+
|
|
240
|
+
// -- Mobile (platform axis): the per-OS runs (android / ios) are their own
|
|
241
|
+
// FeatureSnapshot entries — one per OS, `platform` tagged — NOT locale tabs.
|
|
242
|
+
// Locale overlays for a given OS (e.g. `android.ja`) nest inside that OS's
|
|
243
|
+
// entry as `locales[]`. This mirrors the delivery per-OS deliverables. --
|
|
244
|
+
if (mobile) {
|
|
245
|
+
const osOf = (loc: string): 'android' | 'ios' | null =>
|
|
246
|
+
/^android(\.|$)/.test(loc) ? 'android' : /^ios(\.|$)/.test(loc) ? 'ios' : null;
|
|
247
|
+
const osVariants = variants.filter((v) => osOf(v.locale));
|
|
248
|
+
if (osVariants.length > 0) {
|
|
249
|
+
const executedPlatforms = (['android', 'ios'] as const).filter((os) =>
|
|
250
|
+
osVariants.some((v) => osOf(v.locale) === os && v.resultsPath)
|
|
251
|
+
);
|
|
252
|
+
const feats: FeatureSnapshot[] = [];
|
|
253
|
+
for (const os of ['android', 'ios'] as const) {
|
|
254
|
+
const group = osVariants.filter((v) => osOf(v.locale) === os);
|
|
255
|
+
if (group.length === 0) continue;
|
|
256
|
+
const localeSnaps: LocaleSnapshot[] = [];
|
|
257
|
+
let primaryBuilt: Built | null = null;
|
|
258
|
+
for (const v of group) {
|
|
259
|
+
const built = buildVariant(v);
|
|
260
|
+
localeSnaps.push({ locale: v.locale, displayCode: v.displayCode, summary: built.summary, scenarios: built.scenarios });
|
|
261
|
+
// Representative = the bare OS run (locale === os); else the first overlay.
|
|
262
|
+
if (v.locale === os || !primaryBuilt) primaryBuilt = built;
|
|
263
|
+
}
|
|
264
|
+
if (!primaryBuilt) continue;
|
|
265
|
+
feats.push({
|
|
266
|
+
featureBaseName,
|
|
267
|
+
featureName: primaryBuilt.featureName,
|
|
268
|
+
featurePath: primaryBuilt.featurePath,
|
|
269
|
+
specLink: primaryBuilt.specLink,
|
|
270
|
+
summary: primaryBuilt.summary,
|
|
271
|
+
scenarios: primaryBuilt.scenarios,
|
|
272
|
+
platform: os,
|
|
273
|
+
executedPlatforms,
|
|
274
|
+
locales: localeSnaps.length >= 2 ? localeSnaps : undefined,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
if (feats.length > 0) return feats;
|
|
278
|
+
}
|
|
279
|
+
// Mobile project but no per-OS result files yet → fall through to the base
|
|
280
|
+
// (locale-axis) path so the test CASES still render.
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// -- Web / i18n (locale axis): one FeatureSnapshot; variants become locales[]. --
|
|
284
|
+
const locales: LocaleSnapshot[] = [];
|
|
285
|
+
let baseBuilt: Built | { scenarios: ScenarioSnapshot[]; summary: ScreenSummaryStats; featureName: string; featurePath?: string; specLink?: string } | null = null;
|
|
206
286
|
|
|
287
|
+
for (const variant of variants) {
|
|
288
|
+
const built = buildVariant(variant);
|
|
207
289
|
locales.push({
|
|
208
290
|
locale: variant.locale,
|
|
209
291
|
displayCode: variant.displayCode,
|
|
210
292
|
summary: built.summary,
|
|
211
293
|
scenarios: built.scenarios,
|
|
212
294
|
});
|
|
213
|
-
|
|
214
|
-
if (variant.locale === '') {
|
|
215
|
-
baseBuilt = built;
|
|
216
|
-
}
|
|
295
|
+
if (variant.locale === '') baseBuilt = built;
|
|
217
296
|
}
|
|
218
297
|
|
|
219
298
|
// Guarantee a base entry — every feature in the dashboard must have
|
|
@@ -227,9 +306,9 @@ function buildOneFeature(
|
|
|
227
306
|
specLink,
|
|
228
307
|
};
|
|
229
308
|
}
|
|
230
|
-
if (!baseBuilt) return
|
|
309
|
+
if (!baseBuilt) return [];
|
|
231
310
|
|
|
232
|
-
return {
|
|
311
|
+
return [{
|
|
233
312
|
featureBaseName,
|
|
234
313
|
featureName: baseBuilt.featureName,
|
|
235
314
|
featurePath: baseBuilt.featurePath,
|
|
@@ -239,7 +318,7 @@ function buildOneFeature(
|
|
|
239
318
|
// Only expose `locales` array when 2+ variants exist — single-locale
|
|
240
319
|
// features stay slimmer and UI can skip the locale tab UI entirely.
|
|
241
320
|
locales: locales.length >= 2 ? locales : undefined,
|
|
242
|
-
};
|
|
321
|
+
}];
|
|
243
322
|
}
|
|
244
323
|
|
|
245
324
|
/**
|
|
@@ -361,14 +440,24 @@ function bumpKey(record: Record<string, number>, key: string): void {
|
|
|
361
440
|
// Path helpers (mirror delivery.ts to stay consistent)
|
|
362
441
|
// ----------------------------------------------------------------------------
|
|
363
442
|
|
|
443
|
+
/** qa/ subfolder for a unit kind (mirrors delivery.ts's qaParent). */
|
|
444
|
+
function qaParent(kind: DashboardTarget['kind']): string {
|
|
445
|
+
return kind === 'flow' ? 'flows' : kind === 'api' ? 'api' : 'screens';
|
|
446
|
+
}
|
|
447
|
+
|
|
364
448
|
function qaDir(cwd: string, target: DashboardTarget): string {
|
|
365
|
-
return path.join(cwd, 'qa', target.
|
|
449
|
+
return path.join(cwd, 'qa', qaParent(target.kind), target.name);
|
|
366
450
|
}
|
|
367
451
|
|
|
368
452
|
function generatedDir(cwd: string, target: DashboardTarget): string {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
453
|
+
// Mirrors delivery.ts: screen → specs/generated/<name>, flow → …/flows/<name>,
|
|
454
|
+
// api → …/api/<name> (api flows carry name 'flows/<flow>', composing correctly).
|
|
455
|
+
const sub = target.kind === 'flow'
|
|
456
|
+
? path.join('flows', target.name)
|
|
457
|
+
: target.kind === 'api'
|
|
458
|
+
? path.join('api', target.name)
|
|
459
|
+
: target.name;
|
|
460
|
+
return path.join(cwd, 'specs', 'generated', sub);
|
|
372
461
|
}
|
|
373
462
|
|
|
374
463
|
// ----------------------------------------------------------------------------
|
|
@@ -378,17 +467,23 @@ function generatedDir(cwd: string, target: DashboardTarget): string {
|
|
|
378
467
|
export function listDashboardTargets(cwd: string): DashboardTarget[] {
|
|
379
468
|
const targets: DashboardTarget[] = [];
|
|
380
469
|
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
470
|
+
const scan = (kind: DashboardTarget['kind'], skip: (n: string) => boolean = () => false): void => {
|
|
471
|
+
const dir = path.join(cwd, 'qa', qaParent(kind));
|
|
472
|
+
if (!fs.existsSync(dir)) return;
|
|
473
|
+
for (const d of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
474
|
+
if (d.isDirectory() && !skip(d.name)) targets.push({ name: d.name, kind, isFlow: kind === 'flow' });
|
|
385
475
|
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
476
|
+
};
|
|
477
|
+
scan('screen');
|
|
478
|
+
scan('flow');
|
|
479
|
+
scan('api', (n) => n === 'flows'); // api areas: qa/api/<area> (the flows/ container is a nested tier)
|
|
480
|
+
|
|
481
|
+
// api flows: qa/api/flows/<flow> → name 'flows/<flow>', kind 'api'
|
|
482
|
+
// (paths compose to qa/api/flows/… + specs/generated/api/flows/…), mirroring delivery.ts.
|
|
483
|
+
const apiFlowsRoot = path.join(cwd, 'qa', 'api', 'flows');
|
|
484
|
+
if (fs.existsSync(apiFlowsRoot)) {
|
|
485
|
+
for (const d of fs.readdirSync(apiFlowsRoot, { withFileTypes: true })) {
|
|
486
|
+
if (d.isDirectory()) targets.push({ name: path.posix.join('flows', d.name), kind: 'api', isFlow: false });
|
|
392
487
|
}
|
|
393
488
|
}
|
|
394
489
|
|
|
@@ -396,10 +491,15 @@ export function listDashboardTargets(cwd: string): DashboardTarget[] {
|
|
|
396
491
|
}
|
|
397
492
|
|
|
398
493
|
export function resolveTargetType(cwd: string, name: string): DashboardTarget {
|
|
399
|
-
if (fs.existsSync(path.join(cwd, 'qa', 'flows', name))) {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
return { name, isFlow: false };
|
|
494
|
+
if (fs.existsSync(path.join(cwd, 'qa', 'flows', name))) return { name, kind: 'flow', isFlow: true };
|
|
495
|
+
// Screen before api so `dashboard <name>` resolves the same unit as `delivery <name>`
|
|
496
|
+
// when a repo has both qa/screens/<name> and qa/api/<name> (delivery.ts precedence).
|
|
497
|
+
if (fs.existsSync(path.join(cwd, 'qa', 'screens', name))) return { name, kind: 'screen', isFlow: false };
|
|
498
|
+
if (fs.existsSync(path.join(cwd, 'qa', 'api', name))) return { name, kind: 'api', isFlow: false };
|
|
499
|
+
// `flows/<flow>` or a bare api-flow name → qa/api/flows/<flow>
|
|
500
|
+
const apiFlow = name.startsWith('flows/') ? name : path.posix.join('flows', name);
|
|
501
|
+
if (fs.existsSync(path.join(cwd, 'qa', 'api', apiFlow))) return { name: apiFlow, kind: 'api', isFlow: false };
|
|
502
|
+
return { name, kind: 'screen', isFlow: false };
|
|
403
503
|
}
|
|
404
504
|
|
|
405
505
|
// ----------------------------------------------------------------------------
|