@zibby/core 0.1.48 → 0.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.
Files changed (41) hide show
  1. package/dist/index.js +100 -100
  2. package/dist/package.json +2 -2
  3. package/dist/register-built-in-strategies.js +52 -52
  4. package/dist/strategies/assistant-strategy.js +1 -1
  5. package/dist/strategies/claude-strategy.js +3 -3
  6. package/dist/strategies/codex-strategy.js +3 -3
  7. package/dist/strategies/cursor-strategy.js +30 -30
  8. package/dist/strategies/gemini-strategy.js +13 -13
  9. package/dist/strategies/index.js +57 -57
  10. package/dist/templates/browser-test-automation/README.md +136 -0
  11. package/dist/templates/browser-test-automation/chat.mjs +36 -0
  12. package/dist/templates/browser-test-automation/graph.mjs +54 -0
  13. package/dist/templates/browser-test-automation/nodes/execute-live.mjs +222 -0
  14. package/dist/templates/browser-test-automation/nodes/generate-script.mjs +97 -0
  15. package/dist/templates/browser-test-automation/nodes/index.mjs +3 -0
  16. package/dist/templates/browser-test-automation/nodes/preflight.mjs +59 -0
  17. package/dist/templates/browser-test-automation/nodes/utils.mjs +297 -0
  18. package/dist/templates/browser-test-automation/pipeline-ids.js +12 -0
  19. package/dist/templates/browser-test-automation/result-handler.mjs +327 -0
  20. package/dist/templates/browser-test-automation/run-index.mjs +418 -0
  21. package/dist/templates/browser-test-automation/run_test.json +358 -0
  22. package/dist/templates/code-analysis/graph.js +72 -0
  23. package/dist/templates/code-analysis/index.js +18 -0
  24. package/dist/templates/code-analysis/nodes/analyze-ticket-node.js +204 -0
  25. package/dist/templates/code-analysis/nodes/create-pr-node.js +175 -0
  26. package/dist/templates/code-analysis/nodes/finalize-node.js +118 -0
  27. package/dist/templates/code-analysis/nodes/generate-code-node.js +425 -0
  28. package/dist/templates/code-analysis/nodes/generate-test-cases-node.js +376 -0
  29. package/dist/templates/code-analysis/nodes/services/prMetaService.js +86 -0
  30. package/dist/templates/code-analysis/nodes/setup-node.js +142 -0
  31. package/dist/templates/code-analysis/prompts/analyze-ticket.md +181 -0
  32. package/dist/templates/code-analysis/prompts/generate-code.md +33 -0
  33. package/dist/templates/code-analysis/prompts/generate-test-cases.md +110 -0
  34. package/dist/templates/code-analysis/state.js +40 -0
  35. package/dist/templates/code-implementation/graph.js +35 -0
  36. package/dist/templates/code-implementation/index.js +7 -0
  37. package/dist/templates/code-implementation/state.js +14 -0
  38. package/dist/templates/global-setup.js +56 -0
  39. package/dist/templates/index.js +94 -0
  40. package/dist/templates/register-nodes.js +24 -0
  41. package/package.json +2 -2
