@taodigital/agents 0.0.0-stage → 1.2.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 +99 -2
- package/dist/api-test-agent.d.ts +126 -0
- package/dist/api-test-agent.js +267 -0
- package/dist/artifact-intel-agent.d.ts +11 -0
- package/dist/artifact-intel-agent.js +152 -0
- package/dist/coverage-gap-agent.d.ts +25 -0
- package/dist/coverage-gap-agent.js +269 -0
- package/dist/coverage-map-agent.d.ts +15 -0
- package/dist/coverage-map-agent.js +255 -0
- package/dist/crawl-diff-agent.d.ts +27 -0
- package/dist/crawl-diff-agent.js +412 -0
- package/dist/data-agent.d.ts +101 -0
- package/dist/data-agent.js +2284 -0
- package/dist/document-agent.d.ts +10 -0
- package/dist/document-agent.js +32 -0
- package/dist/enhancement-agent.d.ts +48 -0
- package/dist/enhancement-agent.js +342 -0
- package/dist/field-detector-agent.d.ts +35 -0
- package/dist/field-detector-agent.js +220 -0
- package/dist/flow-agent.d.ts +130 -0
- package/dist/flow-agent.js +169 -0
- package/dist/github-agent.d.ts +18 -0
- package/dist/github-agent.js +39 -0
- package/dist/github-write-agent.d.ts +67 -0
- package/dist/github-write-agent.js +249 -0
- package/dist/grouping-agent.d.ts +18 -0
- package/dist/grouping-agent.js +338 -0
- package/dist/healing-agent.d.ts +73 -0
- package/dist/healing-agent.js +137 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +67 -0
- package/dist/integration-test-agent.d.ts +118 -0
- package/dist/integration-test-agent.js +165 -0
- package/dist/label-assignment-agent.d.ts +15 -0
- package/dist/label-assignment-agent.js +97 -0
- package/dist/llm-config.d.ts +38 -0
- package/dist/llm-config.js +287 -0
- package/dist/llm-mock.d.ts +77 -0
- package/dist/llm-mock.js +192 -0
- package/dist/llm.d.ts +58 -0
- package/dist/llm.js +438 -0
- package/dist/nlp-agent.d.ts +208 -0
- package/dist/nlp-agent.js +340 -0
- package/dist/orchestrator.d.ts +285 -0
- package/dist/orchestrator.js +141 -0
- package/dist/plan-agent.d.ts +4 -0
- package/dist/plan-agent.js +200 -0
- package/dist/repo-scan-agent.d.ts +189 -0
- package/dist/repo-scan-agent.js +2032 -0
- package/dist/requirements-agent.d.ts +59 -0
- package/dist/requirements-agent.js +641 -0
- package/dist/scenario-agent.d.ts +20 -0
- package/dist/scenario-agent.js +123 -0
- package/dist/script-agent.d.ts +447 -0
- package/dist/script-agent.js +11813 -0
- package/dist/security-test-agent.d.ts +111 -0
- package/dist/security-test-agent.js +141 -0
- package/dist/skill-file-agent.d.ts +264 -0
- package/dist/skill-file-agent.js +3470 -0
- package/dist/step-coverage.d.ts +46 -0
- package/dist/step-coverage.js +288 -0
- package/dist/step-utils.d.ts +2 -0
- package/dist/step-utils.js +62 -0
- package/dist/test-case-agent.d.ts +206 -0
- package/dist/test-case-agent.js +2141 -0
- package/dist/test-import-agent.d.ts +148 -0
- package/dist/test-import-agent.js +3235 -0
- package/dist/test-script-agent.d.ts +16 -0
- package/dist/test-script-agent.js +48 -0
- package/dist/token-pricing.d.ts +14 -0
- package/dist/token-pricing.js +62 -0
- package/dist/types.d.ts +232 -0
- package/dist/types.js +7 -0
- package/dist/visual-agent.d.ts +114 -0
- package/dist/visual-agent.js +261 -0
- package/package.json +26 -4
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZeTa-AI Self-Healing Agent
|
|
3
|
+
*
|
|
4
|
+
* When a test fails because an element can't be found, this agent:
|
|
5
|
+
* 1. Builds a multi-signal fingerprint of every element during crawl.
|
|
6
|
+
* 2. On failure, searches the fresh page scan using all fingerprint signals.
|
|
7
|
+
* 3. Finds the best match, scores confidence, updates the test.
|
|
8
|
+
* 4. Routes to human review if confidence is too low.
|
|
9
|
+
*
|
|
10
|
+
* This makes ZeTa-AI tests far more durable than competitors — tests
|
|
11
|
+
* don't break when the app changes slightly.
|
|
12
|
+
*/
|
|
13
|
+
export interface ElementSelector {
|
|
14
|
+
type: 'text' | 'aria-label' | 'role' | 'css' | 'position' | 'placeholder';
|
|
15
|
+
value: string;
|
|
16
|
+
confidence: number;
|
|
17
|
+
}
|
|
18
|
+
export interface HealingResult {
|
|
19
|
+
healed: boolean;
|
|
20
|
+
confidence: number;
|
|
21
|
+
matchedSelector?: ElementSelector;
|
|
22
|
+
reasoning: string;
|
|
23
|
+
needsHumanReview: boolean;
|
|
24
|
+
updatedTest?: {
|
|
25
|
+
elementMeaning: string;
|
|
26
|
+
newSelector: ElementSelector;
|
|
27
|
+
};
|
|
28
|
+
healCandidate?: {
|
|
29
|
+
elementMeaning: string;
|
|
30
|
+
newSelector: ElementSelector;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Build a multi-signal fingerprint for an element during crawl.
|
|
35
|
+
* Called after readScreen() to enrich each element with stable selectors.
|
|
36
|
+
*/
|
|
37
|
+
export declare function buildFingerprint(rawElement: {
|
|
38
|
+
label?: string;
|
|
39
|
+
role: string;
|
|
40
|
+
ariaLabel?: string;
|
|
41
|
+
placeholder?: string;
|
|
42
|
+
cssSelector?: string;
|
|
43
|
+
textContent?: string;
|
|
44
|
+
position?: {
|
|
45
|
+
x: number;
|
|
46
|
+
y: number;
|
|
47
|
+
width: number;
|
|
48
|
+
height: number;
|
|
49
|
+
};
|
|
50
|
+
}): ElementSelector[];
|
|
51
|
+
/**
|
|
52
|
+
* Attempt to heal a failed element lookup using AI.
|
|
53
|
+
* Called when a test step fails because an element wasn't found.
|
|
54
|
+
*/
|
|
55
|
+
export declare function healElement(opts: {
|
|
56
|
+
tenantId: string;
|
|
57
|
+
projectId: string;
|
|
58
|
+
/** Omit when the caller has no crawl-time Element record for this step (e.g. an
|
|
59
|
+
* execution plan generated straight from a natural-language test case) — healing
|
|
60
|
+
* still works from currentPageElements alone, just without fingerprint history. */
|
|
61
|
+
elementId?: string;
|
|
62
|
+
failedElementMeaning: string;
|
|
63
|
+
currentPageElements: Array<{
|
|
64
|
+
meaning: string;
|
|
65
|
+
role: string;
|
|
66
|
+
ariaLabel?: string;
|
|
67
|
+
textContent?: string;
|
|
68
|
+
}>;
|
|
69
|
+
/** Override the module-level HEALING_REQUIRE_APPROVAL env var for this call.
|
|
70
|
+
* When true, healing is NOT auto-applied — the candidate is returned in
|
|
71
|
+
* healCandidate for engineer review. Defaults to HEALING_REQUIRE_APPROVAL. */
|
|
72
|
+
requireApproval?: boolean;
|
|
73
|
+
}): Promise<HealingResult>;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ZeTa-AI Self-Healing Agent
|
|
4
|
+
*
|
|
5
|
+
* When a test fails because an element can't be found, this agent:
|
|
6
|
+
* 1. Builds a multi-signal fingerprint of every element during crawl.
|
|
7
|
+
* 2. On failure, searches the fresh page scan using all fingerprint signals.
|
|
8
|
+
* 3. Finds the best match, scores confidence, updates the test.
|
|
9
|
+
* 4. Routes to human review if confidence is too low.
|
|
10
|
+
*
|
|
11
|
+
* This makes ZeTa-AI tests far more durable than competitors — tests
|
|
12
|
+
* don't break when the app changes slightly.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.buildFingerprint = buildFingerprint;
|
|
16
|
+
exports.healElement = healElement;
|
|
17
|
+
const app_brain_1 = require("@taodigital/app-brain");
|
|
18
|
+
const core_1 = require("@taodigital/core");
|
|
19
|
+
const llm_js_1 = require("./llm.js");
|
|
20
|
+
const llm_config_js_1 = require("./llm-config.js");
|
|
21
|
+
const HEALING_CONFIDENCE_THRESHOLD = parseInt(process.env.HEALING_CONFIDENCE_THRESHOLD ?? '') || 70;
|
|
22
|
+
const HEALING_REQUIRE_APPROVAL = process.env.HEALING_REQUIRE_APPROVAL === 'true';
|
|
23
|
+
/**
|
|
24
|
+
* Build a multi-signal fingerprint for an element during crawl.
|
|
25
|
+
* Called after readScreen() to enrich each element with stable selectors.
|
|
26
|
+
*/
|
|
27
|
+
function buildFingerprint(rawElement) {
|
|
28
|
+
const selectors = [];
|
|
29
|
+
if (rawElement.ariaLabel) {
|
|
30
|
+
selectors.push({ type: 'aria-label', value: rawElement.ariaLabel, confidence: 0.95 });
|
|
31
|
+
}
|
|
32
|
+
if (rawElement.label && rawElement.label !== '(unlabeled)') {
|
|
33
|
+
selectors.push({ type: 'text', value: rawElement.label, confidence: 0.85 });
|
|
34
|
+
}
|
|
35
|
+
if (rawElement.placeholder) {
|
|
36
|
+
selectors.push({ type: 'placeholder', value: rawElement.placeholder, confidence: 0.80 });
|
|
37
|
+
}
|
|
38
|
+
if (rawElement.textContent) {
|
|
39
|
+
selectors.push({ type: 'text', value: rawElement.textContent.slice(0, 100), confidence: 0.75 });
|
|
40
|
+
}
|
|
41
|
+
if (rawElement.role) {
|
|
42
|
+
selectors.push({ type: 'role', value: rawElement.role, confidence: 0.60 });
|
|
43
|
+
}
|
|
44
|
+
if (rawElement.cssSelector) {
|
|
45
|
+
selectors.push({ type: 'css', value: rawElement.cssSelector, confidence: 0.40 });
|
|
46
|
+
}
|
|
47
|
+
return selectors.sort((a, b) => b.confidence - a.confidence);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Attempt to heal a failed element lookup using AI.
|
|
51
|
+
* Called when a test step fails because an element wasn't found.
|
|
52
|
+
*/
|
|
53
|
+
async function healElement(opts) {
|
|
54
|
+
const fingerprint = opts.elementId ? await app_brain_1.AppBrain.getElementFingerprint(opts.tenantId, opts.elementId) : null;
|
|
55
|
+
// Pre-rank elements by semantic relevance — keeps LLM prompt focused when page has many elements
|
|
56
|
+
let candidateElements = opts.currentPageElements;
|
|
57
|
+
const MAX_ELEMENTS_IN_PROMPT = 15;
|
|
58
|
+
if (candidateElements.length > MAX_ELEMENTS_IN_PROMPT) {
|
|
59
|
+
try {
|
|
60
|
+
const texts = candidateElements.map((e) => `${e.meaning} ${e.role} ${e.ariaLabel ?? ''}`.trim());
|
|
61
|
+
const ranked = await (0, app_brain_1.rerankStrings)(opts.failedElementMeaning, texts, MAX_ELEMENTS_IN_PROMPT, 0);
|
|
62
|
+
candidateElements = ranked.map(({ index }) => opts.currentPageElements[index]);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
candidateElements = candidateElements.slice(0, MAX_ELEMENTS_IN_PROMPT);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const SYSTEM_DEFAULT = [
|
|
69
|
+
'You are ZeTa-AI\'s Self-Healing Agent.',
|
|
70
|
+
'A test failed because a UI element could not be found.',
|
|
71
|
+
'Find the element on the current page that BEST MATCHES the failed element.',
|
|
72
|
+
'Return JSON only: { found, matchedMeaning, confidence, reasoning, needsHuman }',
|
|
73
|
+
].join(' ');
|
|
74
|
+
const system = await (0, core_1.getAgentPrompt)('healing-element', undefined, opts.tenantId) ?? SYSTEM_DEFAULT;
|
|
75
|
+
const user = [
|
|
76
|
+
`FAILED ELEMENT: "${opts.failedElementMeaning}"`,
|
|
77
|
+
fingerprint
|
|
78
|
+
? `KNOWN FINGERPRINT: ${JSON.stringify(fingerprint.selectors, null, 2)}`
|
|
79
|
+
: 'No fingerprint stored yet.',
|
|
80
|
+
'',
|
|
81
|
+
'CURRENT PAGE ELEMENTS:',
|
|
82
|
+
candidateElements.map((e) => `- "${e.meaning}" [${e.role}]${e.ariaLabel ? ` aria="${e.ariaLabel}"` : ''}`).join('\n'),
|
|
83
|
+
'',
|
|
84
|
+
'Find the best match. If confidence < 70, set needsHuman: true.',
|
|
85
|
+
].join('\n');
|
|
86
|
+
// ZETA-166: enforce token budget and use tenant-configured LLM (testRunner is the closest task type)
|
|
87
|
+
const llmCfg = await (0, llm_config_js_1.getTaskAgentLLMConfig)(opts.tenantId, 'testRunner');
|
|
88
|
+
let result;
|
|
89
|
+
try {
|
|
90
|
+
const seed = (0, llm_js_1.deterministicSeed)(`${opts.tenantId}:${opts.projectId}:heal:${opts.failedElementMeaning.slice(0, 200)}`);
|
|
91
|
+
const { text: raw } = await (0, llm_js_1.askTracked)(system, user, llmCfg, undefined, undefined, { temperature: 0, seed });
|
|
92
|
+
const json = raw.slice(raw.indexOf('{'), raw.lastIndexOf('}') + 1);
|
|
93
|
+
result = JSON.parse(json);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return {
|
|
97
|
+
healed: false,
|
|
98
|
+
confidence: 0,
|
|
99
|
+
reasoning: 'Healing agent failed to parse response.',
|
|
100
|
+
needsHumanReview: true,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const approvalRequired = opts.requireApproval ?? HEALING_REQUIRE_APPROVAL;
|
|
104
|
+
if (result.found && result.confidence >= HEALING_CONFIDENCE_THRESHOLD) {
|
|
105
|
+
// Update the fingerprint's heal count and flakiness stats only when auto-healing.
|
|
106
|
+
if (fingerprint && opts.elementId && !approvalRequired) {
|
|
107
|
+
await app_brain_1.AppBrain.recordElementHeal(opts.tenantId, opts.elementId);
|
|
108
|
+
// Refresh LOCATOR_STRATEGY with updated self-healing data (GAP-SF-19)
|
|
109
|
+
const { refreshLocatorStrategyWithHealingData } = await import('./skill-file-agent.js');
|
|
110
|
+
refreshLocatorStrategyWithHealingData(opts.tenantId, opts.projectId).catch(console.error);
|
|
111
|
+
// TODO: track healing as a flakiness event once AppBrain.recordFlakinessOutcome
|
|
112
|
+
// accepts a testCaseId derived from the healed element — requires AppBrain schema
|
|
113
|
+
// addition to link elementId → testCaseId before this call is useful.
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
healed: !approvalRequired,
|
|
117
|
+
confidence: result.confidence,
|
|
118
|
+
reasoning: result.reasoning,
|
|
119
|
+
needsHumanReview: approvalRequired,
|
|
120
|
+
updatedTest: !approvalRequired ? {
|
|
121
|
+
elementMeaning: result.matchedMeaning,
|
|
122
|
+
newSelector: { type: 'text', value: result.matchedMeaning, confidence: result.confidence / 100 },
|
|
123
|
+
} : undefined,
|
|
124
|
+
// When approval required, surface the candidate for the engineer to review
|
|
125
|
+
healCandidate: approvalRequired ? {
|
|
126
|
+
elementMeaning: result.matchedMeaning,
|
|
127
|
+
newSelector: { type: 'text', value: result.matchedMeaning, confidence: result.confidence / 100 },
|
|
128
|
+
} : undefined,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
healed: false,
|
|
133
|
+
confidence: result.confidence,
|
|
134
|
+
reasoning: result.reasoning,
|
|
135
|
+
needsHumanReview: result.needsHuman || result.confidence < HEALING_CONFIDENCE_THRESHOLD,
|
|
136
|
+
};
|
|
137
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type { GenerateTestCasesOpts, GenerateTestCasesResult, RegenerateTestCaseTitleOpts, TestGenerationAgent, GenerateTestDataOpts, GenerateTestDataBatchCase, GenerateTestDataBatchOpts, GenerateTestDataResult, TestDataAgent, GenerateTestScriptOpts, GenerateTestScriptResult, GenerateSuiteScriptOpts, GenerateSuiteScriptResult, ScriptAgent, GenerateScenariosOpts, ScenarioAgent, ConvertNlpToTestOpts, ConvertNlpToTestResult, NlpAgent, SkillFileAgent, } from './types.js';
|
|
2
|
+
export * from './test-case-agent.js';
|
|
3
|
+
export * from './test-script-agent.js';
|
|
4
|
+
export * from './data-agent.js';
|
|
5
|
+
export * from './field-detector-agent.js';
|
|
6
|
+
export * from './script-agent.js';
|
|
7
|
+
export * from './nlp-agent.js';
|
|
8
|
+
export * from './healing-agent.js';
|
|
9
|
+
export * from './visual-agent.js';
|
|
10
|
+
export * from './skill-file-agent.js';
|
|
11
|
+
export * from './plan-agent.js';
|
|
12
|
+
export * from './llm.js';
|
|
13
|
+
export * from './llm-mock.js';
|
|
14
|
+
export * from './document-agent.js';
|
|
15
|
+
export * from './github-agent.js';
|
|
16
|
+
export * from './github-write-agent.js';
|
|
17
|
+
export * from './orchestrator.js';
|
|
18
|
+
export * from './flow-agent.js';
|
|
19
|
+
export * from './api-test-agent.js';
|
|
20
|
+
export * from './security-test-agent.js';
|
|
21
|
+
export * from './integration-test-agent.js';
|
|
22
|
+
export * from './llm-config.js';
|
|
23
|
+
export * from './grouping-agent.js';
|
|
24
|
+
export * from './artifact-intel-agent.js';
|
|
25
|
+
export * from './requirements-agent.js';
|
|
26
|
+
export * from './scenario-agent.js';
|
|
27
|
+
export * from './label-assignment-agent.js';
|
|
28
|
+
export * from './repo-scan-agent.js';
|
|
29
|
+
export * from './coverage-map-agent.js';
|
|
30
|
+
export * from './step-coverage.js';
|
|
31
|
+
export * from './crawl-diff-agent.js';
|
|
32
|
+
export * from './enhancement-agent.js';
|
|
33
|
+
export * from './coverage-gap-agent.js';
|
|
34
|
+
export { estimateCost, loadPricingOverrides } from './token-pricing.js';
|
|
35
|
+
export { parseTestCases, parseTestCasesWithCache, scenarioContextKey, sanitizeParsedCase, inferColumnMapping, saveColumnMapping, loadColumnMapping, headerSignature, extractTabularHeaders, extractScenarios, buildSeleniumHints, annotateWithSeleniumHints, parseJsonWithRepair, isFunctionizeFormat, } from './test-import-agent.js';
|
|
36
|
+
export type { ColumnMapping, InferMappingResult, ScenarioSummary } from './test-import-agent.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.isFunctionizeFormat = exports.parseJsonWithRepair = exports.annotateWithSeleniumHints = exports.buildSeleniumHints = exports.extractScenarios = exports.extractTabularHeaders = exports.headerSignature = exports.loadColumnMapping = exports.saveColumnMapping = exports.inferColumnMapping = exports.sanitizeParsedCase = exports.scenarioContextKey = exports.parseTestCasesWithCache = exports.parseTestCases = exports.loadPricingOverrides = exports.estimateCost = void 0;
|
|
18
|
+
__exportStar(require("./test-case-agent.js"), exports);
|
|
19
|
+
__exportStar(require("./test-script-agent.js"), exports);
|
|
20
|
+
__exportStar(require("./data-agent.js"), exports);
|
|
21
|
+
__exportStar(require("./field-detector-agent.js"), exports);
|
|
22
|
+
__exportStar(require("./script-agent.js"), exports);
|
|
23
|
+
__exportStar(require("./nlp-agent.js"), exports);
|
|
24
|
+
__exportStar(require("./healing-agent.js"), exports);
|
|
25
|
+
__exportStar(require("./visual-agent.js"), exports);
|
|
26
|
+
__exportStar(require("./skill-file-agent.js"), exports);
|
|
27
|
+
__exportStar(require("./plan-agent.js"), exports);
|
|
28
|
+
__exportStar(require("./llm.js"), exports);
|
|
29
|
+
__exportStar(require("./llm-mock.js"), exports);
|
|
30
|
+
__exportStar(require("./document-agent.js"), exports);
|
|
31
|
+
__exportStar(require("./github-agent.js"), exports);
|
|
32
|
+
__exportStar(require("./github-write-agent.js"), exports);
|
|
33
|
+
__exportStar(require("./orchestrator.js"), exports);
|
|
34
|
+
__exportStar(require("./flow-agent.js"), exports);
|
|
35
|
+
__exportStar(require("./api-test-agent.js"), exports);
|
|
36
|
+
__exportStar(require("./security-test-agent.js"), exports);
|
|
37
|
+
__exportStar(require("./integration-test-agent.js"), exports);
|
|
38
|
+
__exportStar(require("./llm-config.js"), exports);
|
|
39
|
+
__exportStar(require("./grouping-agent.js"), exports);
|
|
40
|
+
__exportStar(require("./artifact-intel-agent.js"), exports);
|
|
41
|
+
__exportStar(require("./requirements-agent.js"), exports);
|
|
42
|
+
__exportStar(require("./scenario-agent.js"), exports);
|
|
43
|
+
__exportStar(require("./label-assignment-agent.js"), exports);
|
|
44
|
+
__exportStar(require("./repo-scan-agent.js"), exports);
|
|
45
|
+
__exportStar(require("./coverage-map-agent.js"), exports);
|
|
46
|
+
__exportStar(require("./step-coverage.js"), exports);
|
|
47
|
+
__exportStar(require("./crawl-diff-agent.js"), exports);
|
|
48
|
+
__exportStar(require("./enhancement-agent.js"), exports);
|
|
49
|
+
__exportStar(require("./coverage-gap-agent.js"), exports);
|
|
50
|
+
var token_pricing_js_1 = require("./token-pricing.js");
|
|
51
|
+
Object.defineProperty(exports, "estimateCost", { enumerable: true, get: function () { return token_pricing_js_1.estimateCost; } });
|
|
52
|
+
Object.defineProperty(exports, "loadPricingOverrides", { enumerable: true, get: function () { return token_pricing_js_1.loadPricingOverrides; } });
|
|
53
|
+
var test_import_agent_js_1 = require("./test-import-agent.js");
|
|
54
|
+
Object.defineProperty(exports, "parseTestCases", { enumerable: true, get: function () { return test_import_agent_js_1.parseTestCases; } });
|
|
55
|
+
Object.defineProperty(exports, "parseTestCasesWithCache", { enumerable: true, get: function () { return test_import_agent_js_1.parseTestCasesWithCache; } });
|
|
56
|
+
Object.defineProperty(exports, "scenarioContextKey", { enumerable: true, get: function () { return test_import_agent_js_1.scenarioContextKey; } });
|
|
57
|
+
Object.defineProperty(exports, "sanitizeParsedCase", { enumerable: true, get: function () { return test_import_agent_js_1.sanitizeParsedCase; } });
|
|
58
|
+
Object.defineProperty(exports, "inferColumnMapping", { enumerable: true, get: function () { return test_import_agent_js_1.inferColumnMapping; } });
|
|
59
|
+
Object.defineProperty(exports, "saveColumnMapping", { enumerable: true, get: function () { return test_import_agent_js_1.saveColumnMapping; } });
|
|
60
|
+
Object.defineProperty(exports, "loadColumnMapping", { enumerable: true, get: function () { return test_import_agent_js_1.loadColumnMapping; } });
|
|
61
|
+
Object.defineProperty(exports, "headerSignature", { enumerable: true, get: function () { return test_import_agent_js_1.headerSignature; } });
|
|
62
|
+
Object.defineProperty(exports, "extractTabularHeaders", { enumerable: true, get: function () { return test_import_agent_js_1.extractTabularHeaders; } });
|
|
63
|
+
Object.defineProperty(exports, "extractScenarios", { enumerable: true, get: function () { return test_import_agent_js_1.extractScenarios; } });
|
|
64
|
+
Object.defineProperty(exports, "buildSeleniumHints", { enumerable: true, get: function () { return test_import_agent_js_1.buildSeleniumHints; } });
|
|
65
|
+
Object.defineProperty(exports, "annotateWithSeleniumHints", { enumerable: true, get: function () { return test_import_agent_js_1.annotateWithSeleniumHints; } });
|
|
66
|
+
Object.defineProperty(exports, "parseJsonWithRepair", { enumerable: true, get: function () { return test_import_agent_js_1.parseJsonWithRepair; } });
|
|
67
|
+
Object.defineProperty(exports, "isFunctionizeFormat", { enumerable: true, get: function () { return test_import_agent_js_1.isFunctionizeFormat; } });
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration Test Agent — UI/API/DB Full Stack Verification
|
|
3
|
+
*
|
|
4
|
+
* Generates tests that verify the full stack:
|
|
5
|
+
* UI action → API call → backend state change → assertion
|
|
6
|
+
*
|
|
7
|
+
* Uses: API_CONTRACT, DATA_MODEL, INTERACTION_PATTERNS, TECH_STACK
|
|
8
|
+
* Test kinds: INTEGRATION, POSITIVE, NEGATIVE
|
|
9
|
+
*
|
|
10
|
+
* DB setup/teardown uses "TODO_DB_CONNECTION" placeholder that
|
|
11
|
+
* developers replace with their actual connection string.
|
|
12
|
+
*/
|
|
13
|
+
export interface IntegrationTestCase {
|
|
14
|
+
title: string;
|
|
15
|
+
kind: 'INTEGRATION' | 'POSITIVE' | 'NEGATIVE';
|
|
16
|
+
priority: 'CRITICAL' | 'HIGH' | 'MEDIUM';
|
|
17
|
+
integrationLayer: 'UI_API' | 'API_DB' | 'UI_API_DB' | 'SERVICE_SERVICE';
|
|
18
|
+
preconditions: string;
|
|
19
|
+
dbSetup: string;
|
|
20
|
+
steps: string[];
|
|
21
|
+
apiAssertions: string[];
|
|
22
|
+
dbAssertions: string[];
|
|
23
|
+
expectedResult: string;
|
|
24
|
+
dbTeardown: string;
|
|
25
|
+
groundedOn: string[];
|
|
26
|
+
tags: string[];
|
|
27
|
+
}
|
|
28
|
+
export declare function generateIntegrationTests(opts: {
|
|
29
|
+
tenantId: string;
|
|
30
|
+
projectId: string;
|
|
31
|
+
apiEndpoints?: Array<{
|
|
32
|
+
method: string;
|
|
33
|
+
urlPattern: string;
|
|
34
|
+
}>;
|
|
35
|
+
}): Promise<{
|
|
36
|
+
status: string;
|
|
37
|
+
cases: never[];
|
|
38
|
+
saved: null;
|
|
39
|
+
suiteName?: undefined;
|
|
40
|
+
} | {
|
|
41
|
+
status: string;
|
|
42
|
+
cases: IntegrationTestCase[];
|
|
43
|
+
saved: {
|
|
44
|
+
suite: {
|
|
45
|
+
tenantId: string;
|
|
46
|
+
id: string;
|
|
47
|
+
projectId: string;
|
|
48
|
+
name: string;
|
|
49
|
+
createdAt: Date;
|
|
50
|
+
genModel: string | null;
|
|
51
|
+
category: import(".prisma/client").$Enums.SuiteCategory | null;
|
|
52
|
+
parentId: string | null;
|
|
53
|
+
ownerUserId: string | null;
|
|
54
|
+
genTotalTokens: number | null;
|
|
55
|
+
genTotalCostUsd: number | null;
|
|
56
|
+
genAt: Date | null;
|
|
57
|
+
genScreenCount: number | null;
|
|
58
|
+
coverageReport: import("@prisma/client/runtime/library").JsonValue | null;
|
|
59
|
+
};
|
|
60
|
+
testCases: {
|
|
61
|
+
tenantId: string;
|
|
62
|
+
screenId: string | null;
|
|
63
|
+
id: string;
|
|
64
|
+
projectId: string;
|
|
65
|
+
source: string;
|
|
66
|
+
status: string;
|
|
67
|
+
createdAt: Date;
|
|
68
|
+
updatedAt: Date;
|
|
69
|
+
steps: import("@prisma/client/runtime/library").JsonValue;
|
|
70
|
+
kind: string;
|
|
71
|
+
tags: import("@prisma/client/runtime/library").JsonValue;
|
|
72
|
+
approvedBy: string | null;
|
|
73
|
+
suiteId: string;
|
|
74
|
+
ref: string | null;
|
|
75
|
+
title: string;
|
|
76
|
+
priority: string;
|
|
77
|
+
preconditions: string | null;
|
|
78
|
+
expectedResult: string | null;
|
|
79
|
+
gherkin: string | null;
|
|
80
|
+
approvedAt: Date | null;
|
|
81
|
+
groundedOn: import("@prisma/client/runtime/library").JsonValue;
|
|
82
|
+
caseRefs: import("@prisma/client/runtime/library").JsonValue;
|
|
83
|
+
viewports: import("@prisma/client/runtime/library").JsonValue;
|
|
84
|
+
viewportConstraint: string;
|
|
85
|
+
techniqueOfRecord: string | null;
|
|
86
|
+
traceTo: string | null;
|
|
87
|
+
traceToExtra: import("@prisma/client/runtime/library").JsonValue;
|
|
88
|
+
testDataInline: import("@prisma/client/runtime/library").JsonValue | null;
|
|
89
|
+
dataNote: string | null;
|
|
90
|
+
scriptNote: string | null;
|
|
91
|
+
structuredSteps: import("@prisma/client/runtime/library").JsonValue | null;
|
|
92
|
+
artifactId: string | null;
|
|
93
|
+
scenarioId: string | null;
|
|
94
|
+
suspect: boolean;
|
|
95
|
+
suspectReason: string | null;
|
|
96
|
+
flakyScore: number;
|
|
97
|
+
consecutiveFlakes: number;
|
|
98
|
+
lastFlakyAt: Date | null;
|
|
99
|
+
quarantined: boolean;
|
|
100
|
+
quarantinedAt: Date | null;
|
|
101
|
+
quarantineReason: string | null;
|
|
102
|
+
genPromptTokens: number | null;
|
|
103
|
+
genCompletionTokens: number | null;
|
|
104
|
+
genCostUsd: number | null;
|
|
105
|
+
genModel: string | null;
|
|
106
|
+
dataSetId: string | null;
|
|
107
|
+
repoTargetPath: string | null;
|
|
108
|
+
stepsKey: string | null;
|
|
109
|
+
gherkinKey: string | null;
|
|
110
|
+
contentHash: string | null;
|
|
111
|
+
groundingHash: string | null;
|
|
112
|
+
locatorsJson: import("@prisma/client/runtime/library").JsonValue | null;
|
|
113
|
+
locatorsExtractedAt: Date | null;
|
|
114
|
+
captureStepScreenshots: import("@prisma/client/runtime/library").JsonValue | null;
|
|
115
|
+
}[];
|
|
116
|
+
};
|
|
117
|
+
suiteName: string;
|
|
118
|
+
}>;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Integration Test Agent — UI/API/DB Full Stack Verification
|
|
4
|
+
*
|
|
5
|
+
* Generates tests that verify the full stack:
|
|
6
|
+
* UI action → API call → backend state change → assertion
|
|
7
|
+
*
|
|
8
|
+
* Uses: API_CONTRACT, DATA_MODEL, INTERACTION_PATTERNS, TECH_STACK
|
|
9
|
+
* Test kinds: INTEGRATION, POSITIVE, NEGATIVE
|
|
10
|
+
*
|
|
11
|
+
* DB setup/teardown uses "TODO_DB_CONNECTION" placeholder that
|
|
12
|
+
* developers replace with their actual connection string.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.generateIntegrationTests = generateIntegrationTests;
|
|
16
|
+
const app_brain_1 = require("@taodigital/app-brain");
|
|
17
|
+
const core_1 = require("@taodigital/core");
|
|
18
|
+
const llm_js_1 = require("./llm.js");
|
|
19
|
+
const llm_config_js_1 = require("./llm-config.js");
|
|
20
|
+
async function generateIntegrationTests(opts) {
|
|
21
|
+
const llmCfg = await (0, llm_config_js_1.getTaskAgentLLMConfig)(opts.tenantId, 'testGeneration');
|
|
22
|
+
const project = await app_brain_1.AppBrain.getProjectMap(opts.tenantId, opts.projectId);
|
|
23
|
+
if (!project)
|
|
24
|
+
return { status: 'NO_PROJECT', cases: [], saved: null };
|
|
25
|
+
const screens = project.screens ?? [];
|
|
26
|
+
// Focus on screens with forms / interactive elements
|
|
27
|
+
const formScreens = screens
|
|
28
|
+
.filter((s) => (s.elements ?? []).some((e) => ['submit', 'button', 'form', 'input', 'checkbox', 'textarea'].some((r) => (e.role ?? '').toLowerCase().includes(r))))
|
|
29
|
+
.slice(0, 15)
|
|
30
|
+
.map((s) => {
|
|
31
|
+
const els = (s.elements ?? [])
|
|
32
|
+
.filter((e) => e.isInteractive)
|
|
33
|
+
.slice(0, 6)
|
|
34
|
+
.map((e) => ` - ${e.role}: "${e.meaning}"${e.expectedData ? ` (${e.expectedData})` : ''}`)
|
|
35
|
+
.join('\n');
|
|
36
|
+
return `Screen: ${s.name}${s.url ? ` (${s.url})` : ''}\n Interactive elements:\n${els || ' (none)'}`;
|
|
37
|
+
})
|
|
38
|
+
.join('\n\n');
|
|
39
|
+
const skillFiles = await app_brain_1.AppBrain.listSkillFiles(opts.tenantId, opts.projectId, ['API_CONTRACT', 'DATA_MODEL', 'INTERACTION_PATTERNS', 'TECH_STACK']);
|
|
40
|
+
const skillCtx = skillFiles.map((sf) => `### ${sf.kind}\n${sf.content}`).join('\n\n');
|
|
41
|
+
const endpointSummary = (opts.apiEndpoints ?? []).slice(0, 30)
|
|
42
|
+
.map((e) => ` ${e.method} ${e.urlPattern}`)
|
|
43
|
+
.join('\n');
|
|
44
|
+
const SYSTEM_DEFAULT = `You are an expert integration QA engineer generating full-stack test cases.
|
|
45
|
+
Integration tests verify UI actions correctly modify backend state — not just that the UI shows success.
|
|
46
|
+
|
|
47
|
+
TEST STRUCTURE per case:
|
|
48
|
+
1. dbSetup: SQL/ORM to seed required data (use TODO_DB_CONNECTION as the connection variable)
|
|
49
|
+
2. steps: Playwright UI actions on actual screen elements
|
|
50
|
+
3. apiAssertions: Exact API response checks (status + fields)
|
|
51
|
+
4. dbAssertions: Exact DB state checks after the operation
|
|
52
|
+
5. dbTeardown: Cleanup SQL/ORM
|
|
53
|
+
|
|
54
|
+
INTEGRATION LAYERS:
|
|
55
|
+
- UI_API: UI action triggers correct API call with correct payload
|
|
56
|
+
- API_DB: API call correctly persists/reads from database
|
|
57
|
+
- UI_API_DB: Full stack — UI triggers API which updates DB, then UI reflects new state
|
|
58
|
+
- SERVICE_SERVICE: Backend service correctly calls another service
|
|
59
|
+
|
|
60
|
+
GROUNDING RULES:
|
|
61
|
+
- Only reference screen elements from the list below
|
|
62
|
+
- Only reference API endpoints from the list below
|
|
63
|
+
- DB operations use the DATA_MODEL schema
|
|
64
|
+
- DB steps use "TODO_DB_CONNECTION" placeholder
|
|
65
|
+
|
|
66
|
+
OUTPUT FORMAT — valid JSON array only, no markdown:
|
|
67
|
+
[
|
|
68
|
+
{
|
|
69
|
+
"title": "string",
|
|
70
|
+
"kind": "INTEGRATION|POSITIVE|NEGATIVE",
|
|
71
|
+
"priority": "CRITICAL|HIGH|MEDIUM",
|
|
72
|
+
"integrationLayer": "UI_API|API_DB|UI_API_DB|SERVICE_SERVICE",
|
|
73
|
+
"preconditions": "string",
|
|
74
|
+
"dbSetup": "string (SQL/ORM with TODO_DB_CONNECTION)",
|
|
75
|
+
"steps": ["UI step1", "UI step2"],
|
|
76
|
+
"apiAssertions": ["Assert status 201", "Assert response.id is not null"],
|
|
77
|
+
"dbAssertions": ["Assert DB: SELECT COUNT(*) FROM table WHERE... = 1"],
|
|
78
|
+
"expectedResult": "string",
|
|
79
|
+
"dbTeardown": "string (cleanup)",
|
|
80
|
+
"groundedOn": ["ScreenName.ElementMeaning or endpoint:METHOD /path"],
|
|
81
|
+
"tags": ["integration"]
|
|
82
|
+
}
|
|
83
|
+
]`;
|
|
84
|
+
const user = `Generate integration test cases verifying full-stack behavior.
|
|
85
|
+
|
|
86
|
+
SCREENS WITH INTERACTIVE FORMS:
|
|
87
|
+
${formScreens || ' (No form screens found — run a crawl first)'}
|
|
88
|
+
|
|
89
|
+
API ENDPOINTS:
|
|
90
|
+
${endpointSummary || ' (No endpoints captured)'}
|
|
91
|
+
|
|
92
|
+
SKILL FILE CONTEXT:
|
|
93
|
+
${skillCtx.slice(0, 5000)}
|
|
94
|
+
|
|
95
|
+
Generate tests for:
|
|
96
|
+
1. FORM SUBMIT → API → DB (UI_API_DB): Fill form → API persists → DB has new record
|
|
97
|
+
2. LOGIN → SESSION → PROTECTED RESOURCE (UI_API): Auth creates valid session token
|
|
98
|
+
3. CREATE → READ (API_DB): POST creates resource → GET returns it with correct fields
|
|
99
|
+
4. UPDATE → VERIFY (UI_API_DB): Edit form → API PATCH → DB shows new value → UI reflects
|
|
100
|
+
5. DELETE → 404 (UI_API_DB): UI delete → API DELETE → resource no longer accessible
|
|
101
|
+
6. CONCURRENT MODIFICATION (NEGATIVE): Two updates to same record → conflict handled
|
|
102
|
+
7. INVALID STATE TRANSITION (NEGATIVE): UI tries illegal state change → API rejects, DB unchanged
|
|
103
|
+
|
|
104
|
+
Rules:
|
|
105
|
+
- dbSetup/dbTeardown use "TODO_DB_CONNECTION" as the connection variable
|
|
106
|
+
- apiAssertions must be exact: status code + specific field name checks
|
|
107
|
+
- dbAssertions must be exact: specific column/table queries after the operation
|
|
108
|
+
- Reference ACTUAL element names and endpoint paths from context above`;
|
|
109
|
+
const seed = (0, llm_js_1.deterministicSeed)(`${opts.tenantId}:${opts.projectId}:integration:${endpointSummary.slice(0, 300)}`);
|
|
110
|
+
const system = await (0, core_1.getAgentPrompt)('integration-test-generation', undefined, opts.tenantId) ?? SYSTEM_DEFAULT;
|
|
111
|
+
let parsed = [];
|
|
112
|
+
const t0 = Date.now();
|
|
113
|
+
try {
|
|
114
|
+
const { text: raw, usage } = await (0, llm_js_1.askTracked)(system, user, llmCfg, undefined, undefined, { temperature: 0, seed });
|
|
115
|
+
const latencyMs = Date.now() - t0;
|
|
116
|
+
(0, app_brain_1.recordLLMCall)({
|
|
117
|
+
tenantId: opts.tenantId, projectId: opts.projectId,
|
|
118
|
+
agentType: 'integration_test', label: 'integration_test_generation', usage, latencyMs,
|
|
119
|
+
}).catch(console.error);
|
|
120
|
+
const jsonMatch = raw.match(/\[[\s\S]*\]/);
|
|
121
|
+
if (jsonMatch) {
|
|
122
|
+
const items = JSON.parse(jsonMatch[0]);
|
|
123
|
+
parsed = items
|
|
124
|
+
.filter((tc) => tc.title && Array.isArray(tc.steps) && tc.steps.length >= 1)
|
|
125
|
+
.map((tc) => ({
|
|
126
|
+
title: String(tc.title).slice(0, 200),
|
|
127
|
+
kind: (['INTEGRATION', 'POSITIVE', 'NEGATIVE'].includes(tc.kind) ? tc.kind : 'INTEGRATION'),
|
|
128
|
+
priority: (['CRITICAL', 'HIGH', 'MEDIUM'].includes(tc.priority) ? tc.priority : 'HIGH'),
|
|
129
|
+
integrationLayer: (['UI_API', 'API_DB', 'UI_API_DB', 'SERVICE_SERVICE'].includes(tc.integrationLayer) ? tc.integrationLayer : 'UI_API_DB'),
|
|
130
|
+
preconditions: String(tc.preconditions || '').slice(0, 300),
|
|
131
|
+
dbSetup: String(tc.dbSetup || '').slice(0, 600),
|
|
132
|
+
steps: tc.steps.map((s) => String(s).slice(0, 400)),
|
|
133
|
+
apiAssertions: Array.isArray(tc.apiAssertions) ? tc.apiAssertions.map((a) => String(a).slice(0, 300)) : [],
|
|
134
|
+
dbAssertions: Array.isArray(tc.dbAssertions) ? tc.dbAssertions.map((a) => String(a).slice(0, 300)) : [],
|
|
135
|
+
expectedResult: String(tc.expectedResult || '').slice(0, 500),
|
|
136
|
+
dbTeardown: String(tc.dbTeardown || '').slice(0, 400),
|
|
137
|
+
groundedOn: Array.isArray(tc.groundedOn) ? tc.groundedOn.map(String) : [],
|
|
138
|
+
tags: ['integration', tc.integrationLayer || 'UI_API_DB', ...(Array.isArray(tc.tags) ? tc.tags.map(String) : [])],
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
console.error('[integration-test-agent] parse error:', e?.message);
|
|
144
|
+
}
|
|
145
|
+
if (parsed.length === 0)
|
|
146
|
+
return { status: 'LLM_EMPTY', cases: [], saved: null };
|
|
147
|
+
const suiteName = 'Integration Tests: Full Stack';
|
|
148
|
+
const saved = await app_brain_1.AppBrain.saveGeneratedTestCases(opts.tenantId, opts.projectId, suiteName, parsed.map((tc) => ({
|
|
149
|
+
title: tc.title,
|
|
150
|
+
kind: tc.kind,
|
|
151
|
+
priority: tc.priority,
|
|
152
|
+
preconditions: `${tc.preconditions}\n\nDB Setup: ${tc.dbSetup}`,
|
|
153
|
+
steps: [
|
|
154
|
+
...tc.steps,
|
|
155
|
+
...tc.apiAssertions.map((a) => `Assert API: ${a}`),
|
|
156
|
+
...tc.dbAssertions.map((a) => `Assert DB: ${a}`),
|
|
157
|
+
],
|
|
158
|
+
expectedResult: `${tc.expectedResult}\n\nDB Teardown: ${tc.dbTeardown}`,
|
|
159
|
+
groundedOn: tc.groundedOn,
|
|
160
|
+
tags: tc.tags,
|
|
161
|
+
source: 'AI_GENERATED',
|
|
162
|
+
})));
|
|
163
|
+
console.log(`[integration-test-agent] ${parsed.length} integration test cases saved`);
|
|
164
|
+
return { status: 'GENERATED', cases: parsed, saved, suiteName };
|
|
165
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface LabelInput {
|
|
2
|
+
id: string;
|
|
3
|
+
title: string;
|
|
4
|
+
stepsText: string;
|
|
5
|
+
expectedResult?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface LabelResult {
|
|
8
|
+
id: string;
|
|
9
|
+
priority: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW';
|
|
10
|
+
inferredKind: string;
|
|
11
|
+
tags: string[];
|
|
12
|
+
confidence: number;
|
|
13
|
+
needsReview: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function labelTestCasesBatch(cases: LabelInput[], tenantId: string): Promise<LabelResult[]>;
|