@sun-asterisk/sungen 3.2.6 → 3.2.7

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.
Files changed (64) hide show
  1. package/dist/cli/commands/delivery.d.ts.map +1 -1
  2. package/dist/cli/commands/delivery.js +7 -46
  3. package/dist/cli/commands/delivery.js.map +1 -1
  4. package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
  5. package/dist/dashboard/snapshot-builder.js +28 -6
  6. package/dist/dashboard/snapshot-builder.js.map +1 -1
  7. package/dist/dashboard/templates/index.html +20 -20
  8. package/dist/dashboard/types.d.ts +18 -0
  9. package/dist/dashboard/types.d.ts.map +1 -1
  10. package/dist/exporters/api-catalog-loader.d.ts +18 -0
  11. package/dist/exporters/api-catalog-loader.d.ts.map +1 -0
  12. package/dist/exporters/api-catalog-loader.js +77 -0
  13. package/dist/exporters/api-catalog-loader.js.map +1 -0
  14. package/dist/exporters/json-exporter.d.ts +10 -1
  15. package/dist/exporters/json-exporter.d.ts.map +1 -1
  16. package/dist/exporters/json-exporter.js +34 -2
  17. package/dist/exporters/json-exporter.js.map +1 -1
  18. package/dist/exporters/playwright-report-parser.d.ts +14 -0
  19. package/dist/exporters/playwright-report-parser.d.ts.map +1 -1
  20. package/dist/exporters/playwright-report-parser.js +30 -0
  21. package/dist/exporters/playwright-report-parser.js.map +1 -1
  22. package/dist/exporters/xlsx-exporter.d.ts +18 -17
  23. package/dist/exporters/xlsx-exporter.d.ts.map +1 -1
  24. package/dist/exporters/xlsx-exporter.js +25 -595
  25. package/dist/exporters/xlsx-exporter.js.map +1 -1
  26. package/dist/exporters/xlsx-report-builder.d.ts +196 -0
  27. package/dist/exporters/xlsx-report-builder.d.ts.map +1 -0
  28. package/dist/exporters/xlsx-report-builder.js +653 -0
  29. package/dist/exporters/xlsx-report-builder.js.map +1 -0
  30. package/package.json +3 -3
  31. package/src/cli/commands/delivery.ts +8 -42
  32. package/src/dashboard/snapshot-builder.ts +31 -7
  33. package/src/dashboard/templates/index.html +20 -20
  34. package/src/dashboard/types.ts +19 -0
  35. package/src/exporters/api-catalog-loader.ts +44 -0
  36. package/src/exporters/json-exporter.ts +46 -4
  37. package/src/exporters/playwright-report-parser.ts +27 -0
  38. package/src/exporters/xlsx-exporter.ts +34 -658
  39. package/src/exporters/xlsx-report-builder.ts +806 -0
  40. package/dist/cli/types.d.ts +0 -9
  41. package/dist/cli/types.d.ts.map +0 -1
  42. package/dist/cli/types.js +0 -7
  43. package/dist/cli/types.js.map +0 -1
  44. package/dist/generators/gherkin-parser/selector-extractor.d.ts +0 -37
  45. package/dist/generators/gherkin-parser/selector-extractor.d.ts.map +0 -1
  46. package/dist/generators/gherkin-parser/selector-extractor.js +0 -108
  47. package/dist/generators/gherkin-parser/selector-extractor.js.map +0 -1
  48. package/dist/generators/test-generator/types.d.ts +0 -25
  49. package/dist/generators/test-generator/types.d.ts.map +0 -1
  50. package/dist/generators/test-generator/types.js +0 -7
  51. package/dist/generators/test-generator/types.js.map +0 -1
  52. package/dist/generators/types.d.ts +0 -119
  53. package/dist/generators/types.d.ts.map +0 -1
  54. package/dist/generators/types.js +0 -48
  55. package/dist/generators/types.js.map +0 -1
  56. package/dist/orchestrator/figma/node-path-collapser.d.ts +0 -16
  57. package/dist/orchestrator/figma/node-path-collapser.d.ts.map +0 -1
  58. package/dist/orchestrator/figma/node-path-collapser.js +0 -37
  59. package/dist/orchestrator/figma/node-path-collapser.js.map +0 -1
  60. package/src/cli/types.ts +0 -9
  61. package/src/generators/gherkin-parser/selector-extractor.ts +0 -142
  62. package/src/generators/test-generator/types.ts +0 -26
  63. package/src/generators/types.ts +0 -227
  64. package/src/orchestrator/figma/node-path-collapser.ts +0 -38
