swipium 1.2.0 → 1.4.0
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/README.md +11 -11
- package/dist/appMap/featureIndex.js +183 -0
- package/dist/appMap/featureIndex.js.map +1 -0
- package/dist/featureTesting/executionBootstrap.js +14 -10
- package/dist/featureTesting/executionBootstrap.js.map +1 -1
- package/dist/featureTesting/featureMap.js +53 -0
- package/dist/featureTesting/featureMap.js.map +1 -0
- package/dist/featureTesting/featureScope.js +354 -0
- package/dist/featureTesting/featureScope.js.map +1 -0
- package/dist/featureTesting/mapFeatureScope.js +143 -0
- package/dist/featureTesting/mapFeatureScope.js.map +1 -0
- package/dist/featureTesting/objectiveModel.js +129 -0
- package/dist/featureTesting/objectiveModel.js.map +1 -0
- package/dist/featureTesting/resultMerge.js +112 -0
- package/dist/featureTesting/resultMerge.js.map +1 -0
- package/dist/featureTesting/sources.js +96 -0
- package/dist/featureTesting/sources.js.map +1 -0
- package/dist/featureTesting/suiteBridge.js +51 -0
- package/dist/featureTesting/suiteBridge.js.map +1 -0
- package/dist/featureTesting/synonyms.js +73 -0
- package/dist/featureTesting/synonyms.js.map +1 -0
- package/dist/featureTesting/testCaseFactory.js +107 -0
- package/dist/featureTesting/testCaseFactory.js.map +1 -0
- package/dist/featureTesting/testPlan.js +50 -0
- package/dist/featureTesting/testPlan.js.map +1 -0
- package/dist/server.js +11 -0
- package/dist/server.js.map +1 -1
- package/dist/tools/agent.js +12 -0
- package/dist/tools/agent.js.map +1 -1
- package/dist/tools/appMap.js +123 -1
- package/dist/tools/appMap.js.map +1 -1
- package/dist/tools/build.js +172 -0
- package/dist/tools/build.js.map +1 -0
- package/dist/tools/bundletool.js +145 -0
- package/dist/tools/bundletool.js.map +1 -0
- package/dist/tools/capabilities.js +24 -0
- package/dist/tools/capabilities.js.map +1 -1
- package/dist/tools/featureTesting.js +344 -0
- package/dist/tools/featureTesting.js.map +1 -0
- package/dist/tools/resolveArtifact.js +80 -0
- package/dist/tools/resolveArtifact.js.map +1 -0
- package/dist/tools/resolveTarget.js +67 -0
- package/dist/tools/resolveTarget.js.map +1 -0
- package/dist/tools/snapshot.js +41 -1
- package/dist/tools/snapshot.js.map +1 -1
- package/dist/version.js +16 -2
- package/dist/version.js.map +1 -1
- package/docs/mcp-server.md +1 -1
- package/docs/tools.md +52 -3
- package/package.json +1 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Feature objective model (SWIPIUM-REQ-03 "Objective Model Requirements"). PURE, best-effort,
|
|
2
|
+
// deterministic. From a resolved FeatureScope it derives what the feature is *for* — happy path,
|
|
3
|
+
// expected outputs, inputs, business rules (marked as hypotheses when low-confidence), negative and
|
|
4
|
+
// edge cases, destructive boundaries, and the oracle strategy. Every claim carries provenance and a
|
|
5
|
+
// confidence so a low-confidence hypothesis is never presented as proof (Non-Goals).
|
|
6
|
+
const AUTH_TERMS = new Set(['auth', 'login', 'signin', 'authentication', 'credentials', 'password', 'account', 'register', 'signup']);
|
|
7
|
+
const SEARCH_TERMS = new Set(['search', 'find', 'filter', 'query', 'lookup', 'browse']);
|
|
8
|
+
const CREATE_TERMS = new Set(['create', 'add', 'new', 'compose', 'upload', 'post']);
|
|
9
|
+
const PAYMENT_TERMS = new Set(['checkout', 'payment', 'pay', 'purchase', 'order', 'subscribe', 'billing']);
|
|
10
|
+
const DESTRUCTIVE_TERMS = new Set(['delete', 'remove', 'destroy', 'clear', 'wipe', 'cancel', 'logout', 'deactivate']);
|
|
11
|
+
function has(terms, set) {
|
|
12
|
+
return terms.some((t) => set.has(t));
|
|
13
|
+
}
|
|
14
|
+
export function buildObjective(scope) {
|
|
15
|
+
const terms = scope.matchedTerms;
|
|
16
|
+
const provenance = [];
|
|
17
|
+
const happyPath = [];
|
|
18
|
+
const expectedOutputs = [];
|
|
19
|
+
const inputFields = [];
|
|
20
|
+
const businessRules = [];
|
|
21
|
+
const hypotheses = [];
|
|
22
|
+
const externalDependencies = [];
|
|
23
|
+
const negativeCases = [];
|
|
24
|
+
const edgeCases = [];
|
|
25
|
+
const destructiveBoundaries = [];
|
|
26
|
+
// ---- entry → happy path ----
|
|
27
|
+
const entry = scope.entryPoints[0];
|
|
28
|
+
if (entry) {
|
|
29
|
+
happyPath.push(`Open the ${entry.kind.replace('_', ' ')} "${entry.value}"`);
|
|
30
|
+
provenance.push({ source: entry.kind === 'route' || entry.kind === 'deep_link' ? 'route' : entry.kind === 'runtime_node' ? 'runtime' : 'screen', detail: `entry point ${entry.value}`, confidence: entry.confidence });
|
|
31
|
+
}
|
|
32
|
+
else if (scope.staticScreens[0]) {
|
|
33
|
+
happyPath.push(`Navigate to ${scope.staticScreens[0].name}`);
|
|
34
|
+
provenance.push({ source: 'screen', detail: scope.staticScreens[0].name, confidence: scope.staticScreens[0].confidence });
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
happyPath.push(`Navigate to the ${scope.title} surface`);
|
|
38
|
+
}
|
|
39
|
+
// ---- vocabulary-driven path + outputs + inputs ----
|
|
40
|
+
if (has(terms, SEARCH_TERMS)) {
|
|
41
|
+
happyPath.push('Enter a query and apply the search/filter');
|
|
42
|
+
expectedOutputs.push('A result list updates to reflect the query');
|
|
43
|
+
inputFields.push({ name: 'searchTerm', type: 'text', required: true, source: 'search vocabulary' });
|
|
44
|
+
negativeCases.push('Empty query → no crash; shows empty/zero-results state');
|
|
45
|
+
edgeCases.push('Query with special characters / very long input');
|
|
46
|
+
provenance.push({ source: 'vocabulary', detail: 'search/filter terms', confidence: 0.5 });
|
|
47
|
+
}
|
|
48
|
+
if (has(terms, CREATE_TERMS)) {
|
|
49
|
+
happyPath.push('Fill the required fields and submit');
|
|
50
|
+
expectedOutputs.push('The new item appears / a success confirmation is shown');
|
|
51
|
+
negativeCases.push('Submit with required fields blank → validation error, no creation');
|
|
52
|
+
edgeCases.push('Duplicate / boundary-length values; interruption mid-create');
|
|
53
|
+
businessRules.push('Required fields must be validated before submission');
|
|
54
|
+
hypotheses.push('Required-field set is inferred from vocabulary, not a schema — verify against the form');
|
|
55
|
+
provenance.push({ source: 'vocabulary', detail: 'create/add terms', confidence: 0.45 });
|
|
56
|
+
}
|
|
57
|
+
if (has(terms, AUTH_TERMS)) {
|
|
58
|
+
inputFields.push({ name: 'email', type: 'email', required: true, source: 'auth vocabulary' });
|
|
59
|
+
inputFields.push({ name: 'password', type: 'password', required: true, source: 'auth vocabulary' });
|
|
60
|
+
expectedOutputs.push('On valid credentials the authenticated home/landing surface is reached');
|
|
61
|
+
negativeCases.push('Invalid credentials → inline error, stays on the login screen');
|
|
62
|
+
edgeCases.push('Locked/rate-limited account; OTP/2FA step');
|
|
63
|
+
externalDependencies.push('Authentication backend / identity provider');
|
|
64
|
+
businessRules.push('Invalid credentials must not grant access');
|
|
65
|
+
provenance.push({ source: 'vocabulary', detail: 'auth terms', confidence: 0.6 });
|
|
66
|
+
}
|
|
67
|
+
if (has(terms, PAYMENT_TERMS)) {
|
|
68
|
+
happyPath.push('Proceed through the checkout/payment step');
|
|
69
|
+
expectedOutputs.push('An order/subscription confirmation is shown');
|
|
70
|
+
externalDependencies.push('Payment processor (use sandbox/test mode)');
|
|
71
|
+
destructiveBoundaries.push('Do NOT submit a real payment — only a sandbox/test method, with explicit consent');
|
|
72
|
+
negativeCases.push('Declined card → error surfaced, no charge');
|
|
73
|
+
businessRules.push('A charge must only occur after explicit user confirmation');
|
|
74
|
+
provenance.push({ source: 'vocabulary', detail: 'payment terms', confidence: 0.55 });
|
|
75
|
+
}
|
|
76
|
+
if (has(terms, DESTRUCTIVE_TERMS)) {
|
|
77
|
+
destructiveBoundaries.push('Destructive actions (delete/remove/cancel) require disposable test state + candidate-bound consent');
|
|
78
|
+
negativeCases.push('Cancel the destructive confirmation → nothing is destroyed');
|
|
79
|
+
edgeCases.push('Undo / recovery path after the destructive action');
|
|
80
|
+
provenance.push({ source: 'vocabulary', detail: 'destructive terms', confidence: 0.5 });
|
|
81
|
+
}
|
|
82
|
+
// ---- external dependencies from services in scope ----
|
|
83
|
+
for (const dep of scope.dataDependencies) {
|
|
84
|
+
if (dep.kind === 'external' || dep.kind === 'api') {
|
|
85
|
+
externalDependencies.push(dep.name);
|
|
86
|
+
provenance.push({ source: 'symbol', detail: dep.evidence, confidence: 0.6 });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// ---- existing tests sharpen the objective ----
|
|
90
|
+
for (const t of scope.existingTests) {
|
|
91
|
+
provenance.push({ source: 'test', detail: `${t.id} ${t.title}`, confidence: 0.7 });
|
|
92
|
+
}
|
|
93
|
+
// ---- defaults / dedupe ----
|
|
94
|
+
if (!happyPath.some((s) => /verif|confirm|outcome|result/i.test(s)))
|
|
95
|
+
happyPath.push(`Verify the ${scope.title} outcome is shown without an error surface`);
|
|
96
|
+
if (!expectedOutputs.length)
|
|
97
|
+
expectedOutputs.push(`The ${scope.title} screen renders its primary content without an error surface`);
|
|
98
|
+
// ---- oracle strategy ----
|
|
99
|
+
const oracleStrategy = scope.runtimeScreens.length
|
|
100
|
+
? 'structured'
|
|
101
|
+
: externalDependencies.length
|
|
102
|
+
? 'api_assisted'
|
|
103
|
+
: scope.staticScreens.length
|
|
104
|
+
? 'structured'
|
|
105
|
+
: 'manual';
|
|
106
|
+
const userGoal = `As a user, I want to use ${scope.title.toLowerCase()} so that I accomplish its intended outcome.`;
|
|
107
|
+
// objective confidence tracks scope confidence but is capped lower since the model is inferential
|
|
108
|
+
const confidence = Math.round(Math.min(0.9, scope.confidence * 0.9 + 0.1) * 100) / 100;
|
|
109
|
+
return {
|
|
110
|
+
featureId: scope.featureId,
|
|
111
|
+
userGoal,
|
|
112
|
+
primaryHappyPath: dedupe(happyPath),
|
|
113
|
+
expectedOutputs: dedupe(expectedOutputs),
|
|
114
|
+
inputFields,
|
|
115
|
+
businessRules: dedupe(businessRules),
|
|
116
|
+
externalDependencies: dedupe(externalDependencies),
|
|
117
|
+
negativeCases: dedupe(negativeCases),
|
|
118
|
+
edgeCases: dedupe(edgeCases),
|
|
119
|
+
destructiveBoundaries: dedupe(destructiveBoundaries),
|
|
120
|
+
oracleStrategy,
|
|
121
|
+
confidence,
|
|
122
|
+
provenance,
|
|
123
|
+
hypotheses: dedupe(hypotheses),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function dedupe(arr) {
|
|
127
|
+
return [...new Set(arr)];
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=objectiveModel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"objectiveModel.js","sourceRoot":"","sources":["../../src/featureTesting/objectiveModel.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Merge a focused feature run back into cases + the durable feature map (SWIPIUM-REQ-03
|
|
2
|
+
// "App Map Update Requirements" + "Runtime Execution Requirements"). PURE. Given the scope, the
|
|
3
|
+
// generated cases, and the signals a run produced (qa_note outcomes, runtime screens visited,
|
|
4
|
+
// evidence, exploration terminal state), it fills each case's actual result/status/evidence and
|
|
5
|
+
// computes the feature's coverage delta for persistence. Honest: a feature blocked by a gate
|
|
6
|
+
// becomes `blocked` with guidance, never a false `fail`.
|
|
7
|
+
function tokens(s) {
|
|
8
|
+
return new Set(s
|
|
9
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
10
|
+
.toLowerCase()
|
|
11
|
+
.split(/[^a-z0-9]+/)
|
|
12
|
+
.filter((t) => t.length > 2));
|
|
13
|
+
}
|
|
14
|
+
/** Best-effort match a run note to a generated case by title/workflow token overlap. */
|
|
15
|
+
function matchNote(testCase, notes) {
|
|
16
|
+
const ct = tokens(testCase.title);
|
|
17
|
+
let best;
|
|
18
|
+
for (const n of notes) {
|
|
19
|
+
const nt = tokens(n.workflow);
|
|
20
|
+
let overlap = 0;
|
|
21
|
+
for (const t of nt)
|
|
22
|
+
if (ct.has(t))
|
|
23
|
+
overlap++;
|
|
24
|
+
if (overlap > 0 && (!best || overlap > best.overlap))
|
|
25
|
+
best = { note: n, overlap };
|
|
26
|
+
}
|
|
27
|
+
return best?.note;
|
|
28
|
+
}
|
|
29
|
+
function noteToStatus(outcome) {
|
|
30
|
+
switch (outcome) {
|
|
31
|
+
case 'pass':
|
|
32
|
+
return 'pass';
|
|
33
|
+
case 'fail':
|
|
34
|
+
return 'fail';
|
|
35
|
+
case 'blocked':
|
|
36
|
+
return 'blocked';
|
|
37
|
+
case 'skipped':
|
|
38
|
+
case 'not_applicable':
|
|
39
|
+
return 'skipped';
|
|
40
|
+
default:
|
|
41
|
+
return 'not_run';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export function mergeFeatureRun(scope, cases, signals, prior) {
|
|
45
|
+
const globallyBlocked = !!signals.blocked || signals.exploration?.state === 'blocked';
|
|
46
|
+
const mergedCases = cases.map((c) => {
|
|
47
|
+
const note = matchNote(c, signals.notes);
|
|
48
|
+
let status = c.status;
|
|
49
|
+
const actualResult = [...c.actualResult];
|
|
50
|
+
const evidence = [...c.evidence];
|
|
51
|
+
if (note) {
|
|
52
|
+
status = noteToStatus(note.outcome);
|
|
53
|
+
actualResult.push(`${note.outcome}${note.reason ? ` — ${note.reason}` : ''}`);
|
|
54
|
+
if (note.artifactUris)
|
|
55
|
+
evidence.push(...note.artifactUris);
|
|
56
|
+
}
|
|
57
|
+
else if (globallyBlocked && status === 'not_run') {
|
|
58
|
+
// Honest: the run was blocked before reaching this case — blocked, not failed.
|
|
59
|
+
status = 'blocked';
|
|
60
|
+
actualResult.push(`blocked — ${signals.blockReason ?? signals.exploration?.stoppedReason ?? 'feature run was blocked before this case'}`);
|
|
61
|
+
}
|
|
62
|
+
else if (status === 'not_run') {
|
|
63
|
+
actualResult.push('not executed in this run');
|
|
64
|
+
}
|
|
65
|
+
return { ...c, status, actualResult, evidence: [...new Set(evidence)] };
|
|
66
|
+
});
|
|
67
|
+
const casesPassed = mergedCases.filter((c) => c.status === 'pass').length;
|
|
68
|
+
const casesFailed = mergedCases.filter((c) => c.status === 'fail').length;
|
|
69
|
+
const casesBlocked = mergedCases.filter((c) => c.status === 'blocked').length;
|
|
70
|
+
const casesRun = mergedCases.filter((c) => c.status === 'pass' || c.status === 'fail').length;
|
|
71
|
+
let statusAfter;
|
|
72
|
+
if (globallyBlocked && casesRun === 0)
|
|
73
|
+
statusAfter = 'blocked';
|
|
74
|
+
else if (casesRun === 0)
|
|
75
|
+
statusAfter = prior?.status === 'covered' ? 'covered' : 'not_tested';
|
|
76
|
+
else if (casesRun === mergedCases.length && casesFailed === 0 && casesBlocked === 0)
|
|
77
|
+
statusAfter = 'covered';
|
|
78
|
+
else
|
|
79
|
+
statusAfter = 'partial';
|
|
80
|
+
const newRuntimeScreens = signals.visitedRuntimeScreens.filter((id) => !(prior?.runtimeScreens ?? []).includes(id));
|
|
81
|
+
const runtimeScreens = [...new Set([...(prior?.runtimeScreens ?? []), ...scope.runtimeScreens.map((s) => s.id ?? s.name).filter(Boolean), ...signals.visitedRuntimeScreens])];
|
|
82
|
+
const staticScreens = [...new Set([...(prior?.staticScreens ?? []), ...scope.staticScreens.map((s) => s.file ?? s.name).filter(Boolean)])];
|
|
83
|
+
const blockers = [];
|
|
84
|
+
if (globallyBlocked)
|
|
85
|
+
blockers.push(signals.blockGuidance ?? signals.blockReason ?? signals.exploration?.stoppedReason ?? 'feature run blocked');
|
|
86
|
+
for (const n of signals.notes)
|
|
87
|
+
if (n.outcome === 'blocked')
|
|
88
|
+
blockers.push(`${n.workflow}: ${n.reason ?? 'blocked'}${n.recommendedSetup ? ` — ${n.recommendedSetup}` : ''}`);
|
|
89
|
+
const coverage = {
|
|
90
|
+
featureId: scope.featureId,
|
|
91
|
+
title: scope.title,
|
|
92
|
+
query: scope.query,
|
|
93
|
+
status: statusAfter,
|
|
94
|
+
runtimeScreens,
|
|
95
|
+
staticScreens,
|
|
96
|
+
linkConfidence: Math.max(scope.confidence, prior?.linkConfidence ?? 0),
|
|
97
|
+
cases: mergedCases.map((c) => ({ id: c.id, title: c.title, status: c.status, creativity: c.creativity })),
|
|
98
|
+
blockers: [...new Set([...(prior?.blockers ?? []), ...blockers])].slice(0, 20),
|
|
99
|
+
coverageGaps: scope.coverageGaps.map((g) => `${g.area}: ${g.reason}`),
|
|
100
|
+
automationReadiness: prior?.automationReadiness ?? 'partial',
|
|
101
|
+
evidence: [...new Set([...(prior?.evidence ?? []), ...signals.evidence])].slice(0, 40),
|
|
102
|
+
};
|
|
103
|
+
const summary = globallyBlocked
|
|
104
|
+
? `🚧 ${scope.title} blocked: ${signals.blockReason ?? signals.exploration?.stoppedReason ?? 'gate encountered'} (${casesBlocked} case(s) blocked).`
|
|
105
|
+
: `${statusAfter === 'covered' ? '✅' : '◐'} ${scope.title}: ${casesPassed} passed, ${casesFailed} failed, ${casesBlocked} blocked; ${newRuntimeScreens.length} new runtime screen(s).`;
|
|
106
|
+
return {
|
|
107
|
+
cases: mergedCases,
|
|
108
|
+
coverage,
|
|
109
|
+
delta: { statusBefore: prior?.status, statusAfter, casesPassed, casesFailed, casesBlocked, newRuntimeScreens, summary },
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=resultMerge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resultMerge.js","sourceRoot":"","sources":["../../src/featureTesting/resultMerge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// Ground-truth gathering for feature scope (SWIPIUM-REQ-03 "Feature Scope Model" sources). Reads the
|
|
2
|
+
// durable, on-disk Swipium artifacts so the pure scope ranker has runtime screens + existing tests to
|
|
3
|
+
// search: the latest exploration screen graph (runtime nodes) and authored flows / generated test
|
|
4
|
+
// cases. Best-effort and defensive — a malformed file is skipped, never fatal.
|
|
5
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
6
|
+
import { join, basename, extname } from 'node:path';
|
|
7
|
+
import { parse as parseYaml } from 'yaml';
|
|
8
|
+
/** Map a serialized exploration graph's nodes into the runtime-screen inputs the ranker searches. */
|
|
9
|
+
export function runtimeScreensFromGraph(graph) {
|
|
10
|
+
if (!graph || !Array.isArray(graph.nodes))
|
|
11
|
+
return [];
|
|
12
|
+
return graph.nodes.map((n) => ({
|
|
13
|
+
id: n.id,
|
|
14
|
+
title: n.title,
|
|
15
|
+
route: n.urlOrRoute,
|
|
16
|
+
text: n.elements.map((e) => `${e.label ?? ''} ${e.locator?.value ?? ''}`).join(' ').trim() || undefined,
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
/** Read + parse the latest exploration graph JSON from a known artifact file path. */
|
|
20
|
+
export function loadGraphFromFile(path) {
|
|
21
|
+
if (!path || !existsSync(path))
|
|
22
|
+
return undefined;
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Gather existing tests Swipium already knows about: generated test cases + authored flows. */
|
|
31
|
+
export function gatherExistingTests(root) {
|
|
32
|
+
const out = [];
|
|
33
|
+
const seen = new Set();
|
|
34
|
+
const add = (ref) => {
|
|
35
|
+
if (seen.has(ref.id))
|
|
36
|
+
return;
|
|
37
|
+
seen.add(ref.id);
|
|
38
|
+
out.push(ref);
|
|
39
|
+
};
|
|
40
|
+
// .swipium/testcases/*.{yaml,yml,json} → { cases: [{ id, title }] }
|
|
41
|
+
const tcDir = join(root, '.swipium', 'testcases');
|
|
42
|
+
if (existsSync(tcDir)) {
|
|
43
|
+
for (const name of safeReaddir(tcDir)) {
|
|
44
|
+
const ext = extname(name).toLowerCase();
|
|
45
|
+
if (!['.yaml', '.yml', '.json'].includes(ext))
|
|
46
|
+
continue;
|
|
47
|
+
const path = join(tcDir, name);
|
|
48
|
+
try {
|
|
49
|
+
const text = readFileSync(path, 'utf8');
|
|
50
|
+
const doc = ext === '.json' ? JSON.parse(text) : parseYaml(text);
|
|
51
|
+
const cases = (doc?.cases ?? doc);
|
|
52
|
+
if (Array.isArray(cases)) {
|
|
53
|
+
for (const c of cases) {
|
|
54
|
+
if (!c?.id && !c?.title)
|
|
55
|
+
continue;
|
|
56
|
+
add({ id: c.id ?? c.title, title: c.title ?? c.id, source: 'testcase', path });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
/* skip malformed */
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// .swipium/flows/*.{yaml,yml} → flow name (file + `name:` field)
|
|
66
|
+
const flowsDir = join(root, '.swipium', 'flows');
|
|
67
|
+
if (existsSync(flowsDir)) {
|
|
68
|
+
for (const name of safeReaddir(flowsDir)) {
|
|
69
|
+
const ext = extname(name).toLowerCase();
|
|
70
|
+
if (!['.yaml', '.yml'].includes(ext))
|
|
71
|
+
continue;
|
|
72
|
+
const path = join(flowsDir, name);
|
|
73
|
+
const base = basename(name, ext);
|
|
74
|
+
let title = base;
|
|
75
|
+
try {
|
|
76
|
+
const doc = parseYaml(readFileSync(path, 'utf8'));
|
|
77
|
+
if (doc?.name)
|
|
78
|
+
title = doc.name;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
/* use filename */
|
|
82
|
+
}
|
|
83
|
+
add({ id: `flow:${base}`, title, source: 'flow', path });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
function safeReaddir(dir) {
|
|
89
|
+
try {
|
|
90
|
+
return readdirSync(dir);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=sources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sources.js","sourceRoot":"","sources":["../../src/featureTesting/sources.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// SWIPIUM-REQ-03 / REQ-06 Fix Group 3 — bridge focused-feature cases into the durable, project-level
|
|
2
|
+
// test suite (.swipium/test-suite.json). Feature testing used to persist canonical facts ONLY under
|
|
3
|
+
// .swipium/feature-map; this converts each FeatureTestCase into a CanonicalTestCase (linked to the
|
|
4
|
+
// featureId + map screens + evidence) so applyMerge() can fold them into the single source of truth.
|
|
5
|
+
import { normalizeCase } from '../testSuite/generator.js';
|
|
6
|
+
const ACTUAL_STATUS = { not_run: 'not_run', pass: 'pass', fail: 'fail', blocked: 'blocked', skipped: 'skipped' };
|
|
7
|
+
export function featureCasesToCanonical(cases, scope, now, platform) {
|
|
8
|
+
const platforms = [platform ?? 'android'];
|
|
9
|
+
return cases.map((c) => {
|
|
10
|
+
const mapLinks = [
|
|
11
|
+
{ kind: 'feature', id: c.featureId, label: scope.title },
|
|
12
|
+
...c.mapLinks.staticScreens.map((id) => ({ kind: 'static_screen', id })),
|
|
13
|
+
...c.mapLinks.runtimeScreens.map((id) => ({ kind: 'runtime_screen', id })),
|
|
14
|
+
];
|
|
15
|
+
const actualStatus = ACTUAL_STATUS[c.status] ?? 'not_run';
|
|
16
|
+
const automationStatus = c.automation.status === 'none' ? 'manual' : c.automation.status;
|
|
17
|
+
return normalizeCase({
|
|
18
|
+
featureId: c.featureId,
|
|
19
|
+
functionality: scope.title,
|
|
20
|
+
title: c.title,
|
|
21
|
+
description: c.purpose,
|
|
22
|
+
objective: c.purpose,
|
|
23
|
+
creativityLevel: c.creativity,
|
|
24
|
+
platforms,
|
|
25
|
+
type: c.creativity === 'adversarial' ? 'negative' : 'functional',
|
|
26
|
+
priority: c.riskLevel === 'high' ? 'P1' : c.riskLevel === 'low' ? 'P3' : 'P2',
|
|
27
|
+
preconditions: c.preconditions,
|
|
28
|
+
fixtures: c.fixtures,
|
|
29
|
+
steps: c.steps.map((action, i) => ({ index: i + 1, action, expected: c.expected[i] })),
|
|
30
|
+
expectedResult: c.expected,
|
|
31
|
+
actualResult: {
|
|
32
|
+
status: actualStatus,
|
|
33
|
+
summary: c.actualResult.join('; ') || 'Generated from a focused feature run.',
|
|
34
|
+
evidence: c.evidence,
|
|
35
|
+
lastRunAt: actualStatus !== 'not_run' ? now : undefined,
|
|
36
|
+
},
|
|
37
|
+
automation: {
|
|
38
|
+
status: automationStatus,
|
|
39
|
+
pageObjects: c.automation.pageObjects,
|
|
40
|
+
testFiles: c.automation.flow ? [c.automation.flow] : [],
|
|
41
|
+
locatorReadiness: 'D',
|
|
42
|
+
replayStatus: 'not_replayed',
|
|
43
|
+
},
|
|
44
|
+
status: actualStatus === 'blocked' ? 'blocked' : 'active',
|
|
45
|
+
mapLinks,
|
|
46
|
+
evidence: c.evidence.map((uri) => ({ uri })),
|
|
47
|
+
tags: ['feature', c.featureId],
|
|
48
|
+
}, now);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=suiteBridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suiteBridge.js","sourceRoot":"","sources":["../../src/featureTesting/suiteBridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Query normalization + synonym expansion (SWIPIUM-REQ-03 "Query and Ranking Requirements").
|
|
2
|
+
// Deterministic, English-leaning v1 (Non-Goals: schema allows locale/synonym expansion later).
|
|
3
|
+
// A feature request like "weather analysis feature" expands to the broader vocabulary the code
|
|
4
|
+
// might actually use ("forecast", "temperature", "radar", …) so static/runtime matches are found.
|
|
5
|
+
const STOPWORDS = new Set([
|
|
6
|
+
'the', 'a', 'an', 'of', 'for', 'to', 'in', 'on', 'and', 'or', 'feature', 'features', 'flow', 'flows',
|
|
7
|
+
'screen', 'screens', 'page', 'pages', 'test', 'testing', 'tests', 'my', 'app', 'this', 'with', 'that',
|
|
8
|
+
'functionality', 'function', 'section', 'module', 'workflow',
|
|
9
|
+
]);
|
|
10
|
+
// Bidirectional-ish synonym groups: any term in a group expands to the whole group. Kept small and
|
|
11
|
+
// high-signal; the index's own token splitting handles the rest.
|
|
12
|
+
const SYNONYM_GROUPS = [
|
|
13
|
+
['weather', 'forecast', 'climate', 'temperature', 'radar', 'precipitation', 'wind', 'humidity', 'meteo'],
|
|
14
|
+
['analysis', 'analytics', 'analyze', 'insight', 'insights', 'report', 'stats', 'statistics'],
|
|
15
|
+
['alert', 'alerts', 'notification', 'notifications', 'warning', 'warnings'],
|
|
16
|
+
['auth', 'login', 'signin', 'logon', 'authentication', 'credentials', 'password', 'account'],
|
|
17
|
+
['signup', 'register', 'registration', 'onboarding', 'enroll'],
|
|
18
|
+
['search', 'find', 'filter', 'query', 'lookup', 'browse'],
|
|
19
|
+
['checkout', 'payment', 'purchase', 'pay', 'billing', 'cart', 'order', 'subscription', 'subscribe'],
|
|
20
|
+
['profile', 'account', 'settings', 'preferences', 'avatar'],
|
|
21
|
+
['map', 'location', 'geolocation', 'gps', 'nearby', 'route', 'navigation'],
|
|
22
|
+
['chat', 'message', 'messaging', 'inbox', 'conversation', 'thread'],
|
|
23
|
+
['media', 'photo', 'image', 'camera', 'video', 'gallery', 'upload'],
|
|
24
|
+
['feed', 'timeline', 'post', 'posts', 'article', 'content'],
|
|
25
|
+
['favorite', 'favorites', 'bookmark', 'saved', 'wishlist', 'like'],
|
|
26
|
+
['delete', 'remove', 'destroy', 'clear', 'discard'],
|
|
27
|
+
['share', 'send', 'invite', 'export'],
|
|
28
|
+
];
|
|
29
|
+
const SYNONYM_INDEX = (() => {
|
|
30
|
+
const idx = new Map();
|
|
31
|
+
for (const group of SYNONYM_GROUPS) {
|
|
32
|
+
const set = new Set(group);
|
|
33
|
+
for (const term of group)
|
|
34
|
+
idx.set(term, set);
|
|
35
|
+
}
|
|
36
|
+
return idx;
|
|
37
|
+
})();
|
|
38
|
+
function splitTerms(query) {
|
|
39
|
+
return query
|
|
40
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
41
|
+
.toLowerCase()
|
|
42
|
+
.split(/[^a-z0-9]+/)
|
|
43
|
+
.map((t) => t.trim())
|
|
44
|
+
.filter((t) => t.length > 1);
|
|
45
|
+
}
|
|
46
|
+
/** Normalize a feature request into core terms + a synonym-expanded search vocabulary. */
|
|
47
|
+
export function normalizeQuery(query) {
|
|
48
|
+
const core = [];
|
|
49
|
+
const seenCore = new Set();
|
|
50
|
+
for (const t of splitTerms(query)) {
|
|
51
|
+
if (STOPWORDS.has(t) || seenCore.has(t))
|
|
52
|
+
continue;
|
|
53
|
+
seenCore.add(t);
|
|
54
|
+
core.push(t);
|
|
55
|
+
}
|
|
56
|
+
// Fall back to the raw split if every term was a stopword (e.g. "settings" alone is fine, but
|
|
57
|
+
// "the app" should still yield something to search on).
|
|
58
|
+
const coreTerms = core.length ? core : splitTerms(query).filter((t) => !seenCore.has(t));
|
|
59
|
+
const expanded = new Set(coreTerms);
|
|
60
|
+
for (const t of coreTerms) {
|
|
61
|
+
const group = SYNONYM_INDEX.get(t);
|
|
62
|
+
if (group)
|
|
63
|
+
for (const s of group)
|
|
64
|
+
expanded.add(s);
|
|
65
|
+
}
|
|
66
|
+
return { raw: query, coreTerms, expandedTerms: [...expanded] };
|
|
67
|
+
}
|
|
68
|
+
/** Are two term sets about genuinely different features (no shared core term)? Drives disambiguation. */
|
|
69
|
+
export function termsAreUnrelated(a, b) {
|
|
70
|
+
const sb = new Set(b);
|
|
71
|
+
return !a.some((t) => sb.has(t));
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=synonyms.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"synonyms.js","sourceRoot":"","sources":["../../src/featureTesting/synonyms.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Feature-focused test case generation (SWIPIUM-REQ-03 "Test Case Generation Requirements"). PURE.
|
|
2
|
+
// Turns a FeatureScope + FeatureObjective into industry-style test cases (TestRail-aligned: each case
|
|
3
|
+
// connects preconditions, inputs, steps, and expected results to the documented objective). Cases are
|
|
4
|
+
// linked to featureId + map screens + evidence, carry a creativity level, and start with empty actual
|
|
5
|
+
// results (filled after a run by resultMerge).
|
|
6
|
+
const CREATIVITY_RANK = { conservative: 0, standard: 1, creative: 2, adversarial: 3 };
|
|
7
|
+
export function generateFeatureTestCases(scope, objective, opts = {}) {
|
|
8
|
+
const level = opts.creativity ?? 'standard';
|
|
9
|
+
const maxRank = CREATIVITY_RANK[level];
|
|
10
|
+
const platform = opts.platform ?? 'android+ios';
|
|
11
|
+
const cases = [];
|
|
12
|
+
const fixtures = scope.dataDependencies.filter((d) => d.kind === 'fixture').map((d) => d.name);
|
|
13
|
+
const staticScreenLinks = scope.staticScreens.map((s) => s.file ?? s.name).filter(Boolean);
|
|
14
|
+
const runtimeScreenLinks = scope.runtimeScreens.map((s) => s.id ?? s.name).filter(Boolean);
|
|
15
|
+
const baseRisk = scope.risks.reduce((acc, r) => (rank(r.level) > rank(acc) ? r.level : acc), 'low');
|
|
16
|
+
const mapLinks = { staticScreens: staticScreenLinks, runtimeScreens: runtimeScreenLinks };
|
|
17
|
+
const idFor = (n) => `TC-${scope.featureId.replace(/^feat-/, '').toUpperCase().slice(0, 12)}-${String(n).padStart(3, '0')}`;
|
|
18
|
+
const mk = (over) => ({
|
|
19
|
+
id: idFor(cases.length + 1),
|
|
20
|
+
featureId: scope.featureId,
|
|
21
|
+
purpose: over.purpose ?? `Verify the ${scope.title} feature behaves as expected.`,
|
|
22
|
+
riskLevel: over.riskLevel ?? baseRisk,
|
|
23
|
+
preconditions: over.preconditions ?? ['app installed and launched'],
|
|
24
|
+
fixtures: over.fixtures ?? fixtures,
|
|
25
|
+
actualResult: [],
|
|
26
|
+
status: 'not_run',
|
|
27
|
+
evidence: [],
|
|
28
|
+
automation: over.automation ?? { status: 'none', pageObjects: [] },
|
|
29
|
+
mapLinks,
|
|
30
|
+
requiresConsent: over.requiresConsent ?? false,
|
|
31
|
+
...over,
|
|
32
|
+
});
|
|
33
|
+
// ---- conservative: the core happy path only ----
|
|
34
|
+
cases.push(mk({
|
|
35
|
+
title: `${scope.title} — happy path`,
|
|
36
|
+
creativity: 'conservative',
|
|
37
|
+
purpose: objective.userGoal,
|
|
38
|
+
preconditions: ['app installed and launched', ...(objective.externalDependencies.length ? [`available: ${objective.externalDependencies.join(', ')}`] : [])],
|
|
39
|
+
steps: objective.primaryHappyPath,
|
|
40
|
+
expected: objective.expectedOutputs,
|
|
41
|
+
}));
|
|
42
|
+
// ---- standard: common validation / empty / error cases ----
|
|
43
|
+
if (maxRank >= CREATIVITY_RANK.standard) {
|
|
44
|
+
for (const neg of objective.negativeCases) {
|
|
45
|
+
cases.push(mk({
|
|
46
|
+
title: `${scope.title} — ${shorten(neg)}`,
|
|
47
|
+
creativity: 'standard',
|
|
48
|
+
purpose: `Validate a common negative/validation case for ${scope.title}.`,
|
|
49
|
+
steps: [`Reach the ${scope.title} surface`, neg.split('→')[0].trim()],
|
|
50
|
+
expected: [neg.includes('→') ? neg.split('→')[1].trim() : 'A clear validation/error state is shown without a crash'],
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
if (objective.inputFields.length) {
|
|
54
|
+
cases.push(mk({
|
|
55
|
+
title: `${scope.title} — required input validation`,
|
|
56
|
+
creativity: 'standard',
|
|
57
|
+
purpose: 'Submitting with required inputs missing is rejected.',
|
|
58
|
+
steps: [`Reach the ${scope.title} surface`, `Leave required field(s) blank: ${objective.inputFields.filter((f) => f.required).map((f) => f.name).join(', ')}`, 'Attempt to submit/continue'],
|
|
59
|
+
expected: ['A validation message is shown; the action is not performed'],
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// ---- creative: boundary, interruption, permission, offline, visual/state ----
|
|
64
|
+
if (maxRank >= CREATIVITY_RANK.creative) {
|
|
65
|
+
for (const edge of objective.edgeCases) {
|
|
66
|
+
cases.push(mk({
|
|
67
|
+
title: `${scope.title} — edge: ${shorten(edge)}`,
|
|
68
|
+
creativity: 'creative',
|
|
69
|
+
purpose: `Exercise an edge/boundary condition for ${scope.title}.`,
|
|
70
|
+
steps: [`Reach the ${scope.title} surface`, edge],
|
|
71
|
+
expected: ['The app handles the condition gracefully (no crash, coherent UI state)'],
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
cases.push(mk({
|
|
75
|
+
title: `${scope.title} — offline / interruption resilience`,
|
|
76
|
+
creativity: 'creative',
|
|
77
|
+
purpose: 'The feature degrades gracefully when connectivity drops or it is interrupted.',
|
|
78
|
+
steps: [`Reach the ${scope.title} surface`, 'Toggle airplane mode / background and foreground the app mid-flow'],
|
|
79
|
+
expected: ['No crash; the app shows an offline/retry state or resumes coherently'],
|
|
80
|
+
riskLevel: 'low',
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
// ---- adversarial: destructive/security/high-impact — consent-gated, disposable state only ----
|
|
84
|
+
if (maxRank >= CREATIVITY_RANK.adversarial && opts.allowAdversarial && objective.destructiveBoundaries.length) {
|
|
85
|
+
for (const boundary of objective.destructiveBoundaries) {
|
|
86
|
+
cases.push(mk({
|
|
87
|
+
title: `${scope.title} — destructive boundary`,
|
|
88
|
+
creativity: 'adversarial',
|
|
89
|
+
purpose: 'Probe a destructive/high-impact boundary safely.',
|
|
90
|
+
riskLevel: 'high',
|
|
91
|
+
requiresConsent: true,
|
|
92
|
+
preconditions: ['disposable test state', 'explicit candidate-bound consent'],
|
|
93
|
+
steps: [`Reach the ${scope.title} surface`, boundary],
|
|
94
|
+
expected: ['Either the action is safely contained on disposable state, or it is correctly gated behind confirmation'],
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return cases;
|
|
99
|
+
}
|
|
100
|
+
function rank(level) {
|
|
101
|
+
return level === 'high' ? 2 : level === 'medium' ? 1 : 0;
|
|
102
|
+
}
|
|
103
|
+
function shorten(s) {
|
|
104
|
+
const head = s.split('→')[0].trim();
|
|
105
|
+
return head.length > 48 ? head.slice(0, 45) + '…' : head;
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=testCaseFactory.js.map
|