@zibby/core 0.4.5 → 0.5.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.
Files changed (81) hide show
  1. package/dist/index.js +147 -150
  2. package/dist/package.json +1 -8
  3. package/dist/register-built-in-strategies.js +32 -32
  4. package/dist/strategies/claude-strategy.js +2 -2
  5. package/dist/strategies/index.js +45 -45
  6. package/dist/utils/run-index-post-cli.js +1 -4
  7. package/package.json +1 -8
  8. package/dist/templates/browser-test-automation/README.md +0 -136
  9. package/dist/templates/browser-test-automation/chat.mjs +0 -36
  10. package/dist/templates/browser-test-automation/graph.mjs +0 -80
  11. package/dist/templates/browser-test-automation/nodes/cache-replay.mjs +0 -213
  12. package/dist/templates/browser-test-automation/nodes/execute-live.mjs +0 -254
  13. package/dist/templates/browser-test-automation/nodes/generate-script.mjs +0 -108
  14. package/dist/templates/browser-test-automation/nodes/index.mjs +0 -4
  15. package/dist/templates/browser-test-automation/nodes/preflight.mjs +0 -94
  16. package/dist/templates/browser-test-automation/nodes/utils.mjs +0 -297
  17. package/dist/templates/browser-test-automation/pipeline-ids.js +0 -12
  18. package/dist/templates/browser-test-automation/result-handler.mjs +0 -327
  19. package/dist/templates/browser-test-automation/run-index.mjs +0 -420
  20. package/dist/templates/browser-test-automation/run_test.json +0 -358
  21. package/dist/templates/browser-test-automation/state.js +0 -61
  22. package/dist/templates/code-analysis/README.md +0 -60
  23. package/dist/templates/code-analysis/graph.js +0 -72
  24. package/dist/templates/code-analysis/graph.mjs +0 -33
  25. package/dist/templates/code-analysis/index.js +0 -18
  26. package/dist/templates/code-analysis/nodes/analyze-ticket-node.js +0 -204
  27. package/dist/templates/code-analysis/nodes/create-pr-node.js +0 -175
  28. package/dist/templates/code-analysis/nodes/finalize-node.js +0 -118
  29. package/dist/templates/code-analysis/nodes/generate-code-node.js +0 -425
  30. package/dist/templates/code-analysis/nodes/generate-test-cases-node.js +0 -376
  31. package/dist/templates/code-analysis/nodes/services/prMetaService.js +0 -86
  32. package/dist/templates/code-analysis/nodes/setup-node.js +0 -142
  33. package/dist/templates/code-analysis/prompts/analyze-ticket.md +0 -181
  34. package/dist/templates/code-analysis/prompts/generate-code.md +0 -33
  35. package/dist/templates/code-analysis/prompts/generate-test-cases.md +0 -110
  36. package/dist/templates/code-analysis/state.js +0 -48
  37. package/dist/templates/generate-test-cases/README.md +0 -72
  38. package/dist/templates/generate-test-cases/graph.mjs +0 -46
  39. package/dist/templates/generate-test-cases/nodes/generate-test-cases-node.js +0 -381
  40. package/dist/templates/generate-test-cases/nodes/setup-node.js +0 -142
  41. package/dist/templates/generate-test-cases/state.js +0 -54
  42. package/dist/templates/global-setup.js +0 -56
  43. package/dist/templates/index.js +0 -147
  44. package/dist/templates/register-nodes.js +0 -24
  45. package/templates/browser-test-automation/README.md +0 -136
  46. package/templates/browser-test-automation/chat.mjs +0 -36
  47. package/templates/browser-test-automation/graph.mjs +0 -80
  48. package/templates/browser-test-automation/nodes/cache-replay.mjs +0 -213
  49. package/templates/browser-test-automation/nodes/execute-live.mjs +0 -254
  50. package/templates/browser-test-automation/nodes/generate-script.mjs +0 -108
  51. package/templates/browser-test-automation/nodes/index.mjs +0 -4
  52. package/templates/browser-test-automation/nodes/preflight.mjs +0 -94
  53. package/templates/browser-test-automation/nodes/utils.mjs +0 -297
  54. package/templates/browser-test-automation/pipeline-ids.js +0 -12
  55. package/templates/browser-test-automation/result-handler.mjs +0 -327
  56. package/templates/browser-test-automation/run-index.mjs +0 -420
  57. package/templates/browser-test-automation/run_test.json +0 -358
  58. package/templates/browser-test-automation/state.js +0 -61
  59. package/templates/code-analysis/README.md +0 -60
  60. package/templates/code-analysis/graph.js +0 -72
  61. package/templates/code-analysis/graph.mjs +0 -33
  62. package/templates/code-analysis/index.js +0 -18
  63. package/templates/code-analysis/nodes/analyze-ticket-node.js +0 -204
  64. package/templates/code-analysis/nodes/create-pr-node.js +0 -175
  65. package/templates/code-analysis/nodes/finalize-node.js +0 -118
  66. package/templates/code-analysis/nodes/generate-code-node.js +0 -425
  67. package/templates/code-analysis/nodes/generate-test-cases-node.js +0 -376
  68. package/templates/code-analysis/nodes/services/prMetaService.js +0 -86
  69. package/templates/code-analysis/nodes/setup-node.js +0 -142
  70. package/templates/code-analysis/prompts/analyze-ticket.md +0 -181
  71. package/templates/code-analysis/prompts/generate-code.md +0 -33
  72. package/templates/code-analysis/prompts/generate-test-cases.md +0 -110
  73. package/templates/code-analysis/state.js +0 -48
  74. package/templates/generate-test-cases/README.md +0 -72
  75. package/templates/generate-test-cases/graph.mjs +0 -46
  76. package/templates/generate-test-cases/nodes/generate-test-cases-node.js +0 -381
  77. package/templates/generate-test-cases/nodes/setup-node.js +0 -142
  78. package/templates/generate-test-cases/state.js +0 -54
  79. package/templates/global-setup.js +0 -56
  80. package/templates/index.js +0 -147
  81. 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,4 +0,0 @@
1
- export { preflightNode } from './preflight.mjs';
2
- export { cacheReplayNode } from './cache-replay.mjs';
3
- export { executeLiveNode } from './execute-live.mjs';
4
- export { generateScriptNode } from './generate-script.mjs';
@@ -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
- };