@@ -76,6 +76,25 @@ export interface FeatureSnapshot {
76
76
  locales?: LocaleSnapshot[]; // locale overlays WITHIN this entry (base + at least one overlay)
77
77
  platform?: 'android' | 'ios'; // one OS run of a @platform:mobile feature (separate entry per OS)
78
78
  executedPlatforms?: ('android' | 'ios')[]; // OSes that produced a result file this run (mobile only)
79
+ /** API detail sheet rows (api-kind units only) — precomputed by the snapshot
80
+ * builder with the same buildApiDetailRows call the delivery CLI uses, so the
81
+ * dashboard XLSX export appends an "API detail" sheet identical to delivery's. */
82
+ apiDetail?: ApiDetailRow[];
83
+ }
84
+
85
+ /**
86
+ * One endpoint row of the "API detail" sheet (api-kind units). Structural copy
87
+ * of ApiDetailRow in src/exporters/types.ts — kept local so this file stays
88
+ * import-free (it is mirrored verbatim into dashboard/src/types.ts).
89
+ */
90
+ export interface ApiDetailRow {
91
+ endpoint: string; // catalog path, e.g. /users/count?email=:email
92
+ method: string; // HTTP method
93
+ authDatasource: string; // datasource + any @auth tag
94
+ requestShape: string; // body fields / params / encoding
95
+ expectedStatusMatrix: string; // expect.status + @cases dataset pointer
96
+ flowSteps: string; // ordered @api:<name> chain for flow scenarios
97
+ concurrencyInvariant: string; // @concurrent invariant, when present
79
98
  }
80
99
 
81
100
  /**
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Load the apis.yaml endpoint catalog for an api-kind unit — shared by the
3
+ * delivery CLI (CSV/XLSX export) and the dashboard snapshot builder so both
4
+ * surfaces render the identical request/response contract for API test cases.
5
+ */
6
+
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ import { parse as parseYaml } from 'yaml';
10
+ import { ApiCatalogEntry } from './types';
11
+
12
+ /**
13
+ * Load the apis.yaml catalog for an api-kind unit, merging the shared catalog under
14
+ * the unit catalog. The runtime resolves an endpoint unit-scope-first then falls back
15
+ * to the shared catalog (driver-api api-catalog.ts), so an endpoint defined only in
16
+ * `qa/api/apis.yaml` still runs — we must load it too, or its request block would come
17
+ * back empty (the very bug this feature fixes). Unit wins on a name collision.
18
+ * Returns an empty object when neither file is present (graceful degradation).
19
+ */
20
+ export function loadApiCatalog(
21
+ cwd: string,
22
+ kind: 'screen' | 'flow' | 'api',
23
+ screen: string,
24
+ ): Record<string, ApiCatalogEntry> {
25
+ if (kind !== 'api') return {};
26
+ const shared = readApiCatalogFile(path.join(cwd, 'qa', 'api', 'apis.yaml'));
27
+ const unit = readApiCatalogFile(path.join(cwd, 'qa', 'api', screen, 'api', 'apis.yaml'));
28
+ return { ...shared, ...unit };
29
+ }
30
+
31
+ /** Parse one apis.yaml file into an endpoint map, unwrapping the optional `apis:` wrapper. */
32
+ export function readApiCatalogFile(catalogPath: string): Record<string, ApiCatalogEntry> {
33
+ if (!fs.existsSync(catalogPath)) return {};
34
+ try {
35
+ const parsed = parseYaml(fs.readFileSync(catalogPath, 'utf-8')) as Record<string, unknown> | null;
36
+ if (parsed && typeof parsed === 'object') {
37
+ // The runtime loader accepts both a flat map and an `apis:` wrapper — unwrap it
38
+ // here too so both loaders read the same file identically.
39
+ const root = parsed.apis && typeof parsed.apis === 'object' ? parsed.apis : parsed;
40
+ return root as Record<string, ApiCatalogEntry>;
41
+ }
42
+ } catch { /* malformed yaml — skip */ }
43
+ return {};
44
+ }
@@ -15,12 +15,13 @@ import {
15
15
  splitVpAndName,
16
16
  } from './feature-parser';
