@sun-asterisk/sungen 3.2.12-beta.1 → 3.2.12-beta.2
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/dashboard/templates/index.html +1 -1
- package/dist/exporters/csv-exporter.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.js +7 -2
- package/dist/exporters/csv-exporter.js.map +1 -1
- package/dist/exporters/feature-parser.d.ts.map +1 -1
- package/dist/exporters/feature-parser.js +15 -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 +10 -5
- package/dist/exporters/json-exporter.js.map +1 -1
- package/dist/exporters/result-variants.d.ts +23 -5
- package/dist/exporters/result-variants.d.ts.map +1 -1
- package/dist/exporters/result-variants.js +65 -9
- package/dist/exporters/result-variants.js.map +1 -1
- package/dist/exporters/test-data-resolver.d.ts.map +1 -1
- package/dist/exporters/test-data-resolver.js.map +1 -1
- package/package.json +3 -3
- package/src/dashboard/templates/index.html +1 -1
- package/src/exporters/csv-exporter.ts +7 -3
- package/src/exporters/feature-parser.ts +15 -2
- package/src/exporters/json-exporter.ts +10 -6
- package/src/exporters/result-variants.ts +68 -9
- package/src/exporters/test-data-resolver.ts +1 -0
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
import { ApiCatalogEntry, EnvironmentInfo, PlaywrightResult, ScreenSummary, TestCaseRow } from './types';
|
|
26
26
|
import { collectSecretValues, formatApiExpected, formatApiRequest, hasApiCalls, redactSecrets } from './api-testcase-formatter';
|
|
27
27
|
import { SelectorKeyMap, substituteSelectorKeysInStep } from './selector-key-resolver';
|
|
28
|
-
import { resolveResultVariants } from './result-variants';
|
|
28
|
+
import { formatVariantRow, resolveResultVariants } from './result-variants';
|
|
29
29
|
|
|
30
30
|
export interface BuildCsvInput {
|
|
31
31
|
screen: string;
|
|
@@ -70,7 +70,7 @@ export function buildTestCaseRows(input: BuildCsvInput): TestCaseRow[] {
|
|
|
70
70
|
// Data-driven (@cases): expand to one CSV row per executed dataset input (Playwright titles
|
|
71
71
|
// "<scenario> — <label>"); plain scenarios resolve their single result. Shared with the
|
|
72
72
|
// dashboard/JSON exporter via resolveResultVariants so the two can't drift on @cases handling.
|
|
73
|
-
const variants = resolveResultVariants(m, input.results);
|
|
73
|
+
const variants = resolveResultVariants(m, input.results, input.testData);
|
|
74
74
|
|
|
75
75
|
for (const variant of variants) {
|
|
76
76
|
const displayName = `${m.feature.name}${variant.nameSuffix}`;
|
|
@@ -132,7 +132,11 @@ export function buildTestCaseRows(input: BuildCsvInput): TestCaseRow[] {
|
|
|
132
132
|
// Multi-line bullet format keeps delivery CSV/XLSX in sync with the
|
|
133
133
|
// dashboard's Test data section. Multi-line CSV cells are valid (quoted
|
|
134
134
|
// automatically) and render natively as multi-line in XLSX.
|
|
135
|
-
|
|
135
|
+
let testData = formatTestData(m.feature.referencedVars, input.testData, Infinity, '\n');
|
|
136
|
+
// @cases: each report line shows ITS OWN dataset row (executed auto rows and manual
|
|
137
|
+
// split rows both carry variant.row) — one line per case, one dataset per line.
|
|
138
|
+
const variantRow = formatVariantRow(variant, '\n');
|
|
139
|
+
if (variantRow) testData = testData ? `${testData}\n${variantRow}` : variantRow;
|
|
136
140
|
|
|
137
141
|
// Status for this (possibly per-input) row — resolved into `variant` above.
|
|
138
142
|
const result: PlaywrightResult | undefined = variant.result;
|
|
@@ -9,7 +9,7 @@ import { FeatureMetadata, OrderedStep, ScenarioMetadata } from './types';
|
|
|
9
9
|
/**
|
|
10
10
|
* Variables referenced in a scenario: find all {{var_name}} in step text.
|
|
11
11
|
*/
|
|
12
|
-
function extractReferencedVars(scenario: ParsedScenario): string[] {
|
|
12
|
+
function extractReferencedVars(scenario: ParsedScenario, includeComments = false): string[] {
|
|
13
13
|
const vars = new Set<string>();
|
|
14
14
|
for (const step of scenario.steps) {
|
|
15
15
|
const matches = step.text.matchAll(/\{\{([a-zA-Z_][a-zA-Z0-9_.-]*)\}\}/g);
|
|
@@ -28,6 +28,19 @@ function extractReferencedVars(scenario: ParsedScenario): string[] {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
// Manual scenarios carry their real tester procedure in `# Tester verifies:` comment
|
|
32
|
+
// blocks — the {{var}} refs there are the data the tester needs, so include them
|
|
33
|
+
// (issue: manual test cases exported with an empty Test Data cell). Automated
|
|
34
|
+
// scenarios keep step-only scanning: their comments are notes (SPEC-GAP etc.) and
|
|
35
|
+
// would only add noise vars to the delivery report.
|
|
36
|
+
if (includeComments) {
|
|
37
|
+
for (const line of scenario.comments ?? []) {
|
|
38
|
+
const matches = line.matchAll(/\{\{([a-zA-Z_][a-zA-Z0-9_.-]*)\}\}/g);
|
|
39
|
+
for (const match of matches) {
|
|
40
|
+
vars.add(match[1]);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
31
44
|
return Array.from(vars);
|
|
32
45
|
}
|
|
33
46
|
|
|
@@ -85,7 +98,7 @@ export function parseFeatureMetadata(featureFilePath: string): FeatureMetadata {
|
|
|
85
98
|
tags: [...parsed.tags, ...sc.tags],
|
|
86
99
|
stepsName: sc.stepsName,
|
|
87
100
|
extendsName: sc.extendsName,
|
|
88
|
-
referencedVars: extractReferencedVars(sc),
|
|
101
|
+
referencedVars: extractReferencedVars(sc, extractTestcaseType([...parsed.tags, ...sc.tags]) === 'Manual'),
|
|
89
102
|
rawGivenSteps: given,
|
|
90
103
|
rawWhenSteps: when,
|
|
91
104
|
rawThenSteps: then,
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
} from './playwright-report-parser';
|
|
24
24
|
import { ApiCatalogEntry, EnvironmentInfo, PlaywrightResult } from './types';
|
|
25
25
|
import { SelectorKeyMap, substituteSelectorKeysInStep } from './selector-key-resolver';
|
|
26
|
-
import { resolveResultVariants } from './result-variants';
|
|
26
|
+
import { formatVariantRow, resolveResultVariants } from './result-variants';
|
|
27
27
|
import {
|
|
28
28
|
ScenarioSnapshot,
|
|
29
29
|
ScreenSnapshot,
|
|
@@ -123,13 +123,17 @@ export function buildScreenSnapshot(input: BuildScreenSnapshotInput): ScreenSnap
|
|
|
123
123
|
}
|
|
124
124
|
// Dashboard modal uses whitespace-pre-wrap — pass '\n' to render one
|
|
125
125
|
// key/value per line instead of the CSV-friendly "; " join.
|
|
126
|
-
const
|
|
126
|
+
const baseTestDataStr = formatTestData(m.feature.referencedVars, input.testData, Infinity, '\n');
|
|
127
127
|
|
|
128
|
-
// @cases scenarios expand to one snapshot entry per executed dataset row
|
|
129
|
-
//
|
|
130
|
-
// into one Pending/N/A entry
|
|
131
|
-
const variants = resolveResultVariants(m, input.results ?? null);
|
|
128
|
+
// @cases scenarios expand to one snapshot entry per executed dataset row — and manual
|
|
129
|
+
// @cases split per DATASET row (shared with the CSV exporter via resolveResultVariants),
|
|
130
|
+
// so their per-row pass/fail is counted — not collapsed into one Pending/N/A entry.
|
|
131
|
+
const variants = resolveResultVariants(m, input.results ?? null, input.testData);
|
|
132
132
|
for (const variant of variants) {
|
|
133
|
+
// Per-line Test Data: shared vars + THIS variant's dataset row (parity with csv-exporter).
|
|
134
|
+
let testDataStr = baseTestDataStr;
|
|
135
|
+
const variantRow = formatVariantRow(variant, '\n');
|
|
136
|
+
if (variantRow) testDataStr = testDataStr ? `${testDataStr}\n${variantRow}` : variantRow;
|
|
133
137
|
const displayName = `${m.feature.name}${variant.nameSuffix}`;
|
|
134
138
|
const { vpId, category1 } = splitVpAndName(displayName);
|
|
135
139
|
const tcId = generateTcId(input.screen, vpId, fallbackIndex);
|
|
@@ -5,12 +5,42 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { MergedScenario } from './scenario-merger';
|
|
7
7
|
import { PlaywrightResult } from './types';
|
|
8
|
+
import { extractTestcaseType } from './feature-parser';
|
|
8
9
|
|
|
9
10
|
export interface ResultVariant {
|
|
10
|
-
/** Appended to the scenario display name: `''` for a plain scenario, ` — <label>` per
|
|
11
|
+
/** Appended to the scenario display name: `''` for a plain scenario, ` — <label>` per
|
|
11
12
|
* @cases dataset row (so each row gets a distinct category1). */
|
|
12
13
|
nameSuffix: string;
|
|
13
14
|
result?: PlaywrightResult;
|
|
15
|
+
/** The @cases dataset row backing this variant — lets the exporters render the row's own
|
|
16
|
+
* columns into the Test Data cell (`__`-prefixed bookkeeping keys excluded). Present on
|
|
17
|
+
* executed auto rows (matched by label) and on manual split rows. */
|
|
18
|
+
row?: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Read the `@cases:<name>` dataset rows for a scenario from the (flattened) test-data map.
|
|
23
|
+
* `loadTestData()` flattens arrays to JSON strings — accept that shape and raw arrays.
|
|
24
|
+
* Scalar rows normalize to `{ value }`, mirroring the runtime TestDataLoader.cases().
|
|
25
|
+
*/
|
|
26
|
+
export function getCasesDatasetRows(
|
|
27
|
+
m: MergedScenario,
|
|
28
|
+
testData: Record<string, string> | null | undefined,
|
|
29
|
+
): Array<Record<string, unknown>> | null {
|
|
30
|
+
const tag = m.feature.tags.find((t) => t.startsWith('@cases:'));
|
|
31
|
+
if (!tag || !testData) return null;
|
|
32
|
+
let list: unknown = testData[tag.slice('@cases:'.length)];
|
|
33
|
+
if (typeof list === 'string') {
|
|
34
|
+
try { list = JSON.parse(list); } catch { return null; }
|
|
35
|
+
}
|
|
36
|
+
if (!Array.isArray(list) || list.length === 0) return null;
|
|
37
|
+
return list.map((row) =>
|
|
38
|
+
row && typeof row === 'object' && !Array.isArray(row) ? (row as Record<string, unknown>) : { value: row });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Display label of a dataset row — mirrors the runtime TestDataLoader.cases() rule. */
|
|
42
|
+
function rowLabel(row: Record<string, unknown>, i: number): string {
|
|
43
|
+
return String(row.case ?? row.name ?? row.label ?? `row ${i + 1}`);
|
|
14
44
|
}
|
|
15
45
|
|
|
16
46
|
/**
|
|
@@ -18,9 +48,12 @@ export interface ResultVariant {
|
|
|
18
48
|
*
|
|
19
49
|
* A `@cases:<dataset>` scenario compiles to one `test()` per dataset row, whose Playwright title is
|
|
20
50
|
* `<scenario> — <label>` — expand into one variant per EXECUTED row by matching that prefix (so an
|
|
21
|
-
* already-run row carries its real pass/fail)
|
|
22
|
-
*
|
|
23
|
-
*
|
|
51
|
+
* already-run row carries its real pass/fail), attaching the backing dataset row by label.
|
|
52
|
+
*
|
|
53
|
+
* A MANUAL `@cases` scenario never executes, so that expansion can never fire — split it from the
|
|
54
|
+
* DATASET instead: one variant per row (` — <label>`, no result → Pending), each carrying its row
|
|
55
|
+
* so the manual tester gets one report line + its own data per case to walk and record. An auto
|
|
56
|
+
* @cases scenario that has not run yet stays a single line (its expansion belongs to execution).
|
|
24
57
|
*
|
|
25
58
|
* NOTE: this deliberately does NOT gate on `m.spec`. For @cases the compiled test title carries a
|
|
26
59
|
* `— ${__row.__label}` suffix, so `findMatchingSpecTest` (base-name match) often leaves `m.spec`
|
|
@@ -30,13 +63,26 @@ export interface ResultVariant {
|
|
|
30
63
|
export function resolveResultVariants(
|
|
31
64
|
m: MergedScenario,
|
|
32
65
|
results: Map<string, PlaywrightResult> | null,
|
|
66
|
+
testData?: Record<string, string> | null,
|
|
33
67
|
): ResultVariant[] {
|
|
34
68
|
const isCases = m.feature.tags.some((t) => t.startsWith('@cases:'));
|
|
35
|
-
if (isCases
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
69
|
+
if (isCases) {
|
|
70
|
+
const datasetRows = getCasesDatasetRows(m, testData);
|
|
71
|
+
const byLabel = new Map<string, Record<string, unknown>>();
|
|
72
|
+
(datasetRows ?? []).forEach((row, i) => byLabel.set(rowLabel(row, i), row));
|
|
73
|
+
|
|
74
|
+
if (results) {
|
|
75
|
+
const marker = `${m.feature.name} — `;
|
|
76
|
+
const ran = [...results.entries()].filter(([t]) => t.includes(marker));
|
|
77
|
+
if (ran.length) {
|
|
78
|
+
return ran.map(([t, r]) => {
|
|
79
|
+
const label = t.slice(t.indexOf(marker) + marker.length);
|
|
80
|
+
return { nameSuffix: ` — ${label}`, result: r, row: byLabel.get(label) };
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (datasetRows && extractTestcaseType(m.feature.tags) === 'Manual') {
|
|
85
|
+
return datasetRows.map((row, i) => ({ nameSuffix: ` — ${rowLabel(row, i)}`, row }));
|
|
40
86
|
}
|
|
41
87
|
return [{ nameSuffix: '' }];
|
|
42
88
|
}
|
|
@@ -45,3 +91,16 @@ export function resolveResultVariants(
|
|
|
45
91
|
}
|
|
46
92
|
return [{ nameSuffix: '' }];
|
|
47
93
|
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Render a variant's dataset row for the Test Data cell: one `col: value` line per column,
|
|
97
|
+
* `__`-prefixed bookkeeping keys excluded. Returns '' when the variant carries no row.
|
|
98
|
+
*/
|
|
99
|
+
export function formatVariantRow(variant: ResultVariant, separator: string = '\n'): string {
|
|
100
|
+
if (!variant.row) return '';
|
|
101
|
+
const useBullet = separator.includes('\n');
|
|
102
|
+
return Object.entries(variant.row)
|
|
103
|
+
.filter(([k]) => !k.startsWith('__'))
|
|
104
|
+
.map(([k, v]) => `${useBullet ? '• ' : ''}${k}: ${v}`)
|
|
105
|
+
.join(separator);
|
|
106
|
+
}
|
|
@@ -133,6 +133,7 @@ function truncate(s: string, max: number): string {
|
|
|
133
133
|
return s.substring(0, max - 1) + '…';
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
|
|
136
137
|
/**
|
|
137
138
|
* Resolve `{{var}}` test-data placeholders in a step / expected-result line to their concrete values,
|
|
138
139
|
* so the delivery report shows what QA actually verifies (e.g. `text contains {{app_version}}` →
|