@zibby/core 0.4.6 → 0.5.1
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 +150 -153
- package/dist/package.json +2 -9
- package/dist/utils/run-index-post-cli.js +1 -4
- package/package.json +2 -9
- package/dist/templates/browser-test-automation/README.md +0 -136
- package/dist/templates/browser-test-automation/chat.mjs +0 -36
- package/dist/templates/browser-test-automation/graph.mjs +0 -80
- package/dist/templates/browser-test-automation/nodes/cache-replay.mjs +0 -213
- package/dist/templates/browser-test-automation/nodes/execute-live.mjs +0 -254
- package/dist/templates/browser-test-automation/nodes/generate-script.mjs +0 -108
- package/dist/templates/browser-test-automation/nodes/index.mjs +0 -4
- package/dist/templates/browser-test-automation/nodes/preflight.mjs +0 -94
- package/dist/templates/browser-test-automation/nodes/utils.mjs +0 -297
- package/dist/templates/browser-test-automation/pipeline-ids.js +0 -12
- package/dist/templates/browser-test-automation/result-handler.mjs +0 -327
- package/dist/templates/browser-test-automation/run-index.mjs +0 -420
- package/dist/templates/browser-test-automation/run_test.json +0 -358
- package/dist/templates/browser-test-automation/state.js +0 -61
- package/dist/templates/code-analysis/README.md +0 -60
- package/dist/templates/code-analysis/graph.js +0 -72
- package/dist/templates/code-analysis/graph.mjs +0 -33
- package/dist/templates/code-analysis/index.js +0 -18
- package/dist/templates/code-analysis/nodes/analyze-ticket-node.js +0 -204
- package/dist/templates/code-analysis/nodes/create-pr-node.js +0 -175
- package/dist/templates/code-analysis/nodes/finalize-node.js +0 -118
- package/dist/templates/code-analysis/nodes/generate-code-node.js +0 -425
- package/dist/templates/code-analysis/nodes/generate-test-cases-node.js +0 -376
- package/dist/templates/code-analysis/nodes/services/prMetaService.js +0 -86
- package/dist/templates/code-analysis/nodes/setup-node.js +0 -142
- package/dist/templates/code-analysis/prompts/analyze-ticket.md +0 -181
- package/dist/templates/code-analysis/prompts/generate-code.md +0 -33
- package/dist/templates/code-analysis/prompts/generate-test-cases.md +0 -110
- package/dist/templates/code-analysis/state.js +0 -48
- package/dist/templates/generate-test-cases/README.md +0 -72
- package/dist/templates/generate-test-cases/graph.mjs +0 -46
- package/dist/templates/generate-test-cases/nodes/generate-test-cases-node.js +0 -381
- package/dist/templates/generate-test-cases/nodes/setup-node.js +0 -142
- package/dist/templates/generate-test-cases/state.js +0 -54
- package/dist/templates/global-setup.js +0 -56
- package/dist/templates/index.js +0 -147
- package/dist/templates/register-nodes.js +0 -24
- package/templates/browser-test-automation/README.md +0 -136
- package/templates/browser-test-automation/chat.mjs +0 -36
- package/templates/browser-test-automation/graph.mjs +0 -80
- package/templates/browser-test-automation/nodes/cache-replay.mjs +0 -213
- package/templates/browser-test-automation/nodes/execute-live.mjs +0 -254
- package/templates/browser-test-automation/nodes/generate-script.mjs +0 -108
- package/templates/browser-test-automation/nodes/index.mjs +0 -4
- package/templates/browser-test-automation/nodes/preflight.mjs +0 -94
- package/templates/browser-test-automation/nodes/utils.mjs +0 -297
- package/templates/browser-test-automation/pipeline-ids.js +0 -12
- package/templates/browser-test-automation/result-handler.mjs +0 -327
- package/templates/browser-test-automation/run-index.mjs +0 -420
- package/templates/browser-test-automation/run_test.json +0 -358
- package/templates/browser-test-automation/state.js +0 -61
- package/templates/code-analysis/README.md +0 -60
- package/templates/code-analysis/graph.js +0 -72
- package/templates/code-analysis/graph.mjs +0 -33
- package/templates/code-analysis/index.js +0 -18
- package/templates/code-analysis/nodes/analyze-ticket-node.js +0 -204
- package/templates/code-analysis/nodes/create-pr-node.js +0 -175
- package/templates/code-analysis/nodes/finalize-node.js +0 -118
- package/templates/code-analysis/nodes/generate-code-node.js +0 -425
- package/templates/code-analysis/nodes/generate-test-cases-node.js +0 -376
- package/templates/code-analysis/nodes/services/prMetaService.js +0 -86
- package/templates/code-analysis/nodes/setup-node.js +0 -142
- package/templates/code-analysis/prompts/analyze-ticket.md +0 -181
- package/templates/code-analysis/prompts/generate-code.md +0 -33
- package/templates/code-analysis/prompts/generate-test-cases.md +0 -110
- package/templates/code-analysis/state.js +0 -48
- package/templates/generate-test-cases/README.md +0 -72
- package/templates/generate-test-cases/graph.mjs +0 -46
- package/templates/generate-test-cases/nodes/generate-test-cases-node.js +0 -381
- package/templates/generate-test-cases/nodes/setup-node.js +0 -142
- package/templates/generate-test-cases/state.js +0 -54
- package/templates/global-setup.js +0 -56
- package/templates/index.js +0 -147
- package/templates/register-nodes.js +0 -24
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Execute Live Node
|
|
3
|
-
*
|
|
4
|
-
* Purpose: Execute test in live browser using MCP Playwright tools
|
|
5
|
-
*
|
|
6
|
-
* Configuration:
|
|
7
|
-
* - capabilities: Declares ['browser'] — framework injects the appropriate MCP server
|
|
8
|
-
* - outputSchema: Structured JSON with execution results, actions, assertions
|
|
9
|
-
* - Model: Configured in .zibby.config.mjs → agent.claude.model or agent.cursor.model
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { z, SKILLS } from '@zibby/core';
|
|
13
|
-
import { formatAssertionChecklist } from './utils.mjs';
|
|
14
|
-
|
|
15
|
-
export const executeLiveNode = {
|
|
16
|
-
name: 'execute_live',
|
|
17
|
-
skills: [SKILLS.BROWSER, SKILLS.MEMORY],
|
|
18
|
-
timeout: 600000,
|
|
19
|
-
|
|
20
|
-
prompt: (state) => {
|
|
21
|
-
const ctx = state.context;
|
|
22
|
-
const contextInfo = ctx ? `
|
|
23
|
-
Domain Knowledge & Environment:
|
|
24
|
-
${ctx.global || ''}
|
|
25
|
-
${ctx.pathBased ? `Test-Specific Info:\n${ctx.pathBased}\n` : ''}
|
|
26
|
-
${ctx.env ? `Environment Config:\n${JSON.stringify(ctx.env, null, 2)}\n` : ''}
|
|
27
|
-
---
|
|
28
|
-
` : '';
|
|
29
|
-
|
|
30
|
-
const assertionChecklist = formatAssertionChecklist(state.preflight?.assertions);
|
|
31
|
-
|
|
32
|
-
return `⚠️ CRITICAL: At the END, output ONLY the JSON object. NO explanations after the JSON.
|
|
33
|
-
|
|
34
|
-
🎯 YOUR GOAL: Execute the test steps and collect evidence for script generation.
|
|
35
|
-
You don't need perfect verification - just capture the key actions and results.
|
|
36
|
-
The next node will generate the actual test script from your execution.
|
|
37
|
-
|
|
38
|
-
${contextInfo}
|
|
39
|
-
${state.testSpec}
|
|
40
|
-
${assertionChecklist ? `
|
|
41
|
-
═══════════════════════════════════════════════════
|
|
42
|
-
🎯 ASSERTION CHECKLIST (MANDATORY - from test spec)
|
|
43
|
-
You MUST include ALL of these in your 'assertions' array.
|
|
44
|
-
Report each as passed: true or passed: false with evidence.
|
|
45
|
-
DO NOT skip any. DO NOT add extras.
|
|
46
|
-
|
|
47
|
-
${assertionChecklist}
|
|
48
|
-
═══════════════════════════════════════════════════
|
|
49
|
-
` : ''}
|
|
50
|
-
⚠️ CRITICAL RULES (STRICT ENFORCEMENT):
|
|
51
|
-
1. DO NOT get stuck in read loops - if a snapshot is large, move on
|
|
52
|
-
2. DO NOT over-analyze - just execute the steps
|
|
53
|
-
3. **As soon as you complete the test → IMMEDIATELY return JSON**
|
|
54
|
-
4. **NO screenshots required** - just execute and return JSON
|
|
55
|
-
5. **If test is done, STOP - don't try to be perfect**
|
|
56
|
-
6. **USE VALUES FROM THE TEST SPEC** - if the spec provides specific values, use them exactly. Do NOT replace them with random data.
|
|
57
|
-
7. **USE UNIQUE DATA ONLY when CREATING new resources** (e.g., sign-up forms, new accounts) to avoid "already taken" conflicts:
|
|
58
|
-
- For NEW emails (not provided in test spec): use random digits like "test84729@example.com"
|
|
59
|
-
- For NEW names (not provided in test spec): append random digits like "John84729"
|
|
60
|
-
- This does NOT apply to login credentials or test data explicitly provided in the spec
|
|
61
|
-
|
|
62
|
-
WHEN TO STOP (MANDATORY):
|
|
63
|
-
✓ You've completed the test steps
|
|
64
|
-
✓ Test outcome is visible (even briefly)
|
|
65
|
-
→ **RETURN JSON IMMEDIATELY - DO NOT make any more tool calls**
|
|
66
|
-
|
|
67
|
-
DO NOT:
|
|
68
|
-
- Navigate to the same URL multiple times
|
|
69
|
-
- Use browser_run_code or browser_evaluate
|
|
70
|
-
- Read large snapshots repeatedly (max 2 snapshots per page)
|
|
71
|
-
- Take screenshots (optional, skip if slowing you down)
|
|
72
|
-
- Try to verify every single detail - focus on the MAIN outcome
|
|
73
|
-
- Click/scroll/interact after seeing the expected result
|
|
74
|
-
- Spend more than 2 minutes on any single page
|
|
75
|
-
- Try to click elements that aren't immediately visible
|
|
76
|
-
|
|
77
|
-
📋 BEFORE EXECUTING (MEMORY-AWARE START):
|
|
78
|
-
If the "Domain Knowledge" section above contains a "### Known Pages on This Site"
|
|
79
|
-
block with "expected fingerprint: zibby-..." entries for the URL you're about
|
|
80
|
-
to land on, do this ONCE on first navigation:
|
|
81
|
-
|
|
82
|
-
1. Navigate to the page.
|
|
83
|
-
2. Call browser_snapshot just once to read the live DOM's stableIds.
|
|
84
|
-
3. Count how many of the expected fingerprint stableIds are present.
|
|
85
|
-
4. If ≥ 85% are present → MATCH: trust the cached selectors directly,
|
|
86
|
-
no further exploration on this page is needed.
|
|
87
|
-
5. If < 85% are present → MISMATCH: the page has drifted, ignore the
|
|
88
|
-
cached selectors and rediscover from the live snapshot.
|
|
89
|
-
|
|
90
|
-
This costs ONE snapshot per page in exchange for skipping the rest. Don't
|
|
91
|
-
skip this step on pages that have an expected fingerprint — it's the
|
|
92
|
-
mechanism that makes repeat runs cheap.
|
|
93
|
-
|
|
94
|
-
EXECUTION SEQUENCE (MANDATORY - FOLLOW STRICTLY):
|
|
95
|
-
1. Execute the test steps efficiently (navigate, fill, click)
|
|
96
|
-
- Max 10-15 actions total
|
|
97
|
-
- If stuck, move on to next step
|
|
98
|
-
2. Quick verification - check if main result is visible
|
|
99
|
-
- **If you see expected result → IMMEDIATELY return JSON**
|
|
100
|
-
- Don't try to make it perfect - good enough is enough
|
|
101
|
-
3. **RETURN JSON AND STOP COMPLETELY**
|
|
102
|
-
- Format: { "success": true, "steps": ["step 1", ...], "browserClosed": true, "actions": [...] }
|
|
103
|
-
- MUST include: "success" (boolean), "steps" (array), "browserClosed" (boolean)
|
|
104
|
-
- Keep JSON CONCISE - short descriptions, no excessive detail
|
|
105
|
-
- After the closing brace }, DO NOT write ANYTHING
|
|
106
|
-
- NO commentary, NO explanations, NO additional text
|
|
107
|
-
- NO second JSON object
|
|
108
|
-
- Just the JSON, then STOP
|
|
109
|
-
|
|
110
|
-
IMPORTANT for 'actions' array (STRICT 1:1 MAPPING):
|
|
111
|
-
- Each entry MUST match EXACTLY ONE browser tool call.
|
|
112
|
-
- DO NOT group multiple tool calls into one action.
|
|
113
|
-
- DO NOT combine multiple 'fill' calls into one action.
|
|
114
|
-
- If you call browser_type 3 times for 3 fields, you MUST have 3 actions in the array.
|
|
115
|
-
- Include actual values/URLs in descriptions.
|
|
116
|
-
- Keep descriptions SHORT (5-10 words max).
|
|
117
|
-
- **'committed' field (REQUIRED on actions you DELIBERATELY chose):**
|
|
118
|
-
set committed: true when this action was your CHOSEN attempt for an
|
|
119
|
-
intent (the click/fill you actually wanted, regardless of outcome).
|
|
120
|
-
set committed: false (or omit) for exploratory probes you tried while
|
|
121
|
-
searching for the right element. Only committed actions feed the
|
|
122
|
-
negative cache, so probes don't pollute future runs' Avoid lists.
|
|
123
|
-
Example: tried selector A (probe, failed) → tried selector B
|
|
124
|
-
(deliberate, failed) → tried selector C (deliberate, succeeded). Mark
|
|
125
|
-
B and C as committed: true; A as committed: false.
|
|
126
|
-
|
|
127
|
-
IMPORTANT for 'assertions' array (USE THE CHECKLIST ABOVE):
|
|
128
|
-
- Your assertions array MUST match the ASSERTION CHECKLIST exactly - one entry per item
|
|
129
|
-
- If you verified it and it passed → "passed": true
|
|
130
|
-
- If you could NOT verify it or it wasn't found → "passed": false with evidence of what you saw instead
|
|
131
|
-
- Each assertion MUST include 'verifiedAfterAction' (0-based action index after which you checked)
|
|
132
|
-
- Format: {"description": "...", "passed": true/false, "verifiedAfterAction": N, "evidence": "..."}
|
|
133
|
-
|
|
134
|
-
🔍 CRITICAL: CAPTURE ROBUST SELECTORS (for script generation)
|
|
135
|
-
For EACH action, capture multiple selector strategies in priority order:
|
|
136
|
-
|
|
137
|
-
1. **Role + Name** (Most Robust - Accessibility-first):
|
|
138
|
-
- Role: button/textbox/link/etc
|
|
139
|
-
- Name: visible text or aria-label
|
|
140
|
-
Example: {"role": "button", "name": "Login"}
|
|
141
|
-
|
|
142
|
-
2. **Stable Attributes** (Good Stability):
|
|
143
|
-
- name, type, placeholder, aria-label
|
|
144
|
-
Example: {"attributes": {"name": "username", "type": "text", "placeholder": "Enter username"}}
|
|
145
|
-
|
|
146
|
-
3. **Partial Match** (For Dynamic Elements):
|
|
147
|
-
- Use starts-with for dynamic IDs/classes
|
|
148
|
-
Example: {"partialMatch": {"id": "^user-", "class": "^btn-"}}
|
|
149
|
-
|
|
150
|
-
4. **Structural** (Fallback):
|
|
151
|
-
- Tag + position relative to stable landmark
|
|
152
|
-
Example: {"structure": "form input[type='text']:nth-of-type(1)"}
|
|
153
|
-
|
|
154
|
-
Format for actions with selectors:
|
|
155
|
-
{
|
|
156
|
-
"description": "Fill username field with 'joe'",
|
|
157
|
-
"reasoning": "Need to authenticate user",
|
|
158
|
-
"type": "fill",
|
|
159
|
-
"selectors": {
|
|
160
|
-
"role": {"role": "textbox", "name": "Username"},
|
|
161
|
-
"attributes": {"name": "username", "type": "text", "placeholder": "请输入账号"},
|
|
162
|
-
"structure": "form input[type='text']:first-of-type"
|
|
163
|
-
},
|
|
164
|
-
"value": "joe"
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
IMPORTANT for 'evidenceScreenshots' (array) - OPTIONAL:
|
|
168
|
-
- Screenshots are OPTIONAL - only take if helpful
|
|
169
|
-
- If you take screenshots, use descriptive filenames
|
|
170
|
-
- Filename pattern: "{step-number}-{action-or-state}.png"
|
|
171
|
-
- Keep it minimal - test execution is more important than documentation
|
|
172
|
-
`;
|
|
173
|
-
},
|
|
174
|
-
|
|
175
|
-
outputSchema: z.object({
|
|
176
|
-
success: z.boolean()
|
|
177
|
-
.describe('Whether the test execution completed successfully'),
|
|
178
|
-
|
|
179
|
-
steps: z.array(z.string())
|
|
180
|
-
.describe('Array of test steps executed'),
|
|
181
|
-
|
|
182
|
-
finalUrl: z.string()
|
|
183
|
-
.nullish()
|
|
184
|
-
.describe('Final URL after test execution'),
|
|
185
|
-
|
|
186
|
-
actions: z.array(z.object({
|
|
187
|
-
type: z.string()
|
|
188
|
-
.describe('Action type: navigate, click, fill, type, select, keypress, hover, drag'),
|
|
189
|
-
description: z.string()
|
|
190
|
-
.describe('Human-readable description of the action'),
|
|
191
|
-
reasoning: z.string().nullish()
|
|
192
|
-
.describe('Why this action was performed'),
|
|
193
|
-
committed: z.boolean().nullish()
|
|
194
|
-
.describe('true when this was a deliberate chosen attempt for an intent (feeds negative cache on failure); false/omit for exploratory probes'),
|
|
195
|
-
status: z.enum(['success', 'failed']).nullish()
|
|
196
|
-
.describe('Outcome of the action — set "failed" if the tool call errored or the post-condition was not met'),
|
|
197
|
-
error: z.string().nullish()
|
|
198
|
-
.describe('Error message when status=failed'),
|
|
199
|
-
selectors: z.object({
|
|
200
|
-
role: z.object({
|
|
201
|
-
role: z.string().describe('ARIA role (e.g. button, link, textbox, generic)'),
|
|
202
|
-
name: z.string().nullish().describe('Accessible name of the element')
|
|
203
|
-
}).nullish().describe('Role-based selector for fallback matching')
|
|
204
|
-
}).nullish()
|
|
205
|
-
.describe('Element selectors captured during the action'),
|
|
206
|
-
value: z.string().nullish()
|
|
207
|
-
.describe('Value entered for fill/type actions')
|
|
208
|
-
}))
|
|
209
|
-
.nullish()
|
|
210
|
-
.describe('Detailed array of actions performed with descriptions and reasoning'),
|
|
211
|
-
|
|
212
|
-
assertions: z.array(z.object({
|
|
213
|
-
description: z.string()
|
|
214
|
-
.describe('What was verified'),
|
|
215
|
-
passed: z.boolean()
|
|
216
|
-
.describe('Whether the assertion passed'),
|
|
217
|
-
verifiedAfterAction: z.number()
|
|
218
|
-
.describe('Index of the action after which this was verified (0-based, matches actions array index) - REQUIRED'),
|
|
219
|
-
evidence: z.string()
|
|
220
|
-
.nullish()
|
|
221
|
-
.describe('Brief evidence of what was observed')
|
|
222
|
-
}))
|
|
223
|
-
.nullish()
|
|
224
|
-
.describe('Array of assertions made during test'),
|
|
225
|
-
|
|
226
|
-
waits: z.array(z.object({
|
|
227
|
-
description: z.string().describe('What the wait is for'),
|
|
228
|
-
duration: z.number().nullish().describe('Wait duration in milliseconds'),
|
|
229
|
-
condition: z.string().nullish().describe('Wait condition expression')
|
|
230
|
-
}))
|
|
231
|
-
.nullish()
|
|
232
|
-
.describe('Array of waits needed for proper test execution'),
|
|
233
|
-
|
|
234
|
-
evidenceScreenshots: z.array(z.object({
|
|
235
|
-
filename: z.string()
|
|
236
|
-
.describe('Descriptive filename pattern: {step-number}-{action-or-state}.png'),
|
|
237
|
-
|
|
238
|
-
description: z.string()
|
|
239
|
-
.describe('What the screenshot shows and why it is evidence'),
|
|
240
|
-
|
|
241
|
-
verdict: z.enum(['pass', 'fail', 'info'])
|
|
242
|
-
.describe('Test verdict: pass/fail for validation points, info for checkpoints')
|
|
243
|
-
}))
|
|
244
|
-
.nullish()
|
|
245
|
-
.describe('Array of screenshots taken at key validation points throughout the test'),
|
|
246
|
-
|
|
247
|
-
browserClosed: z.boolean()
|
|
248
|
-
.describe('Whether the browser was properly closed (should always be true)'),
|
|
249
|
-
|
|
250
|
-
notes: z.string()
|
|
251
|
-
.nullish()
|
|
252
|
-
.describe('Additional notes or observations. REQUIRED when success=false to explain why test failed or could not execute')
|
|
253
|
-
})
|
|
254
|
-
};
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { z, SKILLS } from '@zibby/core';
|
|
2
|
-
import { formatRecordedActions, formatAssertionsWithResults, loadRecordedActions, detectLoginPattern, formatSetupHint } from './utils.mjs';
|
|
3
|
-
|
|
4
|
-
const GenerateScriptOutputSchema = z.object({
|
|
5
|
-
success: z.boolean(),
|
|
6
|
-
scriptPath: z.string(),
|
|
7
|
-
method: z.string()
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
export const generateScriptNode = {
|
|
11
|
-
name: 'generate_script',
|
|
12
|
-
skills: [SKILLS.MEMORY],
|
|
13
|
-
outputSchema: GenerateScriptOutputSchema,
|
|
14
|
-
timeout: 1200000,
|
|
15
|
-
|
|
16
|
-
prompt: (state) => {
|
|
17
|
-
const exec = state.execute_live || {};
|
|
18
|
-
const preflight = state.preflight || {};
|
|
19
|
-
|
|
20
|
-
const actionsBlock = formatRecordedActions(state.sessionPath, exec.actions);
|
|
21
|
-
const assertionsBlock = formatAssertionsWithResults(
|
|
22
|
-
preflight.assertions,
|
|
23
|
-
exec.assertions,
|
|
24
|
-
exec.notes,
|
|
25
|
-
exec.finalUrl
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
const recorded = loadRecordedActions(state.sessionPath);
|
|
29
|
-
const setupHint = formatSetupHint(detectLoginPattern(recorded));
|
|
30
|
-
|
|
31
|
-
// `state.outputPath` is computed from `state.specPath` by the
|
|
32
|
-
// framework. For `zibby test <spec>` runs it's set. For
|
|
33
|
-
// `workflow trigger` runs there's no spec file on disk, so
|
|
34
|
-
// outputPath comes back undefined and the prompt rendered
|
|
35
|
-
// "Generate and verify Playwright test at undefined" — which
|
|
36
|
-
// caused the LLM to literally write to a file named `undefined`.
|
|
37
|
-
// Fall back to a path under the session dir so the test always
|
|
38
|
-
// lands somewhere sensible.
|
|
39
|
-
const outputPath = state.outputPath
|
|
40
|
-
|| (state.sessionPath ? `${state.sessionPath}/generate_script/generated-test.spec.js` : 'tests/generated-test.spec.js');
|
|
41
|
-
|
|
42
|
-
return `Generate and verify Playwright test at ${outputPath}
|
|
43
|
-
|
|
44
|
-
Test Spec:
|
|
45
|
-
${state.testSpec}
|
|
46
|
-
|
|
47
|
-
Live Execution Summary:
|
|
48
|
-
- Success: ${exec.success}
|
|
49
|
-
- Steps: ${JSON.stringify(exec.steps)}
|
|
50
|
-
- Final URL: ${exec.finalUrl || 'unknown'}
|
|
51
|
-
${actionsBlock}
|
|
52
|
-
${assertionsBlock}
|
|
53
|
-
${setupHint}
|
|
54
|
-
IMPORTS AND PATTERN:
|
|
55
|
-
\`\`\`javascript
|
|
56
|
-
import { test, expect } from '@playwright/test';
|
|
57
|
-
import { StableIdRuntime } from '@zibby/core';
|
|
58
|
-
|
|
59
|
-
async function clickSafe(page, stableId, fallback) {
|
|
60
|
-
try { await StableIdRuntime.clickWithRetry(page, stableId); }
|
|
61
|
-
catch { await fallback.click(); }
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
test('Test Name', async ({ page }) => {
|
|
65
|
-
await page.goto('https://...');
|
|
66
|
-
await StableIdRuntime.injectStableIds(page);
|
|
67
|
-
// Elements WITH stable IDs + fallback — use clickSafe:
|
|
68
|
-
await clickSafe(page, 'zibby-xxxxx', page.getByRole('button', { name: '...' }));
|
|
69
|
-
await StableIdRuntime.fillWithRetry(page, 'zibby-xxxxx', 'value');
|
|
70
|
-
// Elements WITHOUT stable IDs (NO_STABLE_ID) — use native Playwright selectors:
|
|
71
|
-
await page.getByText('visible text').click();
|
|
72
|
-
await page.getByRole('button', { name: 'Submit' }).click();
|
|
73
|
-
await page.getByPlaceholder('placeholder text').fill('value');
|
|
74
|
-
await expect(page).toHaveURL(/expected-url/);
|
|
75
|
-
});
|
|
76
|
-
\`\`\`
|
|
77
|
-
|
|
78
|
-
RULES:
|
|
79
|
-
1. First navigate → page.goto(), skip subsequent navigates
|
|
80
|
-
2. After goto, call StableIdRuntime.injectStableIds(page)
|
|
81
|
-
3. Selector priority:
|
|
82
|
-
a. If memory/insights flag a stableId as unreliable → use the fallback selector instead
|
|
83
|
-
b. If action is marked [DUPLICATE_STABLE_ID] → always use the provided fallback
|
|
84
|
-
c. Cross-reference stableIds against memory "Reliable Selectors" and "Flaky Selectors" — prefer proven selectors, avoid flaky ones
|
|
85
|
-
d. Otherwise use EXACT stable IDs from recorded actions
|
|
86
|
-
4. For [NO_STABLE_ID] actions, use the fallback selector (getByText, getByRole, getByPlaceholder)
|
|
87
|
-
5. Skip duplicate consecutive clicks on same stableId
|
|
88
|
-
6. No comments in generated code
|
|
89
|
-
7. Implement ALL assertions from the list above
|
|
90
|
-
8. If an assertion fails after retries, comment it out with a TODO (don't delete it)
|
|
91
|
-
9. Selector failure handling:
|
|
92
|
-
- When a stableId fails and you switch to a fallback, IMMEDIATELY call memory_save_insight (category: selector_tip) with which stableId failed, which fallback worked, and the page URL
|
|
93
|
-
- Note the specific Playwright locator strategy that succeeded
|
|
94
|
-
10. Navigation order: always complete setup/login FIRST from the base URL, then navigate to the target page. Never go to a deep URL before setup is done.
|
|
95
|
-
11. The generated test runs in a FRESH browser with no prior state. Even if the live execution skipped setup steps, the test must include them. Check memory insights for any required setup.
|
|
96
|
-
|
|
97
|
-
WORKFLOW:
|
|
98
|
-
1. Study the codebase FIRST — search tests/ for existing helpers, fixtures, and shared setup files. Read them. Reuse what exists. Do NOT create files that duplicate existing ones.
|
|
99
|
-
2. Write test to ${outputPath} (after the run, a copy is mirrored under ${state.sessionPath}/generate_script/ for Studio — you may also write directly there if you prefer)
|
|
100
|
-
3. Verify syntax: run node --check on the file. If it fails, fix and re-check before proceeding.
|
|
101
|
-
4. Run: PLAYWRIGHT_HEADLESS=1 npx playwright test ${outputPath} --reporter=line --timeout=60000
|
|
102
|
-
5. If fails: try selectors in order — (a) getByRole (b) getByText (c) getByTestId (d) add waitForSelector. Never retry the same selector twice.
|
|
103
|
-
6. MAX 2 ATTEMPTS then STOP
|
|
104
|
-
|
|
105
|
-
The test runs in: ${state.cwd || 'project root'}
|
|
106
|
-
`;
|
|
107
|
-
},
|
|
108
|
-
};
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Preflight Node
|
|
3
|
-
*
|
|
4
|
-
* Pattern: Prompt-only node (no tools)
|
|
5
|
-
* Purpose: Analyze test spec and extract title + structured assertion checklist
|
|
6
|
-
*
|
|
7
|
-
* This runs before execution to define:
|
|
8
|
-
* - A concise test title
|
|
9
|
-
* - The complete list of assertions that must be verified
|
|
10
|
-
*
|
|
11
|
-
* Downstream nodes receive this as their contract:
|
|
12
|
-
* - execute_live: must report passed/failed for each assertion
|
|
13
|
-
* - generate_script: must implement each assertion in the test
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { z, invokeAgent } from '@zibby/core';
|
|
17
|
-
import { writeFileSync } from 'fs';
|
|
18
|
-
import { join } from 'path';
|
|
19
|
-
|
|
20
|
-
const AssertionSchema = z.object({
|
|
21
|
-
description: z.string().describe('What to verify (e.g., "User is redirected to dashboard")'),
|
|
22
|
-
expected: z.string().describe('What the expected outcome looks like (e.g., "URL contains /dashboard")')
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
const PreflightOutputSchema = z.object({
|
|
26
|
-
title: z.string().describe('Concise test title (5-10 words, action-oriented). Prefix with ticket ID if found.'),
|
|
27
|
-
assertions: z.array(AssertionSchema).describe('Every expected result from the spec as a verifiable assertion')
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
// Detect "no usable spec" before invoking the LLM. Catches:
|
|
31
|
-
// - state.testSpec is undefined (workflow run with no input)
|
|
32
|
-
// - empty string / whitespace only
|
|
33
|
-
// - the literal string "undefined" (some upstream paths stringify
|
|
34
|
-
// undefined into state, e.g. when --param isn't passed)
|
|
35
|
-
function isMissingSpec(spec) {
|
|
36
|
-
if (spec == null) return true;
|
|
37
|
-
const s = String(spec).trim();
|
|
38
|
-
return s === '' || s.toLowerCase() === 'undefined';
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const PROMPT = (testSpec) => `Analyze this test specification and extract:
|
|
42
|
-
1. A concise test title (5-10 words, action-oriented). If you find a ticket ID (e.g., PROJ-123, ACME-456), prefix the title with it.
|
|
43
|
-
2. Every expected result as a verifiable assertion. Each assertion must be something the browser can check after execution.
|
|
44
|
-
|
|
45
|
-
Test Spec:
|
|
46
|
-
${testSpec}
|
|
47
|
-
|
|
48
|
-
IMPORTANT: You MUST create ONE assertion for EACH expected result in the spec. Do NOT skip any.
|
|
49
|
-
|
|
50
|
-
Return ONLY this JSON:
|
|
51
|
-
{ "title": "TICKET-ID: Short action title", "assertions": [ { "description": "...", "expected": "..." }, ... ] }`;
|
|
52
|
-
|
|
53
|
-
export const preflightNode = {
|
|
54
|
-
name: 'preflight',
|
|
55
|
-
outputSchema: PreflightOutputSchema,
|
|
56
|
-
|
|
57
|
-
async execute(state) {
|
|
58
|
-
// Early exit BEFORE the LLM call when there's no spec to analyze.
|
|
59
|
-
// Without this guard the node fires the LLM, gets back
|
|
60
|
-
// `{title: "No test specification provided", assertions: []}`, and
|
|
61
|
-
// the graph's conditional edge then skips execute_live — but we've
|
|
62
|
-
// still paid for one preflight LLM call we didn't need to make.
|
|
63
|
-
// Returning empty assertions here triggers the same skip-to-END
|
|
64
|
-
// path in graph.mjs's preflight conditional edge.
|
|
65
|
-
if (isMissingSpec(state.testSpec)) {
|
|
66
|
-
console.log('⚠️ No test spec provided — skipping browser run.');
|
|
67
|
-
console.log(' Pass a spec via: zibby test "<inline spec>" or zibby test path/to/spec.txt');
|
|
68
|
-
return {
|
|
69
|
-
title: 'No test specification provided',
|
|
70
|
-
assertions: [],
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const result = await invokeAgent(PROMPT(state.testSpec), {
|
|
75
|
-
state,
|
|
76
|
-
model: state.model || 'auto',
|
|
77
|
-
schema: PreflightOutputSchema,
|
|
78
|
-
});
|
|
79
|
-
return result?.structured || result;
|
|
80
|
-
},
|
|
81
|
-
|
|
82
|
-
async onComplete(state, result) {
|
|
83
|
-
const sessionPath = state.sessionPath || process.env.ZIBBY_SESSION_PATH;
|
|
84
|
-
if (sessionPath && result.title) {
|
|
85
|
-
try {
|
|
86
|
-
writeFileSync(join(sessionPath, 'title.txt'), result.title, 'utf-8');
|
|
87
|
-
console.log(`Saved title: "${result.title}"`);
|
|
88
|
-
} catch (error) {
|
|
89
|
-
console.warn(`⚠️ Could not save title.txt: ${error.message}`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return result;
|
|
93
|
-
},
|
|
94
|
-
};
|