17
17
  import { formatPrecondition, keepActor, stripActor } from './step-formatter';
18
- import { formatTestData } from './test-data-resolver';
18
+ import { formatTestData, substituteTestDataVars } from './test-data-resolver';
19
+ import { collectSecretValues, formatApiExpected, formatApiRequest, hasApiCalls, redactSecrets } from './api-testcase-formatter';
19
20
  import {
20
21
  formatExecutedDate,
21
22
  statusToTestResult,
22
23
  } from './playwright-report-parser';
23
- import { EnvironmentInfo, PlaywrightResult } from './types';
24
+ import { ApiCatalogEntry, EnvironmentInfo, PlaywrightResult } from './types';
24
25
  import { SelectorKeyMap, substituteSelectorKeysInStep } from './selector-key-resolver';
25
26
  import { resolveResultVariants } from './result-variants';
26
27
  import {
@@ -53,6 +54,15 @@ export interface BuildScreenSnapshotInput {
53
54
  * link" instead of the VI key "Click [VỀ KUDOS] link".
54
55
  */
55
56
  selectorKeyMap?: SelectorKeyMap;
57
+ /**
58
+ * Unit kind. When `'api'`, Steps/Expected are composed from the request
59
+ * contract (Method/API/Header/Body) and response contract (Status + Response)
60
+ * exactly like csv-exporter's buildTestCaseRows — so the dashboard UI and its
61
+ * XLSX export show the same cells as the delivery CSV.
62
+ */
63
+ kind?: 'screen' | 'flow' | 'api';
64
+ /** apis.yaml catalog (keyed by endpoint name) for api-kind units. */
65
+ apiCatalog?: Record<string, ApiCatalogEntry>;
56
66
  }
57
67
 
58
68
  /**
@@ -77,8 +87,40 @@ export function buildScreenSnapshot(input: BuildScreenSnapshotInput): ScreenSnap
77
87
  : text;
78
88
  const formatStep = (s: { text: string; bucket: 'given' | 'when' | 'then' }) =>
79
89
  localize(s.bucket === 'given' ? keepActor(s.text) : stripActor(s.text));
80
- const steps = m.resolvedSteps.map(formatStep).filter(Boolean);
81
- const expectedResults = m.resolvedExpected.map(formatStep).filter(Boolean);
90
+ let steps = m.resolvedSteps.map(formatStep).filter(Boolean);
91
+ let expectedResults = m.resolvedExpected.map(formatStep).filter(Boolean);
92
+
93
+ // API test cases: mirror csv-exporter's request/response rendering so the
94
+ // dashboard (UI + XLSX export) shows the same Steps / Expected results as
95
+ // the delivery CSV. The cells are composed with the CSV's exact single-vs-
96
+ // numbered join, then stored as ONE array entry — the dashboard XLSX
97
+ // adapter's formatList() (1 entry → raw) reproduces the delivery cell
98
+ // byte-for-byte, and the UI renders the block with pre-wrap.
99
+ if (input.kind === 'api' && input.apiCatalog && hasApiCalls(m.feature.tags)) {
100
+ // Delivery cells substitute {{var}} test-data refs (csv-exporter's localize);
101
+ // scope that to the API branch so non-API snapshot behavior is untouched.
102
+ const localizeApi = (text: string): string => substituteTestDataVars(localize(text), input.testData);
103
+ const formatStepApi = (s: { text: string; bucket: 'given' | 'when' | 'then' }) =>
104
+ localizeApi(s.bucket === 'given' ? keepActor(s.text) : stripActor(s.text));
105
+ const joinCell = (lines: string[]): string =>
106
+ lines.length === 0 ? '' :
107
+ lines.length === 1 ? lines[0] :
108
+ lines.map((l, i) => `${i + 1}. ${l}`).join('\n');
109
+ let stepsCell = joinCell(m.resolvedSteps.map(formatStepApi).filter(Boolean));
110
+ let expectedCell = joinCell(m.resolvedExpected.map(formatStepApi).filter(Boolean));
111
+ const apiRequest = formatApiRequest(m.feature.tags, input.apiCatalog, localizeApi);
112
+ if (apiRequest) stepsCell = stepsCell ? `${stepsCell}\n\n${apiRequest}` : apiRequest;
113
+ const apiExpected = formatApiExpected(m.resolvedExpected, m.feature.tags, localizeApi);
114
+ if (apiExpected) expectedCell = apiExpected;
115
+ // Same value-based redaction backstop as the CSV path.
116
+ const secrets = collectSecretValues(input.testData);
117
+ if (secrets.length > 0) {
118
+ stepsCell = redactSecrets(stepsCell, secrets);
119
+ expectedCell = redactSecrets(expectedCell, secrets);
120
+ }
121
+ steps = stepsCell ? [stepsCell] : [];
122
+ expectedResults = expectedCell ? [expectedCell] : [];
123
+ }
82
124
  // Dashboard modal uses whitespace-pre-wrap — pass '\n' to render one
83
125
  // key/value per line instead of the CSV-friendly "; " join.
84
126
  const testDataStr = formatTestData(m.feature.referencedVars, input.testData, Infinity, '\n');
@@ -3,8 +3,35 @@
3
3
  */
4
4
 
5
5
  import * as fs from 'fs';
6
+ import * as path from 'path';
6
7
  import { PlaywrightResult } from './types';
7
8
 
9
+ /**
10
+ * Resolve the results.json file for a unit — shared by the delivery CLI and
11
+ * the dashboard snapshot builder so both surfaces resolve execution status
12
+ * from the SAME file (they drifted: the dashboard only looked for the
13
+ * per-feature file and showed every scenario Pending on projects whose
14
+ * playwright.config writes only the default global test-results/results.json).
15
+ *
16
+ * Candidate order (first existing wins):
17
+ * 1. <genDir>/<featureBaseName>-test-result.<SUNGEN_ENV>.json (env runs)
18
+ * 2. <genDir>/<featureBaseName>-test-result.json (per-feature reporter)
19
+ * 3. <cwd>/test-results/results.<SUNGEN_ENV>.json
20
+ * 4. <cwd>/test-results/results.json (playwright default)
21
+ */
22
+ export function resolveResultsPath(cwd: string, genDir: string, featureBaseName: string): string | null {
23
+ const env = process.env.SUNGEN_ENV;
24
+ const candidates: string[] = [];
25
+ if (env) candidates.push(path.join(genDir, `${featureBaseName}-test-result.${env}.json`));
26
+ candidates.push(path.join(genDir, `${featureBaseName}-test-result.json`));
27
+ if (env) candidates.push(path.join(cwd, 'test-results', `results.${env}.json`));
28
+ candidates.push(path.join(cwd, 'test-results', 'results.json'));
29
+ for (const c of candidates) {
30
+ if (fs.existsSync(c)) return c;
31
+ }
32
+ return null;
33
+ }
34
+
8
35
  /**
9
36
  * Shape of the JSON reporter output we care about.
10
37
  * See https://playwright.dev/docs/test-reporters#json-reporter