@zibby/core 0.3.6 → 0.3.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.
- package/dist/index.js +34 -32
- package/dist/package.json +2 -1
- package/dist/register-built-in-strategies.js +25 -23
- package/dist/strategies/claude-strategy.js +3 -1
- package/dist/strategies/index.js +25 -23
- package/dist/templates/browser-test-automation/graph.mjs +20 -6
- package/dist/templates/browser-test-automation/nodes/preflight.mjs +50 -15
- package/dist/templates/browser-test-automation/state.js +61 -0
- package/dist/templates/code-analysis/README.md +60 -0
- package/dist/templates/code-analysis/graph.mjs +33 -0
- package/dist/templates/code-analysis/nodes/analyze-ticket-node.js +1 -1
- package/dist/templates/code-analysis/nodes/create-pr-node.js +1 -1
- package/dist/templates/code-analysis/nodes/generate-code-node.js +1 -1
- package/dist/templates/code-analysis/nodes/generate-test-cases-node.js +1 -1
- package/dist/templates/code-analysis/nodes/services/prMetaService.js +1 -1
- package/dist/templates/code-analysis/state.js +14 -6
- package/dist/templates/generate-test-cases/README.md +72 -0
- package/dist/templates/generate-test-cases/graph.mjs +46 -0
- package/dist/templates/generate-test-cases/nodes/generate-test-cases-node.js +381 -0
- package/dist/templates/generate-test-cases/nodes/setup-node.js +142 -0
- package/dist/templates/generate-test-cases/state.js +54 -0
- package/dist/templates/index.js +53 -0
- package/package.json +2 -1
- package/templates/browser-test-automation/graph.mjs +20 -6
- package/templates/browser-test-automation/nodes/preflight.mjs +50 -15
- package/templates/browser-test-automation/state.js +61 -0
- package/templates/code-analysis/README.md +60 -0
- package/templates/code-analysis/graph.mjs +33 -0
- package/templates/code-analysis/nodes/analyze-ticket-node.js +1 -1
- package/templates/code-analysis/nodes/create-pr-node.js +1 -1
- package/templates/code-analysis/nodes/generate-code-node.js +1 -1
- package/templates/code-analysis/nodes/generate-test-cases-node.js +1 -1
- package/templates/code-analysis/nodes/services/prMetaService.js +1 -1
- package/templates/code-analysis/state.js +14 -6
- package/templates/generate-test-cases/README.md +72 -0
- package/templates/generate-test-cases/graph.mjs +46 -0
- package/templates/generate-test-cases/nodes/generate-test-cases-node.js +381 -0
- package/templates/generate-test-cases/nodes/setup-node.js +142 -0
- package/templates/generate-test-cases/state.js +54 -0
- package/templates/index.js +53 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* code-analysis template — scaffoldable WorkflowAgent wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Why this file exists alongside graph.js:
|
|
5
|
+
* - graph.js exports `buildAnalysisGraph(graph)` — a builder
|
|
6
|
+
* function the existing `zibby analyze-graph` cli + backend
|
|
7
|
+
* analysis handlers consume directly. Its shape can't change
|
|
8
|
+
* without breaking those callers.
|
|
9
|
+
* - To make code-analysis scaffoldable via
|
|
10
|
+
* `zibby workflow new <slug> -t code-analysis` and runnable via
|
|
11
|
+
* `zibby workflow run <slug>` / `zibby workflow deploy <slug>`,
|
|
12
|
+
* the template needs to expose a WorkflowAgent class — same
|
|
13
|
+
* contract as browser-test-automation.
|
|
14
|
+
*
|
|
15
|
+
* This .mjs is the entry the user-facing scaffold uses; graph.js
|
|
16
|
+
* stays put for internal callers.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { WorkflowAgent, WorkflowGraph } from '@zibby/core';
|
|
20
|
+
import { buildAnalysisGraph } from './graph.js';
|
|
21
|
+
|
|
22
|
+
export class CodeAnalysisAgent extends WorkflowAgent {
|
|
23
|
+
buildGraph() {
|
|
24
|
+
const graph = new WorkflowGraph();
|
|
25
|
+
buildAnalysisGraph(graph);
|
|
26
|
+
return graph;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async onComplete(result) {
|
|
30
|
+
const ok = result.success !== false;
|
|
31
|
+
console.log(`[code-analysis] complete — success: ${ok}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from 'fs';
|
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { randomBytes } from 'crypto';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import { adfToText } from '
|
|
5
|
+
import { adfToText } from '@zibby/core/utils/adf-converter.js';
|
|
6
6
|
|
|
7
7
|
const generateId = () => randomBytes(16).toString('hex');
|
|
8
8
|
|
|
@@ -10,7 +10,7 @@ import { existsSync, readFileSync } from 'fs';
|
|
|
10
10
|
import Handlebars from 'handlebars';
|
|
11
11
|
import { invokeAgent } from '@zibby/core';
|
|
12
12
|
import { generatePRMeta } from './services/prMetaService.js';
|
|
13
|
-
import { adfToText } from '
|
|
13
|
+
import { adfToText } from '@zibby/core/utils/adf-converter.js';
|
|
14
14
|
import { z } from 'zod';
|
|
15
15
|
|
|
16
16
|
const CodeImplementationOutputSchema = z.object({
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { invokeAgent } from '@zibby/core';
|
|
12
12
|
import { z } from 'zod';
|
|
13
13
|
import { randomBytes } from 'crypto';
|
|
14
|
-
import { adfToText } from '
|
|
14
|
+
import { adfToText } from '@zibby/core/utils/adf-converter.js';
|
|
15
15
|
|
|
16
16
|
// Generate a simple unique ID
|
|
17
17
|
const generateId = () => randomBytes(8).toString('hex');
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import { invokeAgent } from '@zibby/core';
|
|
7
|
-
import { adfToText } from '
|
|
7
|
+
import { adfToText } from '@zibby/core/utils/adf-converter.js';
|
|
8
8
|
|
|
9
9
|
const PRMetaSchema = z.object({
|
|
10
10
|
prTitle: z.string().describe('Short PR title that includes the ticket key'),
|
|
@@ -31,10 +31,18 @@ export const analysisStateSchema = z.object({
|
|
|
31
31
|
githubToken: z.string().optional().describe('GitHub personal access token'),
|
|
32
32
|
model: z.string().default('auto').describe('AI model to use'),
|
|
33
33
|
nodeConfigs: z.record(z.string(), z.any()).optional().describe('Per-node configuration overrides'),
|
|
34
|
-
|
|
35
|
-
EXECUTION_ID: z.string().nullable().optional(),
|
|
36
|
-
PROGRESS_QUEUE_URL: z.string().url().nullable().optional(),
|
|
37
|
-
PROGRESS_API_URL: z.string().url().nullable().optional(),
|
|
38
|
-
SQS_AUTH_TOKEN: z.string().nullable().optional(),
|
|
39
|
-
PROJECT_API_TOKEN: z.string().nullable().optional(),
|
|
40
34
|
});
|
|
35
|
+
|
|
36
|
+
// NOTE: legacy `EXECUTION_ID`, `PROGRESS_QUEUE_URL`, `PROGRESS_API_URL`,
|
|
37
|
+
// `SQS_AUTH_TOKEN`, `PROJECT_API_TOKEN` fields are no longer declared in
|
|
38
|
+
// this schema. They were used only by the legacy `analyze-graph` cli +
|
|
39
|
+
// the analysis UI's progress reporter — never by template nodes. Zod's
|
|
40
|
+
// safeParse here doesn't strip unknown keys (see graph.js:504), so the
|
|
41
|
+
// legacy flow still works at runtime: analyze-graph.js injects them
|
|
42
|
+
// into initialState, validation passes, nodes ignore them, the
|
|
43
|
+
// progress-reporter middleware reads them off the state argument.
|
|
44
|
+
//
|
|
45
|
+
// Keeping them out of the schema means scaffolded user copies (via
|
|
46
|
+
// `zibby workflow new -t code-analysis`) ship a clean state that
|
|
47
|
+
// reflects only what the template actually needs — no false coupling
|
|
48
|
+
// to internal infra fields.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Generate Test Cases Template
|
|
2
|
+
|
|
3
|
+
Standalone slice of `code-analysis` — takes a code diff you already
|
|
4
|
+
have and generates plain-English test specifications for it. Skips
|
|
5
|
+
ticket-analysis and code-gen.
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- You have a PR diff (or `git diff` output) and want test specs for the changes.
|
|
10
|
+
- You don't need the LLM to generate the code itself — that already exists.
|
|
11
|
+
- You want a smaller, faster pipeline than the full code-analysis workflow.
|
|
12
|
+
|
|
13
|
+
If you want the full pipeline (analyze ticket → generate code → generate
|
|
14
|
+
tests in one shot), use `code-analysis` instead.
|
|
15
|
+
|
|
16
|
+
## Graph
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
setup ─→ generate_test_cases
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
- `setup` — clone repos so the LLM can explore actual routing /
|
|
23
|
+
components when writing test steps
|
|
24
|
+
- `generate_test_cases` — LLM reads the diff + ticket context, emits
|
|
25
|
+
4–8 plain-English test specs (with priority + category)
|
|
26
|
+
|
|
27
|
+
## Required state inputs
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
{
|
|
31
|
+
workspace: '/abs/path/to/workspace',
|
|
32
|
+
repos: [{
|
|
33
|
+
name: 'my-app',
|
|
34
|
+
url: 'https://github.com/org/my-app.git',
|
|
35
|
+
branch: 'main',
|
|
36
|
+
isPrimary: true,
|
|
37
|
+
}],
|
|
38
|
+
ticketContext: {
|
|
39
|
+
ticketKey: 'PROJ-123',
|
|
40
|
+
summary: 'short title',
|
|
41
|
+
description: 'long description',
|
|
42
|
+
},
|
|
43
|
+
codeImplementation: {
|
|
44
|
+
diff: '...unified diff string...',
|
|
45
|
+
changedFiles: ['src/auth/login.ts', 'src/components/LoginForm.tsx'],
|
|
46
|
+
},
|
|
47
|
+
githubToken: process.env.GITHUB_TOKEN,
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The `codeImplementation` field is what makes this template standalone —
|
|
52
|
+
in `code-analysis` it's produced by the upstream `generate_code` node;
|
|
53
|
+
here you supply it directly.
|
|
54
|
+
|
|
55
|
+
See `state.js` for the full Zod schema.
|
|
56
|
+
|
|
57
|
+
## Customizing the prompt
|
|
58
|
+
|
|
59
|
+
The test-generation prompt is inlined in
|
|
60
|
+
`nodes/generate-test-cases-node.js` (the `buildTestGenerationPrompt`
|
|
61
|
+
function). Edit that to change the LLM's instructions — what
|
|
62
|
+
exploration steps it follows, what test format it returns, etc.
|
|
63
|
+
|
|
64
|
+
## Cloud deployment
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
zibby workflow deploy <your-slug>
|
|
68
|
+
zibby workflow trigger <uuid> --input '{"workspace": "...", "codeImplementation": {...}, ...}'
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Cloud runs need network access for `git clone` (the `setup` node
|
|
72
|
+
shells out to `git`). Default cloud egress works.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* generate-test-cases template — standalone slice of code-analysis.
|
|
3
|
+
*
|
|
4
|
+
* Two-node graph:
|
|
5
|
+
*
|
|
6
|
+
* setup ─→ generate_test_cases
|
|
7
|
+
*
|
|
8
|
+
* Use case: "I have a PR diff already; generate test cases for it."
|
|
9
|
+
* Skips ticket-analysis + code-gen — the user provides the diff
|
|
10
|
+
* directly as `state.codeImplementation`.
|
|
11
|
+
*
|
|
12
|
+
* Why duplicated nodes/setup-node.js + nodes/generate-test-cases-node.js
|
|
13
|
+
* (rather than importing from `../code-analysis/nodes/...`):
|
|
14
|
+
* Templates have to be self-contained — when scaffolded into a user's
|
|
15
|
+
* project at `.zibby/workflows/<slug>/`, there's no sibling
|
|
16
|
+
* `code-analysis/` folder to import from. browser-test + code-analysis
|
|
17
|
+
* follow the same convention. The cost is keeping the duplicates in
|
|
18
|
+
* sync; the win is each template is independently editable.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { WorkflowAgent, WorkflowGraph } from '@zibby/core';
|
|
22
|
+
import { setupNode } from './nodes/setup-node.js';
|
|
23
|
+
import { generateTestCasesNode } from './nodes/generate-test-cases-node.js';
|
|
24
|
+
import { generateTestCasesStateSchema } from './state.js';
|
|
25
|
+
|
|
26
|
+
export class GenerateTestCasesAgent extends WorkflowAgent {
|
|
27
|
+
buildGraph() {
|
|
28
|
+
const graph = new WorkflowGraph();
|
|
29
|
+
graph.setStateSchema(generateTestCasesStateSchema);
|
|
30
|
+
|
|
31
|
+
graph.addNode('setup', setupNode);
|
|
32
|
+
graph.addNode('generate_test_cases', generateTestCasesNode);
|
|
33
|
+
|
|
34
|
+
graph.setEntryPoint('setup');
|
|
35
|
+
graph.addEdge('setup', 'generate_test_cases');
|
|
36
|
+
graph.addEdge('generate_test_cases', 'END');
|
|
37
|
+
|
|
38
|
+
return graph;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async onComplete(result) {
|
|
42
|
+
const tests = result.state?.generate_test_cases?.tests;
|
|
43
|
+
const count = tests?.structured?.testCases?.length || 0;
|
|
44
|
+
console.log(`[generate-test-cases] complete — ${count} test case(s) generated`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate Test Cases Node - Generate human-readable test case specifications
|
|
3
|
+
* Used by: analysisGraph, implementationGraph
|
|
4
|
+
*
|
|
5
|
+
* Output: Plain-text test specifications for AI interpretation
|
|
6
|
+
* - High-level test steps in natural language
|
|
7
|
+
* - Includes credentials, prerequisites, and expected results
|
|
8
|
+
* - Can be executed by AI agents (like MCP) and converted to Playwright
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { invokeAgent } from '@zibby/core';
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
import { randomBytes } from 'crypto';
|
|
14
|
+
import { adfToText } from '@zibby/core/utils/adf-converter.js';
|
|
15
|
+
|
|
16
|
+
// Generate a simple unique ID
|
|
17
|
+
const generateId = () => randomBytes(8).toString('hex');
|
|
18
|
+
|
|
19
|
+
// Schema for plain-text test specification
|
|
20
|
+
const TestSpecificationSchema = z.object({
|
|
21
|
+
id: z.string().optional(),
|
|
22
|
+
title: z.string().describe('Test name (e.g., "Login Functionality", "Create New User")'),
|
|
23
|
+
application: z.string().describe('Application name being tested'),
|
|
24
|
+
url: z.string().describe('Base URL of the application'),
|
|
25
|
+
testerRole: z.string().describe('Role performing the test (e.g., "Admin user", "End user")'),
|
|
26
|
+
feature: z.string().describe('Feature being tested (e.g., "Authentication - Login")'),
|
|
27
|
+
ticketId: z.string().describe('Associated ticket ID'),
|
|
28
|
+
testCredentials: z.array(z.object({
|
|
29
|
+
field: z.string().describe('Credential field name (e.g., "Email", "Password", "API Key")'),
|
|
30
|
+
value: z.string().describe('Credential value')
|
|
31
|
+
})).optional().describe('Test credentials needed (if any)'),
|
|
32
|
+
testObjective: z.string().describe('One clear sentence: what this test verifies'),
|
|
33
|
+
testSteps: z.array(z.string()).describe('Plain English steps - natural language instructions (e.g., "Navigate to /login", "Enter email: test@example.com", "Click the submit button")'),
|
|
34
|
+
expectedResults: z.array(z.string()).describe('What should happen - bullet points of expected outcomes'),
|
|
35
|
+
status: z.string().default('Ready for automation').describe('Test status'),
|
|
36
|
+
priority: z.enum(['Critical', 'High', 'Medium', 'Low']).describe('Test priority'),
|
|
37
|
+
category: z.string().describe('Test category (e.g., "Smoke Test", "Regression", "Integration")')
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const CategoryCountSchema = z.object({
|
|
41
|
+
category: z.string().describe('Category name'),
|
|
42
|
+
count: z.number().describe('Number of tests in this category')
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Schema for AI agent output (what invokeAgent validates)
|
|
46
|
+
const TestSuiteSchema = z.object({
|
|
47
|
+
testCases: z.array(TestSpecificationSchema).describe('Array of test specifications'),
|
|
48
|
+
summary: z.object({
|
|
49
|
+
totalTests: z.number(),
|
|
50
|
+
byCategory: z.array(CategoryCountSchema).optional(),
|
|
51
|
+
coverageNotes: z.string().optional()
|
|
52
|
+
}).optional()
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Node output schema (what this node returns to state)
|
|
56
|
+
const GenerateTestCasesOutputSchema = z.object({
|
|
57
|
+
success: z.boolean(),
|
|
58
|
+
tests: z.object({
|
|
59
|
+
structured: z.object({
|
|
60
|
+
testCases: z.array(z.any()),
|
|
61
|
+
summary: z.object({
|
|
62
|
+
totalTests: z.number(),
|
|
63
|
+
byCategory: z.array(CategoryCountSchema).optional()
|
|
64
|
+
}).optional()
|
|
65
|
+
}),
|
|
66
|
+
timestamp: z.string()
|
|
67
|
+
}).nullable()
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
export const generateTestCasesNode = {
|
|
71
|
+
name: 'generate_test_cases',
|
|
72
|
+
|
|
73
|
+
// Output schema defines what this node returns to state
|
|
74
|
+
outputSchema: GenerateTestCasesOutputSchema,
|
|
75
|
+
|
|
76
|
+
execute: async (state) => {
|
|
77
|
+
console.log('\n🧪 Generating test cases...');
|
|
78
|
+
|
|
79
|
+
const { ticketContext, workspace, model, nodeConfigs = {} } = state;
|
|
80
|
+
const aiModel = model || ticketContext.model || 'auto';
|
|
81
|
+
// Standalone-template variant: this template runs WITHOUT an upstream
|
|
82
|
+
// generate_code node, so the diff comes in directly as
|
|
83
|
+
// `state.codeImplementation`. Keep the original two fallbacks so the
|
|
84
|
+
// node still works if someone wires it after a code-gen node.
|
|
85
|
+
const codeImpl = state.codeImplementation ||
|
|
86
|
+
state.generate_code?.codeImplementation ||
|
|
87
|
+
state.implement_code?.codeImplementation;
|
|
88
|
+
|
|
89
|
+
if (!codeImpl || !codeImpl.diff) {
|
|
90
|
+
console.log('⚠️ No code changes detected, skipping test generation');
|
|
91
|
+
return {
|
|
92
|
+
success: true,
|
|
93
|
+
tests: null
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Get node config for this node (if user configured it)
|
|
98
|
+
const nodeConfig = nodeConfigs.generate_test_cases || {};
|
|
99
|
+
console.log('📋 Node config:', JSON.stringify(nodeConfig, null, 2));
|
|
100
|
+
|
|
101
|
+
// Build test generation prompt with workspace path for codebase exploration
|
|
102
|
+
const testPrompt = buildTestGenerationPrompt(ticketContext, codeImpl, workspace, nodeConfig);
|
|
103
|
+
|
|
104
|
+
console.log(`🚀 Running AI Agent to generate tests with model: ${aiModel}...`);
|
|
105
|
+
|
|
106
|
+
const agentResult = await invokeAgent(testPrompt, {
|
|
107
|
+
state,
|
|
108
|
+
model: aiModel,
|
|
109
|
+
schema: TestSuiteSchema
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const validatedOutput = agentResult?.structured || agentResult;
|
|
113
|
+
|
|
114
|
+
// Convert structured AI output → simple { id, title, priority, content } format
|
|
115
|
+
const testCases = (validatedOutput?.testCases || []).map(test => ({
|
|
116
|
+
id: generateId(),
|
|
117
|
+
title: test.title,
|
|
118
|
+
priority: test.priority || 'Medium',
|
|
119
|
+
content: renderTestCaseToText(test),
|
|
120
|
+
status: 'pending'
|
|
121
|
+
}));
|
|
122
|
+
|
|
123
|
+
console.log(`✅ Generated ${testCases.length} test cases`);
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
success: true,
|
|
127
|
+
tests: {
|
|
128
|
+
structured: {
|
|
129
|
+
testCases,
|
|
130
|
+
summary: validatedOutput.summary || {
|
|
131
|
+
totalTests: testCases.length,
|
|
132
|
+
byCategory: Object.entries(
|
|
133
|
+
testCases.reduce((acc, tc) => {
|
|
134
|
+
acc[tc.category] = (acc[tc.category] || 0) + 1;
|
|
135
|
+
return acc;
|
|
136
|
+
}, {})
|
|
137
|
+
).map(([category, count]) => ({ category, count }))
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
timestamp: new Date().toISOString()
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
function renderTestCaseToText(test) {
|
|
147
|
+
const lines = [];
|
|
148
|
+
|
|
149
|
+
if (test.testObjective) {
|
|
150
|
+
lines.push('WHAT THIS TESTS');
|
|
151
|
+
lines.push(test.testObjective);
|
|
152
|
+
lines.push('');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (test.url) {
|
|
156
|
+
lines.push('START URL');
|
|
157
|
+
lines.push(test.url);
|
|
158
|
+
lines.push('');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (test.testCredentials && test.testCredentials.length > 0) {
|
|
162
|
+
lines.push('TEST CREDENTIALS');
|
|
163
|
+
test.testCredentials.forEach(cred => {
|
|
164
|
+
lines.push(`• ${cred.field}: ${cred.value}`);
|
|
165
|
+
});
|
|
166
|
+
lines.push('');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (test.testSteps && test.testSteps.length > 0) {
|
|
170
|
+
lines.push('TEST STEPS');
|
|
171
|
+
test.testSteps.forEach((step, i) => {
|
|
172
|
+
lines.push(`${i + 1}. ${step}`);
|
|
173
|
+
});
|
|
174
|
+
lines.push('');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (test.expectedResults && test.expectedResults.length > 0) {
|
|
178
|
+
lines.push('EXPECTED RESULTS');
|
|
179
|
+
test.expectedResults.forEach(result => {
|
|
180
|
+
lines.push(`• ${result}`);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return lines.join('\n').trim();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function buildTestGenerationPrompt(ticketContext, codeImpl, workspace, nodeConfig = {}) {
|
|
188
|
+
// Extract user-provided context (test URL, admin credentials, etc.)
|
|
189
|
+
const extractContext = nodeConfig.extractContext || {};
|
|
190
|
+
let contextSection = '';
|
|
191
|
+
if (extractContext.testBaseUrl || extractContext.adminUsername || extractContext.testAccountUsername) {
|
|
192
|
+
contextSection = `
|
|
193
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
194
|
+
PROVIDED TEST CONTEXT (USE THESE IN TEST CASES)
|
|
195
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
196
|
+
|
|
197
|
+
${extractContext.testBaseUrl ? `Test Base URL: ${extractContext.testBaseUrl}` : ''}
|
|
198
|
+
${extractContext.adminUsername ? `Admin Username: ${extractContext.adminUsername}` : ''}
|
|
199
|
+
${extractContext.adminPassword ? `Admin Password: ${extractContext.adminPassword}` : ''}
|
|
200
|
+
${extractContext.testAccountUsername ? `Test Account Username: ${extractContext.testAccountUsername}` : ''}
|
|
201
|
+
${extractContext.testAccountPassword ? `Test Account Password: ${extractContext.testAccountPassword}` : ''}
|
|
202
|
+
|
|
203
|
+
**IMPORTANT**: Use these exact values in your test cases. Do NOT make up URLs or credentials.
|
|
204
|
+
|
|
205
|
+
`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return `You are an AI test automation engineer creating human-readable test specifications.
|
|
209
|
+
|
|
210
|
+
WORKSPACE: ${workspace}
|
|
211
|
+
|
|
212
|
+
TICKET: ${ticketContext.ticketKey} - ${ticketContext.summary}
|
|
213
|
+
${ticketContext.description ? `Description: ${typeof ticketContext.description === 'object' ? adfToText(ticketContext.description) : ticketContext.description}` : ''}
|
|
214
|
+
|
|
215
|
+
MODIFIED FILES:
|
|
216
|
+
${codeImpl.changedFiles.map(f => ` - ${f}`).join('\n')}
|
|
217
|
+
|
|
218
|
+
FULL CODE DIFF:
|
|
219
|
+
\`\`\`diff
|
|
220
|
+
${codeImpl.diff}
|
|
221
|
+
\`\`\`
|
|
222
|
+
|
|
223
|
+
${contextSection}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
224
|
+
|
|
225
|
+
YOUR JOB: Generate 4-8 test specifications in plain-text format that AI agents can interpret.
|
|
226
|
+
|
|
227
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
228
|
+
STEP 1: EXPLORE THE CODEBASE (MANDATORY - DO NOT SKIP)
|
|
229
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
230
|
+
|
|
231
|
+
YOU MUST explore the codebase to understand the application structure BEFORE writing tests.
|
|
232
|
+
|
|
233
|
+
REQUIRED EXPLORATION:
|
|
234
|
+
|
|
235
|
+
1. **Find and Read Routing Files**
|
|
236
|
+
- Search the workspace for routing files: look for common names like App, routes, router, main, index files in .js/.jsx/.ts/.tsx format
|
|
237
|
+
- Check common locations: src/, app/, client/, web/, components/, pages/ directories
|
|
238
|
+
- Identify ALL routes and their URL patterns
|
|
239
|
+
- Pay special attention to routes with parameters (e.g., /stores/:storeId/products, /users/:userId/orders, /items/:itemId)
|
|
240
|
+
|
|
241
|
+
2. **Understand Route Parameters**
|
|
242
|
+
- Dynamic segments in routes (marked with :paramName or {paramName}) are PLACEHOLDERS
|
|
243
|
+
- You MUST include navigation steps to select/create that resource first
|
|
244
|
+
- Example: To reach /stores/:storeId/products, you must first navigate to /stores list and select a specific store
|
|
245
|
+
- Example: To reach /orders/:orderId/details, you must first navigate to /orders list and click on a specific order
|
|
246
|
+
|
|
247
|
+
3. **Map Navigation Flow**
|
|
248
|
+
- Understand the hierarchy: authentication → landing/home → resource lists → individual resources → sub-features
|
|
249
|
+
- Find what links, buttons, tabs exist on each page to navigate to the next level
|
|
250
|
+
- Read page components to understand the UI structure and navigation elements
|
|
251
|
+
|
|
252
|
+
4. **Find Authentication**
|
|
253
|
+
- Check for login/signin routes, auth components, protected routes, authentication guards
|
|
254
|
+
- Look for HOCs, route wrappers, or middleware that handle authentication
|
|
255
|
+
- Understand if authentication is required and what the login flow looks like
|
|
256
|
+
|
|
257
|
+
5. **Identify Page Components**
|
|
258
|
+
- Find the actual page/view components referenced in routes
|
|
259
|
+
- Read their code to understand imports, props, state management, and structure
|
|
260
|
+
- Understand what data they load, what APIs they call, and what user actions they support
|
|
261
|
+
|
|
262
|
+
CRITICAL RULES:
|
|
263
|
+
- Routes with dynamic parameters (like :id, :userId, :orderId) REQUIRE parent resource selection
|
|
264
|
+
- You CANNOT jump directly to a URL with an ID - you must navigate through the parent list and SELECT that item
|
|
265
|
+
- Base your test steps on ACTUAL navigation paths you discover in the codebase
|
|
266
|
+
- If you don't explore properly, your tests will have WRONG navigation and be unusable by AI agents
|
|
267
|
+
|
|
268
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
269
|
+
STEP 2: GENERATE TEST SPECIFICATIONS
|
|
270
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
271
|
+
|
|
272
|
+
For each test, provide:
|
|
273
|
+
- **title**: Test name (e.g., "Filter Orders by Status")
|
|
274
|
+
- **application**: Application name (infer from package.json or codebase)
|
|
275
|
+
- **url**: Base application URL (usually http://localhost:3000 or staging URL if found)
|
|
276
|
+
- **testerRole**: Who performs this test (e.g., "Admin user", "End user", "Guest")
|
|
277
|
+
- **feature**: Feature being tested (match the modified files)
|
|
278
|
+
- **ticketId**: Use the ticket key provided above
|
|
279
|
+
- **testCredentials**: Array of credentials needed (if authentication required):
|
|
280
|
+
- field: "Email" / "Password" / "Username" / "API Key", etc.
|
|
281
|
+
- value: Use realistic test data (e.g., "admin@example.com", "TestPass123")
|
|
282
|
+
- **testObjective**: One clear sentence describing what this test verifies
|
|
283
|
+
- **testSteps**: Plain English steps - MUST match actual navigation flow you discovered. Example:
|
|
284
|
+
* "Navigate to login page http://localhost:3000/login"
|
|
285
|
+
* "Enter email: admin@example.com"
|
|
286
|
+
* "Enter password: TestPass123"
|
|
287
|
+
* "Click the Sign In button"
|
|
288
|
+
* "Navigate to Projects page http://localhost:3000/projects"
|
|
289
|
+
* "Click on project 'Test Project 1'"
|
|
290
|
+
* "Navigate to Orders tab"
|
|
291
|
+
* "Click the status filter dropdown"
|
|
292
|
+
* "Select 'Completed' from the filter"
|
|
293
|
+
* "Verify only completed orders are displayed"
|
|
294
|
+
DON'T use technical terms like "action", "target", "selector" - just natural language
|
|
295
|
+
- **expectedResults**: Bullet points of what should happen
|
|
296
|
+
- **priority**: Critical, High, Medium, or Low
|
|
297
|
+
- **category**: Test category (e.g., "Smoke Test", "Regression", "Integration")
|
|
298
|
+
|
|
299
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
300
|
+
EXAMPLE: Multi-Level Navigation (Generic Restaurant Ordering App)
|
|
301
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
302
|
+
|
|
303
|
+
Assume this routing structure (discovered from App.jsx):
|
|
304
|
+
/login
|
|
305
|
+
/restaurants
|
|
306
|
+
/restaurants/:restaurantId/menu
|
|
307
|
+
/restaurants/:restaurantId/menu/:itemId/details
|
|
308
|
+
|
|
309
|
+
CORRECT Test Steps (follows nested structure):
|
|
310
|
+
{
|
|
311
|
+
"title": "Add Menu Item to Cart with Size Selection",
|
|
312
|
+
"application": "Restaurant Ordering System",
|
|
313
|
+
"url": "http://localhost:3000",
|
|
314
|
+
"testerRole": "Customer",
|
|
315
|
+
"feature": "Menu - Item Size Selection",
|
|
316
|
+
"ticketId": "REST-456",
|
|
317
|
+
"testCredentials": [
|
|
318
|
+
{ "field": "Email", "value": "customer@email.com" },
|
|
319
|
+
{ "field": "Password", "value": "Order123" }
|
|
320
|
+
],
|
|
321
|
+
"testObjective": "Verify customers can select different sizes when adding menu items to cart",
|
|
322
|
+
"testSteps": [
|
|
323
|
+
"Navigate to login page http://localhost:3000/login",
|
|
324
|
+
"Enter email: customer@email.com",
|
|
325
|
+
"Enter password: Order123",
|
|
326
|
+
"Click the Sign In button",
|
|
327
|
+
"Navigate to Restaurants page http://localhost:3000/restaurants",
|
|
328
|
+
"Click on restaurant 'Downtown Pizza'",
|
|
329
|
+
"Wait for menu to load",
|
|
330
|
+
"Scroll to 'Beverages' section",
|
|
331
|
+
"Click on menu item 'Lemonade'",
|
|
332
|
+
"Wait for item details to display",
|
|
333
|
+
"Click the size dropdown",
|
|
334
|
+
"Select 'Large' size option",
|
|
335
|
+
"Verify price updates to large size price",
|
|
336
|
+
"Click Add to Cart button",
|
|
337
|
+
"Verify item added to cart with Large size"
|
|
338
|
+
],
|
|
339
|
+
"expectedResults": [
|
|
340
|
+
"Size dropdown displays all available sizes (Small, Medium, Large)",
|
|
341
|
+
"Price updates when different size is selected",
|
|
342
|
+
"Item is added to cart with correct size",
|
|
343
|
+
"Cart icon shows updated item count",
|
|
344
|
+
"No errors displayed during the process"
|
|
345
|
+
],
|
|
346
|
+
"priority": "High",
|
|
347
|
+
"category": "Smoke Test"
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
WRONG Test Steps (skips required navigation):
|
|
351
|
+
❌ "Navigate to Menu page" (Too vague - which restaurant?)
|
|
352
|
+
❌ "Go to /restaurants/123/menu" (Can't directly use IDs without selection)
|
|
353
|
+
❌ "Select item from menu" (Need to navigate to restaurant first)
|
|
354
|
+
|
|
355
|
+
CORRECT Test Steps (shows proper navigation):
|
|
356
|
+
✅ "Navigate to Restaurants page http://localhost:3000/restaurants"
|
|
357
|
+
✅ "Click on restaurant 'Downtown Pizza'"
|
|
358
|
+
✅ "Wait for menu to load"
|
|
359
|
+
✅ "Click on menu item 'Lemonade'"
|
|
360
|
+
|
|
361
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
362
|
+
FINAL REMINDERS
|
|
363
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
364
|
+
|
|
365
|
+
1. ALWAYS explore routing files FIRST
|
|
366
|
+
2. NEVER skip navigation steps - include full path from login to feature
|
|
367
|
+
3. If routes have :parameters, include parent resource selection
|
|
368
|
+
4. Use ACTUAL URLs and paths from the codebase
|
|
369
|
+
5. Write plain English steps - no technical jargon
|
|
370
|
+
6. Base tests on REAL navigation flow, not assumptions
|
|
371
|
+
|
|
372
|
+
Now generate test specifications for the ticket above.`;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Agent Strategy Handling:
|
|
376
|
+
* - Claude: Uses tool calling with schema → guaranteed structured output
|
|
377
|
+
* - Cursor: Post-processes raw output with LangChain withStructuredOutput
|
|
378
|
+
*
|
|
379
|
+
* Node receives validated structured data regardless of agent type.
|
|
380
|
+
*/
|
|
381
|
+
|