@@ -0,0 +1,136 @@
1
+ # Browser Test Automation Workflow
2
+
3
+ This is YOUR workflow graph. You can customize it however you want!
4
+
5
+ Works with **Claude** or **Cursor** agents (configured in `.zibby.config.mjs`).
6
+
7
+ ## Default Flow
8
+
9
+ ```
10
+ preflight → execute_live → generate_script
11
+ ```
12
+
13
+ The workflow generates a test title, executes the test live in a **browser** with AI assistance, and generates a Playwright script with stable selectors.
14
+
15
+ ## Customization
16
+
17
+ ### Add Custom Nodes
18
+
19
+ Create a new file in `nodes/`:
20
+
21
+ ```javascript
22
+ // nodes/send-slack.js
23
+ export const sendSlackNode = {
24
+ name: 'send_slack',
25
+ agent: { type: 'openai', model: 'gpt-4o-mini' },
26
+ prompt: (state) => `Send Slack notification...`,
27
+ outputSchema: { success: { type: 'boolean', required: true } }
28
+ };
29
+ ```
30
+
31
+ Then add it to your graph in `graph.js`:
32
+
33
+ ```javascript
34
+ import { sendSlackNode } from './nodes/send-slack.js';
35
+
36
+ buildGraph() {
37
+ const graph = new WorkflowGraph();
38
+ // ... existing nodes
39
+ graph.addNode('send_slack', sendSlackNode);
40
+ graph.addEdge('verify_script', 'send_slack');
41
+ return graph;
42
+ }
43
+ ```
44
+
45
+ ### Multi-Agent Configuration
46
+
47
+ Each node can use a different LLM:
48
+
49
+ ```javascript
50
+ graph.addNode('generate_title', {
51
+ agent: { type: 'claude', model: 'claude-sonnet-4' },
52
+ prompt: (state) => `Generate title...`
53
+ });
54
+
55
+ graph.addNode('verify_script', {
56
+ agent: { type: 'deepseek', model: 'deepseek-coder' }, // Cheap & fast
57
+ prompt: (state) => `Run test...`
58
+ });
59
+
60
+ graph.addNode('update_jira', {
61
+ agent: { type: 'ollama', model: 'llama3' }, // Local for privacy
62
+ prompt: (state) => `Update Jira...`
63
+ });
64
+ ```
65
+
66
+ ### Skip Nodes
67
+
68
+ Comment out nodes you don't need:
69
+
70
+ ```javascript
71
+ // graph.addNode('verify_script', verifyScriptNode);
72
+ graph.addEdge('generate_script', 'update_jira'); // Skip verification
73
+ ```
74
+
75
+ ### Parallel Execution
76
+
77
+ Run multiple nodes in parallel:
78
+
79
+ ```javascript
80
+ graph.addParallelEdges('verify_script', [
81
+ 'send_slack',
82
+ 'update_jira',
83
+ 'log_datadog'
84
+ ]);
85
+ ```
86
+
87
+ ## Configuration
88
+
89
+ Edit `.zibby.config.mjs` to set your default agent and optional per-node model overrides:
90
+
91
+ ```javascript
92
+ export default {
93
+ agent: {
94
+ cursor: { model: 'auto' }, // or claude: { model: 'auto' }
95
+ strictMode: false,
96
+ },
97
+ models: {
98
+ default: 'auto',
99
+ execute_live: 'auto',
100
+ generate_script: 'auto',
101
+ },
102
+ };
103
+ ```
104
+
105
+ ## Studio / Scripts tab (code discovery)
106
+
107
+ Runs write `generate_script/result.json` with a `scriptPath` (often under your repo `tests/`). After the graph finishes, **`BrowserTestResultHandler.ensureStudioCodegenMirror`** copies that file into the session folder under stable names so tools don’t need Studio running at generation time:
108
+
109
+ | File (under `.zibby/output/sessions/<sessionId>/generate_script/`) | Role |
110
+ |---------------------------------------------------------------------|------|
111
+ | `generated-test.spec.js` | Playwright (`.js`) |
112
+ | `playwright.spec.ts` | Playwright (`.ts` / `.tsx` source) |
113
+ | `test.selenium.py` | Selenium |
114
+
115
+ **Electron Studio** resolves these via `discoverCodegenArtifactsElectron` (after `session/codegen/`).
116
+
117
+ **Web Studio** (`VITE_STUDIO_API_ORIGIN`, e.g. `:3847`) should implement `GET /api/sessions/:id/codegen/playwright` (and `/selenium`) by reading, in order:
118
+
119
+ 1. `sessions/<id>/codegen/` legacy JIT names (`test.spec.ts`, `generated-test.spec.js`, …)
120
+ 2. **`sessions/<id>/generate_script/`** canonical names above
121
+ 3. `scriptPath` from `generate_script/result.json` (resolve relative to session / `cwd` from session meta)
122
+
123
+ ## Documentation
124
+
125
+ - [Full Graph Framework Design](../../docs/GRAPH_FRAMEWORK_DESIGN.md)
126
+ - [Multi-Agent Patterns](../../docs/FRAMEWORK_CONVERSATION_SUMMARY.md)
127
+
128
+ ## Updates
129
+
130
+ To get latest template updates:
131
+
132
+ ```bash
133
+ zibby update-graph --merge
134
+ ```
135
+
136
+ This will merge bug fixes while preserving your customizations.
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Zibby Chat Agent
3
+ *
4
+ * Interactive conversational node that acts as the default entry point
5
+ * when users type `zibby` with no subcommand.
6
+ *
7
+ * This is a plain chat bot — no MCP servers, no middleware, no structured output.
8
+ * Just streamed text conversation with the AI agent.
9
+ *
10
+ * The skill-installer skill injects its promptFragment so the LLM knows which
11
+ * skills are available and can install/uninstall them via natural conversation.
12
+ * Users can customize this file after `zibby init` copies it to .zibby/chat.mjs
13
+ */
14
+
15
+ import { SKILLS } from '@zibby/core';
16
+
17
+ export const CHAT_CONFIG = {
18
+ name: 'zibby_chat',
19
+ skills: [SKILLS.CORE_TOOLS, SKILLS.SKILL_INSTALLER, SKILLS.CHAT_MEMORY, SKILLS.WORKFLOW_BUILDER],
20
+ timeout: 0,
21
+
22
+ systemPrompt: `You are Zibby, a helpful AI assistant. Capabilities come from installed skills.
23
+
24
+ ## How you work
25
+ 1. When you need data, call tools. You can chain up to 5 calls per turn.
26
+ 2. After each tool result, decide: "Would I be embarrassed to give this answer to a coworker?" If yes, call another tool.
27
+ 3. Only respond once you have something genuinely useful.
28
+ 4. Never claim you did something without actually calling the tool.
29
+ 5. After EVERY response, self-evaluate: is the user's goal fully achieved? Is anything still pending or running? If yes, DO NOT ASK — autonomously poll: call wait (you decide how long), then check status, then respond with an update. Repeat until done or the user interrupts.
30
+
31
+ ## How you talk
32
+ - Talk like a teammate in Slack, not a report generator.
33
+ - Summarize and paraphrase. Never copy-paste field values or list raw steps verbatim.
34
+ - Short paragraphs, not numbered lists (unless the user specifically asks for steps).
35
+ - Match the user's tone and energy. Be concise.`,
36
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Test Automation Workflow Graph
3
+ *
4
+ * buildGraph() - define nodes, edges, routing
5
+ * onComplete(result) - post-processing after graph finishes (save artifacts, etc.)
6
+ */
7
+
8
+ import { WorkflowAgent, WorkflowGraph } from '@zibby/core';
9
+ import {
10
+ preflightNode,
11
+ executeLiveNode,
12
+ generateScriptNode,
13
+ } from './nodes/index.mjs';
14
+ import { BrowserTestResultHandler } from './result-handler.mjs';
15
+
16
+ export class BrowserTestAutomationAgent extends WorkflowAgent {
17
+ buildGraph() {
18
+ const graph = new WorkflowGraph();
19
+
20
+ graph.addNode('preflight', preflightNode);
21
+ graph.addNode('execute_live', executeLiveNode);
22
+ graph.addNode('generate_script', generateScriptNode);
23
+
24
+ graph.setEntryPoint('preflight');
25
+ graph.addEdge('preflight', 'execute_live');
26
+
27
+ graph.addConditionalEdges('execute_live', (state) => {
28
+ const result = state.execute_live;
29
+ const hasExecution = (result?.steps?.length > 0) || (result?.actions?.length > 0);
30
+ return hasExecution ? 'generate_script' : 'END';
31
+ });
32
+
33
+ graph.addEdge('generate_script', 'END');
34
+ return graph;
35
+ }
36
+
37
+ async onComplete(result) {
38
+ const cwd = result.state.cwd || process.cwd();
39
+ BrowserTestResultHandler.saveTitle(result, cwd);
40
+ await BrowserTestResultHandler.saveExecutionData(result);
41
+ BrowserTestResultHandler.ensureStudioCodegenMirror(
42
+ result.state?.sessionPath,
43
+ result.state?.cwd || cwd,
44
+ );
45
+
46
+ // Memory end-run hook (if @zibby/memory is installed)
47
+ try {
48
+ const { memoryEndRun, memorySyncPush } = await import('@zibby/memory');
49
+ const sessionId = result.state.sessionPath?.split('/').pop();
50
+ memoryEndRun(cwd, { sessionId, passed: result.success !== false });
51
+ memorySyncPush(cwd);
52
+ } catch { /* @zibby/memory not available */ }
53
+ }
54
+ }
@@ -0,0 +1,222 @@
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
+ EXECUTION SEQUENCE (MANDATORY - FOLLOW STRICTLY):
78
+ 1. Execute the test steps efficiently (navigate, fill, click)
79
+ - Max 10-15 actions total
80
+ - If stuck, move on to next step
81
+ 2. Quick verification - check if main result is visible
82
+ - **If you see expected result → IMMEDIATELY return JSON**
83
+ - Don't try to make it perfect - good enough is enough
84
+ 3. **RETURN JSON AND STOP COMPLETELY**
85
+ - Format: { "success": true, "steps": ["step 1", ...], "browserClosed": true, "actions": [...] }
86
+ - MUST include: "success" (boolean), "steps" (array), "browserClosed" (boolean)
87
+ - Keep JSON CONCISE - short descriptions, no excessive detail
88
+ - After the closing brace }, DO NOT write ANYTHING
89
+ - NO commentary, NO explanations, NO additional text
90
+ - NO second JSON object
91
+ - Just the JSON, then STOP
92
+
93
+ IMPORTANT for 'actions' array (STRICT 1:1 MAPPING):
94
+ - Each entry MUST match EXACTLY ONE browser tool call.
95
+ - DO NOT group multiple tool calls into one action.
96
+ - DO NOT combine multiple 'fill' calls into one action.
97
+ - If you call browser_type 3 times for 3 fields, you MUST have 3 actions in the array.
98
+ - Include actual values/URLs in descriptions.
99
+ - Keep descriptions SHORT (5-10 words max).
100
+
101
+ IMPORTANT for 'assertions' array (USE THE CHECKLIST ABOVE):
102
+ - Your assertions array MUST match the ASSERTION CHECKLIST exactly - one entry per item
103
+ - If you verified it and it passed → "passed": true
104
+ - If you could NOT verify it or it wasn't found → "passed": false with evidence of what you saw instead
105
+ - Each assertion MUST include 'verifiedAfterAction' (0-based action index after which you checked)
106
+ - Format: {"description": "...", "passed": true/false, "verifiedAfterAction": N, "evidence": "..."}
107
+
108
+ 🔍 CRITICAL: CAPTURE ROBUST SELECTORS (for script generation)
109
+ For EACH action, capture multiple selector strategies in priority order:
110
+
111
+ 1. **Role + Name** (Most Robust - Accessibility-first):
112
+ - Role: button/textbox/link/etc
113
+ - Name: visible text or aria-label
114
+ Example: {"role": "button", "name": "Login"}
115
+
116
+ 2. **Stable Attributes** (Good Stability):
117
+ - name, type, placeholder, aria-label
118
+ Example: {"attributes": {"name": "username", "type": "text", "placeholder": "Enter username"}}
119
+
120
+ 3. **Partial Match** (For Dynamic Elements):
121
+ - Use starts-with for dynamic IDs/classes
122
+ Example: {"partialMatch": {"id": "^user-", "class": "^btn-"}}
123
+
124
+ 4. **Structural** (Fallback):
125
+ - Tag + position relative to stable landmark
126
+ Example: {"structure": "form input[type='text']:nth-of-type(1)"}
127
+
128
+ Format for actions with selectors:
129
+ {
130
+ "description": "Fill username field with 'joe'",
131
+ "reasoning": "Need to authenticate user",
132
+ "type": "fill",
133
+ "selectors": {
134
+ "role": {"role": "textbox", "name": "Username"},
135
+ "attributes": {"name": "username", "type": "text", "placeholder": "请输入账号"},
136
+ "structure": "form input[type='text']:first-of-type"
137
+ },
138
+ "value": "joe"
139
+ }
140
+
141
+ IMPORTANT for 'evidenceScreenshots' (array) - OPTIONAL:
142
+ - Screenshots are OPTIONAL - only take if helpful
143
+ - If you take screenshots, use descriptive filenames
144
+ - Filename pattern: "{step-number}-{action-or-state}.png"
145
+ - Keep it minimal - test execution is more important than documentation
146
+ `;
147
+ },
148
+
149
+ outputSchema: z.object({
150
+ success: z.boolean()
151
+ .describe('Whether the test execution completed successfully'),
152
+
153
+ steps: z.array(z.string())
154
+ .describe('Array of test steps executed'),
155
+
156
+ finalUrl: z.string()
157
+ .nullish()
158
+ .describe('Final URL after test execution'),
159
+
160
+ actions: z.array(z.object({
161
+ type: z.string()
162
+ .describe('Action type: navigate, click, fill, type, select, keypress, hover, drag'),
163
+ description: z.string()
164
+ .describe('Human-readable description of the action'),
165
+ reasoning: z.string().nullish()
166
+ .describe('Why this action was performed'),
167
+ selectors: z.object({
168
+ role: z.object({
169
+ role: z.string().describe('ARIA role (e.g. button, link, textbox, generic)'),
170
+ name: z.string().nullish().describe('Accessible name of the element')
171
+ }).nullish().describe('Role-based selector for fallback matching')
172
+ }).nullish()
173
+ .describe('Element selectors captured during the action'),
174
+ value: z.string().nullish()
175
+ .describe('Value entered for fill/type actions')
176
+ }))
177
+ .nullish()
178
+ .describe('Detailed array of actions performed with descriptions and reasoning'),
179
+
180
+ assertions: z.array(z.object({
181
+ description: z.string()
182
+ .describe('What was verified'),
183
+ passed: z.boolean()
184
+ .describe('Whether the assertion passed'),
185
+ verifiedAfterAction: z.number()
186
+ .describe('Index of the action after which this was verified (0-based, matches actions array index) - REQUIRED'),
187
+ evidence: z.string()
188
+ .nullish()
189
+ .describe('Brief evidence of what was observed')
190
+ }))
191
+ .nullish()
192
+ .describe('Array of assertions made during test'),
193
+
194
+ waits: z.array(z.object({
195
+ description: z.string().describe('What the wait is for'),
196
+ duration: z.number().nullish().describe('Wait duration in milliseconds'),
197
+ condition: z.string().nullish().describe('Wait condition expression')
198
+ }))
199
+ .nullish()
200
+ .describe('Array of waits needed for proper test execution'),
201
+
202
+ evidenceScreenshots: z.array(z.object({
203
+ filename: z.string()
204
+ .describe('Descriptive filename pattern: {step-number}-{action-or-state}.png'),
205
+
206
+ description: z.string()
207
+ .describe('What the screenshot shows and why it is evidence'),
208
+
209
+ verdict: z.enum(['pass', 'fail', 'info'])
210
+ .describe('Test verdict: pass/fail for validation points, info for checkpoints')
211
+ }))
212
+ .nullish()
213
+ .describe('Array of screenshots taken at key validation points throughout the test'),
214
+
215
+ browserClosed: z.boolean()
216
+ .describe('Whether the browser was properly closed (should always be true)'),
217
+
218
+ notes: z.string()
219
+ .nullish()
220
+ .describe('Additional notes or observations. REQUIRED when success=false to explain why test failed or could not execute')
221
+ })
222
+ };
@@ -0,0 +1,97 @@
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
+ return `Generate and verify Playwright test at ${state.outputPath}
32
+
33
+ Test Spec:
34
+ ${state.testSpec}
35
+
36
+ Live Execution Summary:
37
+ - Success: ${exec.success}
38
+ - Steps: ${JSON.stringify(exec.steps)}
39
+ - Final URL: ${exec.finalUrl || 'unknown'}
40
+ ${actionsBlock}
41
+ ${assertionsBlock}
42
+ ${setupHint}
43
+ IMPORTS AND PATTERN:
44
+ \`\`\`javascript
45
+ import { test, expect } from '@playwright/test';
46
+ import { StableIdRuntime } from '@zibby/core';
47
+
48
+ async function clickSafe(page, stableId, fallback) {
49
+ try { await StableIdRuntime.clickWithRetry(page, stableId); }
50
+ catch { await fallback.click(); }
51
+ }
52
+
53
+ test('Test Name', async ({ page }) => {
54
+ await page.goto('https://...');
55
+ await StableIdRuntime.injectStableIds(page);
56
+ // Elements WITH stable IDs + fallback — use clickSafe:
57
+ await clickSafe(page, 'zibby-xxxxx', page.getByRole('button', { name: '...' }));
58
+ await StableIdRuntime.fillWithRetry(page, 'zibby-xxxxx', 'value');
59
+ // Elements WITHOUT stable IDs (NO_STABLE_ID) — use native Playwright selectors:
60
+ await page.getByText('visible text').click();
61
+ await page.getByRole('button', { name: 'Submit' }).click();
62
+ await page.getByPlaceholder('placeholder text').fill('value');
63
+ await expect(page).toHaveURL(/expected-url/);
64
+ });
65
+ \`\`\`
66
+
67
+ RULES:
68
+ 1. First navigate → page.goto(), skip subsequent navigates
69
+ 2. After goto, call StableIdRuntime.injectStableIds(page)
70
+ 3. Selector priority:
71
+ a. If memory/insights flag a stableId as unreliable → use the fallback selector instead
72
+ b. If action is marked [DUPLICATE_STABLE_ID] → always use the provided fallback
73
+ c. Cross-reference stableIds against memory "Reliable Selectors" and "Flaky Selectors" — prefer proven selectors, avoid flaky ones
74
+ d. Otherwise use EXACT stable IDs from recorded actions
75
+ 4. For [NO_STABLE_ID] actions, use the fallback selector (getByText, getByRole, getByPlaceholder)
76
+ 5. Skip duplicate consecutive clicks on same stableId
77
+ 6. No comments in generated code
78
+ 7. Implement ALL assertions from the list above
79
+ 8. If an assertion fails after retries, comment it out with a TODO (don't delete it)
80
+ 9. Selector failure handling:
81
+ - 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
82
+ - Note the specific Playwright locator strategy that succeeded
83
+ 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.
84
+ 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.
85
+
86
+ WORKFLOW:
87
+ 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.
88
+ 2. Write test to ${state.outputPath} (after the run, a copy is mirrored under ${state.sessionPath}/generate_script/ for Studio — you may also write directly there if you prefer)
89
+ 3. Verify syntax: run node --check on the file. If it fails, fix and re-check before proceeding.
90
+ 4. Run: PLAYWRIGHT_HEADLESS=1 npx playwright test ${state.outputPath} --reporter=line --timeout=60000
91
+ 5. If fails: try selectors in order — (a) getByRole (b) getByText (c) getByTestId (d) add waitForSelector. Never retry the same selector twice.
92
+ 6. MAX 2 ATTEMPTS then STOP
93
+
94
+ The test runs in: ${state.cwd || 'project root'}
95
+ `;
96
+ },
97
+ };
@@ -0,0 +1,3 @@
1
+ export { preflightNode } from './preflight.mjs';
2
+ export { executeLiveNode } from './execute-live.mjs';
3
+ export { generateScriptNode } from './generate-script.mjs';
@@ -0,0 +1,59 @@
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 } 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
+ export const preflightNode = {
31
+ name: 'preflight',
32
+
33
+ async onComplete(state, result) {
34
+ const sessionPath = state.sessionPath || process.env.ZIBBY_SESSION_PATH;
35
+ if (sessionPath && result.title) {
36
+ try {
37
+ writeFileSync(join(sessionPath, 'title.txt'), result.title, 'utf-8');
38
+ console.log(`Saved title: "${result.title}"`);
39
+ } catch (error) {
40
+ console.warn(`⚠️ Could not save title.txt: ${error.message}`);
41
+ }
42
+ }
43
+ return result;
44
+ },
45
+
46
+ prompt: (state) => `Analyze this test specification and extract:
47
+ 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.
48
+ 2. Every expected result as a verifiable assertion. Each assertion must be something the browser can check after execution.
49
+
50
+ Test Spec:
51
+ ${state.testSpec}
52
+
53
+ IMPORTANT: You MUST create ONE assertion for EACH expected result in the spec. Do NOT skip any.
54
+
55
+ Return ONLY this JSON:
56
+ { "title": "TICKET-ID: Short action title", "assertions": [ { "description": "...", "expected": "..." }, ... ] }`,
57
+
58
+ outputSchema: PreflightOutputSchema
59
+ };