@zibby/cli 0.1.5
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/LICENSE +21 -0
- package/README.md +926 -0
- package/bin/zibby.js +266 -0
- package/package.json +65 -0
- package/src/auth/cli-login.js +406 -0
- package/src/commands/analyze-graph.js +334 -0
- package/src/commands/ci-setup.js +65 -0
- package/src/commands/implement.js +664 -0
- package/src/commands/init.js +736 -0
- package/src/commands/list-projects.js +78 -0
- package/src/commands/memory.js +171 -0
- package/src/commands/run.js +926 -0
- package/src/commands/setup-scripts.js +101 -0
- package/src/commands/upload.js +163 -0
- package/src/commands/video.js +30 -0
- package/src/commands/workflow.js +369 -0
- package/src/config/config.js +117 -0
- package/src/config/environments.js +145 -0
- package/src/utils/execution-context.js +25 -0
- package/src/utils/progress-reporter.js +155 -0
|
@@ -0,0 +1,736 @@
|
|
|
1
|
+
import { mkdir, writeFile, readFile } from 'fs/promises';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { join, resolve, dirname } from 'path';
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import { spawn } from 'child_process';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = dirname(__filename);
|
|
12
|
+
|
|
13
|
+
const MEMORY_ENV_BLOCK = `
|
|
14
|
+
# Test Memory (Dolt-backed ā learns from every run)
|
|
15
|
+
ZIBBY_MEMORY=1
|
|
16
|
+
# ZIBBY_MEMORY_COMPACT_EVERY=1500 # Auto-compact every N runs (0 to disable)
|
|
17
|
+
# ZIBBY_MEMORY_MAX_RUNS=3000 # Max runs per spec to keep
|
|
18
|
+
# ZIBBY_MEMORY_MAX_AGE=1095 # Max age in days for stale data (~3 years)
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
export async function initCommand(projectName, options) {
|
|
22
|
+
console.log(chalk.bold.cyan('\nš Welcome to Zibby Test Automation!\n'));
|
|
23
|
+
|
|
24
|
+
const targetDir = projectName ? resolve(process.cwd(), projectName) : process.cwd();
|
|
25
|
+
const projectNameActual = projectName || 'zibby-tests';
|
|
26
|
+
const isNewProject = !!projectName;
|
|
27
|
+
|
|
28
|
+
// If creating new project, check if directory exists
|
|
29
|
+
if (isNewProject && existsSync(targetDir)) {
|
|
30
|
+
console.log(chalk.red(`\nā Directory "${projectName}" already exists!\n`));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// If in existing directory, check if already initialized (unless --force)
|
|
35
|
+
if (!isNewProject && existsSync(join(targetDir, '.zibby.config.js')) && !options.force) {
|
|
36
|
+
console.log(chalk.yellow('\nā ļø Zibby is already initialized in this directory!\n'));
|
|
37
|
+
console.log(chalk.white('Config file found: .zibby.config.js'));
|
|
38
|
+
console.log(chalk.gray('Use --force or -f to reinitialize\n'));
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (options.force && !isNewProject) {
|
|
43
|
+
console.log(chalk.cyan('\nReinitializing Zibby configuration...\n'));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Build answers from CLI flags or prompt interactively
|
|
47
|
+
let answers;
|
|
48
|
+
|
|
49
|
+
// Check if all options provided via CLI (non-interactive mode)
|
|
50
|
+
const hasAllFlags = options.agent && (options.headed || options.headless);
|
|
51
|
+
|
|
52
|
+
if (hasAllFlags) {
|
|
53
|
+
// Non-interactive mode - use CLI flags
|
|
54
|
+
console.log(chalk.cyan('Setting up with provided options...\n'));
|
|
55
|
+
answers = {
|
|
56
|
+
agent: options.agent,
|
|
57
|
+
browserMode: options.headless ? 'headless' : 'headed',
|
|
58
|
+
apiKey: options.apiKey || null,
|
|
59
|
+
cloudSync: !!(options.cloudSync || options.apiKey),
|
|
60
|
+
};
|
|
61
|
+
} else {
|
|
62
|
+
// Interactive mode - prompt for missing options
|
|
63
|
+
const prompts = [];
|
|
64
|
+
|
|
65
|
+
if (!options.agent) {
|
|
66
|
+
prompts.push({
|
|
67
|
+
type: 'select',
|
|
68
|
+
name: 'agent',
|
|
69
|
+
message: 'Which AI agent do you prefer?',
|
|
70
|
+
choices: [
|
|
71
|
+
{ name: 'Cursor', value: 'cursor' },
|
|
72
|
+
{ name: 'Claude (Anthropic)', value: 'claude' },
|
|
73
|
+
],
|
|
74
|
+
default: 'cursor',
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!options.headed && !options.headless) {
|
|
79
|
+
prompts.push({
|
|
80
|
+
type: 'select',
|
|
81
|
+
name: 'browserMode',
|
|
82
|
+
message: 'Browser mode during live AI execution?',
|
|
83
|
+
choices: [
|
|
84
|
+
{ name: 'Headed - Visible browser (recommended for development)', value: 'headed' },
|
|
85
|
+
{ name: 'Headless - Hidden browser (for CI/CD)', value: 'headless' },
|
|
86
|
+
],
|
|
87
|
+
default: 'headed',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!options.apiKey) {
|
|
92
|
+
prompts.push({
|
|
93
|
+
type: 'input',
|
|
94
|
+
name: 'apiKey',
|
|
95
|
+
message: 'Enable cloud sync? Enter project ZIBBY_API_KEY (or press Enter to skip):',
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
answers = prompts.length > 0 ? await inquirer.prompt(prompts) : {};
|
|
100
|
+
|
|
101
|
+
// Merge with CLI options
|
|
102
|
+
answers.agent = options.agent || answers.agent;
|
|
103
|
+
answers.browserMode = options.headless ? 'headless' : (options.headed ? 'headed' : answers.browserMode);
|
|
104
|
+
answers.apiKey = options.apiKey || answers.apiKey;
|
|
105
|
+
answers.cloudSync = !!(options.cloudSync || options.apiKey || (answers.apiKey && answers.apiKey.trim()));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Set Playwright as default (only option for now)
|
|
109
|
+
answers.mcp = 'playwright';
|
|
110
|
+
|
|
111
|
+
// Automatically setup MCP if Cursor agent selected
|
|
112
|
+
answers.setupMcp = (answers.agent === 'cursor');
|
|
113
|
+
|
|
114
|
+
const spinner = ora('Setting up Zibby...').start();
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
// Create project directory if new project
|
|
118
|
+
if (isNewProject) {
|
|
119
|
+
await mkdir(targetDir, { recursive: true });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Create folders
|
|
123
|
+
await mkdir(join(targetDir, 'test-specs/examples'), { recursive: true });
|
|
124
|
+
await mkdir(join(targetDir, 'tests'), { recursive: true });
|
|
125
|
+
await mkdir(join(targetDir, '.zibby/output'), { recursive: true });
|
|
126
|
+
|
|
127
|
+
if (options.mem) {
|
|
128
|
+
try {
|
|
129
|
+
const { initMemory, DoltDB } = await import('@zibby/memory');
|
|
130
|
+
if (DoltDB.isAvailable()) {
|
|
131
|
+
const { created } = initMemory(targetDir);
|
|
132
|
+
if (created) {
|
|
133
|
+
spinner.text = 'Initialized test memory database (Dolt)...';
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
spinner.text = 'Dolt not found ā skipping memory database (brew install dolt)';
|
|
137
|
+
}
|
|
138
|
+
} catch (_e) {
|
|
139
|
+
// @zibby/memory not available ā skip silently
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
spinner.text = 'Scaffolding workflow graph...';
|
|
144
|
+
|
|
145
|
+
// Use Template Factory to get the right template
|
|
146
|
+
const { TemplateFactory } = await import('@zibby/core/templates');
|
|
147
|
+
const templateName = options.template || 'browser-test-automation'; // Default to browser-test-automation
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const { graphPath, nodesPath, readmePath, resultHandlerPath } = TemplateFactory.getTemplateFiles(templateName);
|
|
151
|
+
const targetZibbyDir = join(targetDir, '.zibby');
|
|
152
|
+
|
|
153
|
+
// Copy graph.js
|
|
154
|
+
const graphTemplate = await readFile(graphPath, 'utf-8');
|
|
155
|
+
await writeFile(join(targetZibbyDir, 'graph.js'), graphTemplate);
|
|
156
|
+
|
|
157
|
+
// Copy result-handler.js (workflow-specific post-processing)
|
|
158
|
+
if (resultHandlerPath) {
|
|
159
|
+
const rhTemplate = await readFile(resultHandlerPath, 'utf-8');
|
|
160
|
+
await writeFile(join(targetZibbyDir, 'result-handler.js'), rhTemplate);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Copy README
|
|
164
|
+
const readmeTemplate = await readFile(readmePath, 'utf-8');
|
|
165
|
+
await writeFile(join(targetZibbyDir, 'README.md'), readmeTemplate);
|
|
166
|
+
|
|
167
|
+
// Copy nodes directory
|
|
168
|
+
await mkdir(join(targetZibbyDir, 'nodes'), { recursive: true });
|
|
169
|
+
const { readdirSync } = await import('fs');
|
|
170
|
+
const nodeFiles = readdirSync(nodesPath);
|
|
171
|
+
for (const file of nodeFiles) {
|
|
172
|
+
const content = await readFile(join(nodesPath, file), 'utf-8');
|
|
173
|
+
await writeFile(join(targetZibbyDir, 'nodes', file), content);
|
|
174
|
+
}
|
|
175
|
+
} catch (error) {
|
|
176
|
+
spinner.fail(`Failed to scaffold template: ${error.message}`);
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
spinner.text = 'Generating configuration files...';
|
|
181
|
+
|
|
182
|
+
// Always create Zibby config
|
|
183
|
+
const configContent = generateConfig(answers, options);
|
|
184
|
+
await writeFile(join(targetDir, '.zibby.config.js'), configContent);
|
|
185
|
+
|
|
186
|
+
// Always create .env.example
|
|
187
|
+
const envContent = generateEnvFile(answers, options);
|
|
188
|
+
await writeFile(join(targetDir, '.env.example'), envContent);
|
|
189
|
+
|
|
190
|
+
// Create/update .env file if API key provided
|
|
191
|
+
if (answers.apiKey && answers.apiKey.trim()) {
|
|
192
|
+
await createOrUpdateEnvFile(targetDir, answers.apiKey.trim(), answers, options);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Ensure ZIBBY_MEMORY=1 is in .env when --mem is used (even without API key)
|
|
196
|
+
if (options.mem) {
|
|
197
|
+
const envPath = join(targetDir, '.env');
|
|
198
|
+
if (existsSync(envPath)) {
|
|
199
|
+
const existingEnv = await readFile(envPath, 'utf-8');
|
|
200
|
+
if (!existingEnv.includes('ZIBBY_MEMORY=')) {
|
|
201
|
+
await writeFile(envPath, existingEnv + MEMORY_ENV_BLOCK);
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
await writeFile(envPath, generateEnvFile(answers, options));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Only create package.json if new project
|
|
209
|
+
if (isNewProject) {
|
|
210
|
+
const packageJsonContent = generatePackageJson(projectNameActual, answers);
|
|
211
|
+
await writeFile(join(targetDir, 'package.json'), packageJsonContent);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Create .gitignore if doesn't exist
|
|
215
|
+
if (!existsSync(join(targetDir, '.gitignore'))) {
|
|
216
|
+
const gitignoreContent = generateGitignore();
|
|
217
|
+
await writeFile(join(targetDir, '.gitignore'), gitignoreContent);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Create playwright.config.js if doesn't exist
|
|
221
|
+
if (!existsSync(join(targetDir, 'playwright.config.js'))) {
|
|
222
|
+
const playwrightConfigContent = generatePlaywrightConfig('on'); // Default: video enabled
|
|
223
|
+
await writeFile(join(targetDir, 'playwright.config.js'), playwrightConfigContent);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Create example spec if doesn't exist
|
|
227
|
+
if (!existsSync(join(targetDir, 'test-specs/examples/example-domain.txt'))) {
|
|
228
|
+
const exampleSpecContent = generateExampleSpec();
|
|
229
|
+
await writeFile(join(targetDir, 'test-specs/examples/example-domain.txt'), exampleSpecContent);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Create README only for new projects
|
|
233
|
+
if (isNewProject) {
|
|
234
|
+
const readmeContent = generateReadme(projectNameActual, answers);
|
|
235
|
+
await writeFile(join(targetDir, 'README.md'), readmeContent);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
spinner.succeed(isNewProject ? 'Project created!' : 'Zibby initialized!');
|
|
239
|
+
|
|
240
|
+
// Install dependencies for new projects
|
|
241
|
+
if (isNewProject && !options.skipInstall) {
|
|
242
|
+
const installSpinner = ora('Installing dependencies...').start();
|
|
243
|
+
|
|
244
|
+
await new Promise((resolveFn, reject) => {
|
|
245
|
+
const npm = spawn('npm', ['install'], {
|
|
246
|
+
cwd: targetDir,
|
|
247
|
+
stdio: 'pipe',
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
npm.on('close', (code) => {
|
|
251
|
+
if (code === 0) {
|
|
252
|
+
installSpinner.succeed('Dependencies installed!');
|
|
253
|
+
resolveFn();
|
|
254
|
+
} else {
|
|
255
|
+
installSpinner.fail('Failed to install dependencies');
|
|
256
|
+
reject(new Error('npm install failed'));
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
} else if (!isNewProject) {
|
|
261
|
+
console.log(chalk.gray('\nMake sure @zibby/cli is installed in your package.json\n'));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Always check and install Playwright browsers if missing (for both new and existing projects)
|
|
265
|
+
// This ensures browsers are available even when reinitializing
|
|
266
|
+
if (!options.skipInstall) {
|
|
267
|
+
const playwrightSpinner = ora('Installing Playwright browsers...').start();
|
|
268
|
+
|
|
269
|
+
await new Promise((resolveFn) => {
|
|
270
|
+
const npx = spawn('npx', ['playwright', 'install', 'chromium'], {
|
|
271
|
+
cwd: targetDir,
|
|
272
|
+
stdio: 'pipe',
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
let output = '';
|
|
276
|
+
npx.stdout.on('data', (data) => {
|
|
277
|
+
output += data.toString();
|
|
278
|
+
});
|
|
279
|
+
npx.stderr.on('data', (data) => {
|
|
280
|
+
output += data.toString();
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
npx.on('close', (code) => {
|
|
284
|
+
if (code === 0) {
|
|
285
|
+
// Check if it actually installed or was already installed
|
|
286
|
+
if (output.includes('already installed') || output.includes('up to date')) {
|
|
287
|
+
playwrightSpinner.succeed('Playwright browsers already installed');
|
|
288
|
+
} else {
|
|
289
|
+
playwrightSpinner.succeed('Playwright browsers installed!');
|
|
290
|
+
}
|
|
291
|
+
resolveFn();
|
|
292
|
+
} else {
|
|
293
|
+
playwrightSpinner.warn('Could not verify Playwright browsers');
|
|
294
|
+
console.log(chalk.yellow('\nā ļø If tests fail, run: npx playwright install\n'));
|
|
295
|
+
resolveFn(); // Don't fail the whole init
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Setup cursor-agent MCP if requested
|
|
302
|
+
if (answers.agent === 'cursor' && answers.setupMcp) {
|
|
303
|
+
const mcpSpinner = ora('Setting up Playwright MCP...').start();
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
const { setupPlaywrightMcpCommand } = await import('./setup-scripts.js');
|
|
307
|
+
|
|
308
|
+
// Use cloudSync from answers (prompt response or flag)
|
|
309
|
+
const cloudSync = answers.cloudSync || false;
|
|
310
|
+
|
|
311
|
+
// Use browserMode from answers (default to headed)
|
|
312
|
+
const isHeaded = answers.browserMode !== 'headless';
|
|
313
|
+
|
|
314
|
+
await setupPlaywrightMcpCommand({
|
|
315
|
+
headed: isHeaded,
|
|
316
|
+
cloudSync,
|
|
317
|
+
video: 'on', // Default video recording enabled
|
|
318
|
+
viewport: { width: 1280, height: 720 } // Default viewport size
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
let message = 'Playwright MCP configured';
|
|
322
|
+
if (isHeaded) message += ' (headed mode - visible browser)';
|
|
323
|
+
else message += ' (headless mode - hidden browser)';
|
|
324
|
+
if (cloudSync) message += ' + Zibby MCP (cloud sync)';
|
|
325
|
+
|
|
326
|
+
mcpSpinner.succeed(message);
|
|
327
|
+
|
|
328
|
+
if (cloudSync && answers.apiKey) {
|
|
329
|
+
console.log(chalk.gray('\n ā ZIBBY_API_KEY saved to .env\n'));
|
|
330
|
+
} else if (cloudSync) {
|
|
331
|
+
console.log(chalk.gray('\n Set ZIBBY_API_KEY in .env to enable uploads\n'));
|
|
332
|
+
}
|
|
333
|
+
} catch (error) {
|
|
334
|
+
// Error during MCP setup - check if MCP is already configured
|
|
335
|
+
mcpSpinner.fail('MCP setup script failed');
|
|
336
|
+
console.log(chalk.yellow(' Check if MCP is already configured:'));
|
|
337
|
+
console.log(chalk.gray(' ⢠Open Cursor settings ā Check "playwright-official" MCP'));
|
|
338
|
+
console.log(chalk.gray(' ⢠Run: cursor-agent mcp list'));
|
|
339
|
+
console.log(chalk.gray(` ⢠Or run manually: zibby setup-playwright\n`));
|
|
340
|
+
console.log(chalk.gray(` Error: ${error.message}\n`));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
console.log(chalk.bold.green('\nš All set!\n'));
|
|
345
|
+
console.log(chalk.cyan('Try running your first test:'));
|
|
346
|
+
if (isNewProject) {
|
|
347
|
+
console.log(chalk.white(` cd ${projectName}`));
|
|
348
|
+
}
|
|
349
|
+
console.log(chalk.white(' zibby run test-specs/examples/example-domain.txt\n'));
|
|
350
|
+
|
|
351
|
+
console.log(chalk.cyan('Next steps:'));
|
|
352
|
+
if (answers.agent === 'claude') {
|
|
353
|
+
let step = 1;
|
|
354
|
+
console.log(chalk.white(` ${step++}. ${answers.apiKey ? 'Update' : 'Copy .env.example to .env and add'} ANTHROPIC_API_KEY`));
|
|
355
|
+
if (answers.cloudSync && !answers.apiKey) {
|
|
356
|
+
console.log(chalk.white(` ${step++}. Add ZIBBY_API_KEY to .env for cloud sync`));
|
|
357
|
+
}
|
|
358
|
+
console.log(chalk.white(` ${step++}. Write test specs in test-specs/`));
|
|
359
|
+
console.log(chalk.white(` ${step++}. Run: npx zibby run <spec-file>\n`));
|
|
360
|
+
} else if (answers.agent === 'cursor') {
|
|
361
|
+
let step = 1;
|
|
362
|
+
if (answers.cloudSync && !answers.apiKey) {
|
|
363
|
+
console.log(chalk.white(` ${step++}. Add ZIBBY_API_KEY to .env for cloud sync`));
|
|
364
|
+
}
|
|
365
|
+
console.log(chalk.white(` ${step++}. Write test specs in test-specs/`));
|
|
366
|
+
console.log(chalk.white(` ${step++}. Run: npx zibby run <spec-file>\n`));
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
} catch (error) {
|
|
370
|
+
spinner.fail('Failed to create project');
|
|
371
|
+
console.error(chalk.red(`\nā Error: ${error.message}\n`));
|
|
372
|
+
process.exit(1);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function generateConfig(answers, _options = {}) {
|
|
377
|
+
// Import here to avoid circular deps - constants are pure values
|
|
378
|
+
const DEFAULT_CLAUDE_MODEL = 'auto';
|
|
379
|
+
return `export default {
|
|
380
|
+
// AI agent settings - specify 'claude' or 'cursor' block
|
|
381
|
+
agent: {${answers.agent === 'claude' ? `
|
|
382
|
+
// Claude API settings
|
|
383
|
+
claude: {
|
|
384
|
+
model: '${DEFAULT_CLAUDE_MODEL}', // Options: 'auto', 'sonnet-4.6', 'opus-4.6', 'sonnet-4.5', 'opus-4.5'
|
|
385
|
+
maxTokens: 4096,
|
|
386
|
+
},
|
|
387
|
+
|
|
388
|
+
// OR use Cursor Agent (comment out claude block above, uncomment this):
|
|
389
|
+
// cursor: {
|
|
390
|
+
// model: 'auto', // Options: 'auto', 'opus-4.5', 'opus-4.5-thinking', 'sonnet-4.5', 'sonnet-4.5-thinking', 'composer-1', 'gpt-5.2-codex', 'gpt-5.2', 'gemini-3-pro', 'gemini-3-flash'
|
|
391
|
+
// },` : `
|
|
392
|
+
// Cursor Agent settings
|
|
393
|
+
cursor: {
|
|
394
|
+
model: 'auto', // Options: 'auto', 'opus-4.5', 'opus-4.5-thinking', 'sonnet-4.5', 'sonnet-4.5-thinking', 'composer-1', 'gpt-5.2-codex', 'gpt-5.2', 'gemini-3-pro', 'gemini-3-flash'
|
|
395
|
+
},
|
|
396
|
+
|
|
397
|
+
// OR use Claude (comment out cursor block above, uncomment this):
|
|
398
|
+
// claude: {
|
|
399
|
+
// model: '${DEFAULT_CLAUDE_MODEL}', // Options: 'auto', 'sonnet-4.6', 'opus-4.6', 'sonnet-4.5', 'opus-4.5'
|
|
400
|
+
// maxTokens: 4096,
|
|
401
|
+
// },`}
|
|
402
|
+
strictMode: false, // Set to true if you need reliable structured output enforcement for production workflows (requires authentication)
|
|
403
|
+
},
|
|
404
|
+
|
|
405
|
+
// Advanced: Override models per node (optional)
|
|
406
|
+
// models: {
|
|
407
|
+
// default: 'auto', // Fallback for all nodes
|
|
408
|
+
// execute_live: 'claude-opus-4', // Override specific node
|
|
409
|
+
// },
|
|
410
|
+
|
|
411
|
+
// Folder paths
|
|
412
|
+
paths: {
|
|
413
|
+
specs: 'test-specs', // Where your .txt test specs are
|
|
414
|
+
generated: 'tests', // Where generated .spec.js files go
|
|
415
|
+
output: '.zibby/output', // Where workflow execution results are saved (default: .zibby/output)
|
|
416
|
+
// sessionPrefix: 'run', // Optional: prefix for session folders (e.g., run_1772788458045)
|
|
417
|
+
},
|
|
418
|
+
|
|
419
|
+
// Context discovery - auto-discovers CONTEXT.md & AGENTS.md files (cascades from root ā spec directory)
|
|
420
|
+
// Override filenames to search for different files
|
|
421
|
+
context: {
|
|
422
|
+
filenames: ['CONTEXT.md', 'AGENTS.md'], // Auto-discover these files (walks up from spec directory)
|
|
423
|
+
discovery: {
|
|
424
|
+
env: \`env-\${process.env.ENV || 'local'}.js\`, // Additional explicit files
|
|
425
|
+
// fixtures: 'fixtures.js', // Add more as needed
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
|
|
429
|
+
// Video recording (affects playwright.config.js generation)
|
|
430
|
+
video: 'on', // Options: 'off', 'on', 'retain-on-failure', 'on-first-retry'
|
|
431
|
+
|
|
432
|
+
// Browser viewport size for test execution
|
|
433
|
+
// Default: 1280x720 (works well on most screens)
|
|
434
|
+
// For larger displays: { width: 1920, height: 1080 }
|
|
435
|
+
// For mobile testing: { width: 375, height: 667 } (iPhone SE)
|
|
436
|
+
viewport: { width: 1280, height: 720 },
|
|
437
|
+
|
|
438
|
+
// Playwright artifacts (screenshots, traces, videos)
|
|
439
|
+
// Traces contain EXACT selectors for 100% accurate script generation
|
|
440
|
+
// Default: true (stored in test-results/playwright, separate from workflow output)
|
|
441
|
+
playwrightArtifacts: true,
|
|
442
|
+
|
|
443
|
+
// Cloud sync - auto-upload test results & videos (requires ZIBBY_API_KEY in .env)
|
|
444
|
+
cloudSync: ${answers.cloudSync || false}
|
|
445
|
+
};
|
|
446
|
+
`;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function generateEnvFile(answers, options = {}) {
|
|
450
|
+
let content = `# Zibby Test Automation - Environment Variables
|
|
451
|
+
|
|
452
|
+
# AI Provider Keys
|
|
453
|
+
${answers.agent === 'claude' ? '# Claude (Anthropic) - Direct API\nANTHROPIC_API_KEY=sk-ant-your_key_here\n' : '# ANTHROPIC_API_KEY=sk-ant-your_key_here\n'}
|
|
454
|
+
${answers.agent === 'cursor' ? '# Cursor Agent (uses cursor-agent CLI)\n# No API key needed if cursor-agent is installed\n# Run: curl https://cursor.com/install -fsS | bash\n' : ''}
|
|
455
|
+
|
|
456
|
+
# Zibby Cloud Sync (for uploading test results & videos)
|
|
457
|
+
# Get your API key from: https://zibby.app/settings/tokens
|
|
458
|
+
# ZIBBY_API_KEY=zby_your_api_key_here
|
|
459
|
+
|
|
460
|
+
# Optional: Notifications
|
|
461
|
+
# SLACK_WEBHOOK_URL=https://hooks.slack.com/services/YOUR/WEBHOOK/URL
|
|
462
|
+
`;
|
|
463
|
+
|
|
464
|
+
if (options.mem) {
|
|
465
|
+
content += MEMORY_ENV_BLOCK;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return content;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
async function createOrUpdateEnvFile(targetDir, apiKey, answers, options = {}) {
|
|
472
|
+
const envPath = join(targetDir, '.env');
|
|
473
|
+
let envContent;
|
|
474
|
+
|
|
475
|
+
// Read existing .env if it exists
|
|
476
|
+
if (existsSync(envPath)) {
|
|
477
|
+
try {
|
|
478
|
+
envContent = await readFile(envPath, 'utf-8');
|
|
479
|
+
|
|
480
|
+
// Update or add ZIBBY_API_KEY
|
|
481
|
+
if (envContent.includes('ZIBBY_API_KEY=')) {
|
|
482
|
+
envContent = envContent.replace(
|
|
483
|
+
/ZIBBY_API_KEY=.*/g,
|
|
484
|
+
`ZIBBY_API_KEY=${apiKey}`
|
|
485
|
+
);
|
|
486
|
+
} else {
|
|
487
|
+
envContent += `\n# Zibby Cloud Sync\nZIBBY_API_KEY=${apiKey}\n`;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (options.mem && !envContent.includes('ZIBBY_MEMORY=')) {
|
|
491
|
+
envContent += MEMORY_ENV_BLOCK;
|
|
492
|
+
}
|
|
493
|
+
} catch (_error) {
|
|
494
|
+
envContent = generateEnvFileWithKey(answers, apiKey, options);
|
|
495
|
+
}
|
|
496
|
+
} else {
|
|
497
|
+
envContent = generateEnvFileWithKey(answers, apiKey, options);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
await writeFile(envPath, envContent);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function generateEnvFileWithKey(answers, apiKey, options = {}) {
|
|
504
|
+
let content = `# Zibby Test Automation - Environment Variables
|
|
505
|
+
|
|
506
|
+
# AI Provider Keys
|
|
507
|
+
${answers.agent === 'claude' ? 'ANTHROPIC_API_KEY=sk-ant-your_key_here\n' : '# ANTHROPIC_API_KEY=sk-ant-your_key_here\n'}
|
|
508
|
+
|
|
509
|
+
# Zibby Cloud Sync
|
|
510
|
+
ZIBBY_API_KEY=${apiKey}
|
|
511
|
+
|
|
512
|
+
# Optional: Notifications
|
|
513
|
+
# SLACK_WEBHOOK_URL=https://hooks.slack.com/services/YOUR/WEBHOOK/URL
|
|
514
|
+
`;
|
|
515
|
+
|
|
516
|
+
if (options.mem) {
|
|
517
|
+
content += MEMORY_ENV_BLOCK;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
return content;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function generatePackageJson(projectName, _answers) {
|
|
524
|
+
return JSON.stringify({
|
|
525
|
+
name: projectName,
|
|
526
|
+
version: '1.0.0',
|
|
527
|
+
type: 'module',
|
|
528
|
+
private: true,
|
|
529
|
+
scripts: {
|
|
530
|
+
'test:spec': 'zibby run',
|
|
531
|
+
'test': 'playwright test',
|
|
532
|
+
'test:headed': 'playwright test --headed'
|
|
533
|
+
},
|
|
534
|
+
dependencies: {
|
|
535
|
+
'@zibby/cli': '^0.1.0',
|
|
536
|
+
'@zibby/core': '^0.1.0'
|
|
537
|
+
},
|
|
538
|
+
devDependencies: {
|
|
539
|
+
'@playwright/test': '^1.49.0',
|
|
540
|
+
'dotenv': '^17.2.3'
|
|
541
|
+
}
|
|
542
|
+
}, null, 2);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function generateGitignore() {
|
|
546
|
+
return `# Dependencies
|
|
547
|
+
node_modules/
|
|
548
|
+
|
|
549
|
+
# Test artifacts
|
|
550
|
+
.zibby/output/
|
|
551
|
+
playwright-report/
|
|
552
|
+
tests/
|
|
553
|
+
*.webm
|
|
554
|
+
*.mp4
|
|
555
|
+
|
|
556
|
+
# Environment variables
|
|
557
|
+
.env
|
|
558
|
+
|
|
559
|
+
# OS
|
|
560
|
+
.DS_Store
|
|
561
|
+
Thumbs.db
|
|
562
|
+
|
|
563
|
+
# IDE
|
|
564
|
+
.vscode/
|
|
565
|
+
.idea/
|
|
566
|
+
|
|
567
|
+
# Zibby cache
|
|
568
|
+
.zibby/cache/
|
|
569
|
+
.zibby/tenancy.json
|
|
570
|
+
|
|
571
|
+
# Zibby memory (Dolt DB synced separately via dolt remote, not git)
|
|
572
|
+
.zibby/memory/
|
|
573
|
+
.zibby/memory-context.md
|
|
574
|
+
`;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function generatePlaywrightConfig(_videoOption = 'off', outputDir = null) {
|
|
578
|
+
const outputDirLine = outputDir ? ` outputDir: '${outputDir}',\n` : ` outputDir: 'test-results/playwright', // Keep Playwright artifacts separate from Zibby workflow output\n`;
|
|
579
|
+
|
|
580
|
+
return `import { defineConfig} from '@playwright/test';
|
|
581
|
+
|
|
582
|
+
export default defineConfig({
|
|
583
|
+
testDir: './tests',
|
|
584
|
+
${outputDirLine} timeout: 30000,
|
|
585
|
+
retries: 0,
|
|
586
|
+
workers: 1,
|
|
587
|
+
|
|
588
|
+
use: {
|
|
589
|
+
headless: process.env.PLAYWRIGHT_HEADLESS === '1' ? true : false,
|
|
590
|
+
viewport: { width: 1280, height: 720 },
|
|
591
|
+
screenshot: 'off',
|
|
592
|
+
video: 'off',
|
|
593
|
+
},
|
|
594
|
+
|
|
595
|
+
reporter: [
|
|
596
|
+
['html'],
|
|
597
|
+
['list']
|
|
598
|
+
],
|
|
599
|
+
});
|
|
600
|
+
`;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function generateExampleSpec() {
|
|
604
|
+
return `Test Specification: Example Domain Navigation
|
|
605
|
+
==============================================
|
|
606
|
+
|
|
607
|
+
Application: Example Domain
|
|
608
|
+
URL: https://example.com
|
|
609
|
+
Feature: Basic navigation and link verification
|
|
610
|
+
|
|
611
|
+
Test Objective:
|
|
612
|
+
---------------
|
|
613
|
+
Verify that the example.com website loads correctly, displays expected content,
|
|
614
|
+
and navigational links function as intended.
|
|
615
|
+
|
|
616
|
+
Test Steps:
|
|
617
|
+
-----------
|
|
618
|
+
1. Navigate to https://example.com
|
|
619
|
+
2. Verify the page title contains "Example Domain"
|
|
620
|
+
3. Verify the main heading is visible and contains "Example Domain"
|
|
621
|
+
4. Locate and verify the "More information..." link is present
|
|
622
|
+
5. Click the "More information..." link
|
|
623
|
+
6. Verify navigation to IANA domain information page
|
|
624
|
+
7. Verify the new page URL contains "iana.org"
|
|
625
|
+
8. Verify the page loaded successfully (no errors)
|
|
626
|
+
|
|
627
|
+
Expected Results:
|
|
628
|
+
-----------------
|
|
629
|
+
- Initial page loads within 3 seconds
|
|
630
|
+
- "Example Domain" heading is clearly visible
|
|
631
|
+
- "More information..." link is clickable and visible
|
|
632
|
+
- Navigation to IANA page succeeds
|
|
633
|
+
- No console errors or network failures
|
|
634
|
+
- Page title updates after navigation
|
|
635
|
+
|
|
636
|
+
Notes:
|
|
637
|
+
------
|
|
638
|
+
- Uses a stable, public domain (example.com) maintained by IANA
|
|
639
|
+
- No authentication required
|
|
640
|
+
- Suitable for CI/CD smoke testing
|
|
641
|
+
`;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function generateReadme(projectName, answers) {
|
|
645
|
+
return `# ${projectName}
|
|
646
|
+
|
|
647
|
+
AI-powered test automation with Zibby.
|
|
648
|
+
|
|
649
|
+
## Setup
|
|
650
|
+
|
|
651
|
+
1. Install dependencies:
|
|
652
|
+
\`\`\`bash
|
|
653
|
+
npm install
|
|
654
|
+
\`\`\`
|
|
655
|
+
|
|
656
|
+
2. Configure environment:
|
|
657
|
+
\`\`\`bash
|
|
658
|
+
cp .env.example .env
|
|
659
|
+
# Edit .env and add your ${answers.agent === 'claude' ? 'ANTHROPIC_API_KEY' : 'OPENAI_API_KEY'}
|
|
660
|
+
\`\`\`
|
|
661
|
+
|
|
662
|
+
3. Run example test:
|
|
663
|
+
\`\`\`bash
|
|
664
|
+
npx zibby run test-specs/examples/example-domain.txt
|
|
665
|
+
\`\`\`
|
|
666
|
+
|
|
667
|
+
## Usage
|
|
668
|
+
|
|
669
|
+
### Run Test Specs
|
|
670
|
+
|
|
671
|
+
\`\`\`bash
|
|
672
|
+
# Run a test spec (executes + generates + verifies)
|
|
673
|
+
npx zibby run test-specs/auth/login.txt
|
|
674
|
+
|
|
675
|
+
# Run in headless mode
|
|
676
|
+
npx zibby run test-specs/auth/login.txt --headless
|
|
677
|
+
\`\`\`
|
|
678
|
+
|
|
679
|
+
### Write Test Specs
|
|
680
|
+
|
|
681
|
+
Create test specifications in \`test-specs/\`:
|
|
682
|
+
|
|
683
|
+
\`\`\`
|
|
684
|
+
test-specs/
|
|
685
|
+
auth/
|
|
686
|
+
login.txt
|
|
687
|
+
logout.txt
|
|
688
|
+
dashboard/
|
|
689
|
+
overview.txt
|
|
690
|
+
\`\`\`
|
|
691
|
+
|
|
692
|
+
### Run Generated Tests
|
|
693
|
+
|
|
694
|
+
\`\`\`bash
|
|
695
|
+
# Run all generated tests
|
|
696
|
+
npx playwright test
|
|
697
|
+
|
|
698
|
+
# Run specific test
|
|
699
|
+
npx playwright test tests/auth/login.spec.js
|
|
700
|
+
|
|
701
|
+
# Run with UI
|
|
702
|
+
npx playwright test --ui
|
|
703
|
+
\`\`\`
|
|
704
|
+
|
|
705
|
+
## Configuration
|
|
706
|
+
|
|
707
|
+
Edit \`.zibby.config.js\` to customize:
|
|
708
|
+
- Agent settings (model, temperature)
|
|
709
|
+
- Browser settings (headless, viewport)
|
|
710
|
+
- Cloud sync${answers.cloudSync ? ' (enabled)' : ' (disabled)'}
|
|
711
|
+
- Self-healing behavior
|
|
712
|
+
|
|
713
|
+
## Project Structure
|
|
714
|
+
|
|
715
|
+
\`\`\`
|
|
716
|
+
${projectName}/
|
|
717
|
+
āāā .zibby/
|
|
718
|
+
ā āāā graph.js # Workflow definition
|
|
719
|
+
ā āāā nodes/ # Custom nodes
|
|
720
|
+
ā āāā output/ # Workflow execution results (gitignored)
|
|
721
|
+
ā āāā sessions/ # Session artifacts & recordings
|
|
722
|
+
āāā .zibby.config.js # Configuration
|
|
723
|
+
āāā .env # API keys (gitignored)
|
|
724
|
+
āāā test-specs/ # Test specifications (committed)
|
|
725
|
+
ā āāā examples/
|
|
726
|
+
ā āāā example-domain.txt
|
|
727
|
+
āāā tests/ # Generated tests (gitignored)
|
|
728
|
+
\`\`\`
|
|
729
|
+
|
|
730
|
+
## Learn More
|
|
731
|
+
|
|
732
|
+
- Documentation: https://docs.zibby.dev
|
|
733
|
+
- Examples: https://github.com/zibby/examples
|
|
734
|
+
`;
|
|
735
|
+
}
|
|
736
|
+
|