bangonit 0.3.0 → 0.3.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/README.md +69 -37
- package/app/desktopapp/dist/main/index.js +10 -161
- package/app/desktopapp/dist/main/ipc.js +0 -20
- package/app/desktopapp/dist/main/preload.js +0 -2
- package/app/desktopapp/dist/main/tabs.js +0 -10
- package/app/desktopapp/dist/shared/args.js +21 -0
- package/app/desktopapp/package.json +1 -1
- package/app/replay/dist/replay.css +1 -1
- package/app/replay/dist/replay.js +20 -20
- package/app/webapp/.next/BUILD_ID +1 -1
- package/app/webapp/.next/app-build-manifest.json +2 -2
- package/app/webapp/.next/app-path-routes-manifest.json +1 -1
- package/app/webapp/.next/build-manifest.json +2 -2
- package/app/webapp/.next/next-minimal-server.js.nft.json +1 -1
- package/app/webapp/.next/next-server.js.nft.json +1 -1
- package/app/webapp/.next/prerender-manifest.json +1 -1
- package/app/webapp/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/app/webapp/.next/server/app/_not-found.html +1 -1
- package/app/webapp/.next/server/app/_not-found.rsc +1 -1
- package/app/webapp/.next/server/app/app/page.js +3 -7
- package/app/webapp/.next/server/app/app/page_client-reference-manifest.js +1 -1
- package/app/webapp/.next/server/app/app.html +1 -1
- package/app/webapp/.next/server/app/app.rsc +2 -2
- package/app/webapp/.next/server/app/index.html +1 -1
- package/app/webapp/.next/server/app/index.rsc +1 -1
- package/app/webapp/.next/server/app/page_client-reference-manifest.js +1 -1
- package/app/webapp/.next/server/app-paths-manifest.json +2 -2
- package/app/webapp/.next/server/chunks/708.js +1 -1
- package/app/webapp/.next/server/functions-config-manifest.json +1 -1
- package/app/webapp/.next/server/pages/404.html +1 -1
- package/app/webapp/.next/server/pages/500.html +1 -1
- package/app/webapp/.next/server/server-reference-manifest.json +1 -1
- package/app/webapp/.next/static/chunks/app/app/page-0e096497dcb81dae.js +1 -0
- package/app/webapp/.next/static/css/{869d3ff23c36c4b5.css → 38219627f55424f2.css} +1 -1
- package/app/webapp/.next/trace +2 -2
- package/app/webapp/package.json +7 -2
- package/app/webapp/src/shared/api/chat.ts +2 -11
- package/app/webapp/src/shared/components/AppShell.tsx +21 -1
- package/app/webapp/src/shared/components/SessionView.tsx +37 -65
- package/app/webapp/src/shared/lib/browser/mouse.ts +1 -1
- package/app/webapp/src/shared/lib/browser/recorder.ts +3 -3
- package/app/webapp/src/shared/types/global.d.ts +0 -3
- package/bin/app/desktopapp/src/shared/args.js +21 -0
- package/bin/bangonit.js +259 -96
- package/bin/src/cli/bangonit.js +767 -0
- package/package.json +6 -4
- package/app/webapp/.next/static/chunks/app/app/page-d38c1e48d37def82.js +0 -1
- /package/app/webapp/.next/static/{TaLpPsk5rC30wNNcyfUN3 → Qq0OvlQijtcR84Dg9Dgp0}/_buildManifest.js +0 -0
- /package/app/webapp/.next/static/{TaLpPsk5rC30wNNcyfUN3 → Qq0OvlQijtcR84Dg9Dgp0}/_ssgManifest.js +0 -0
package/bin/bangonit.js
CHANGED
|
@@ -267,6 +267,16 @@ function createPrompter() {
|
|
|
267
267
|
};
|
|
268
268
|
}
|
|
269
269
|
// --- init command ---
|
|
270
|
+
// Common timezone offsets from UTC (standard time)
|
|
271
|
+
const TIMEZONE_OFFSETS = {
|
|
272
|
+
"us/eastern": -5, "us/central": -6, "us/mountain": -7, "us/pacific": -8,
|
|
273
|
+
"europe/london": 0, "europe/berlin": 1, "europe/paris": 1,
|
|
274
|
+
"asia/tokyo": 9, "asia/shanghai": 8, "asia/kolkata": 5,
|
|
275
|
+
"australia/sydney": 11,
|
|
276
|
+
};
|
|
277
|
+
function localHourToUtc(localHour, utcOffset) {
|
|
278
|
+
return ((localHour - utcOffset) % 24 + 24) % 24;
|
|
279
|
+
}
|
|
270
280
|
async function initProject() {
|
|
271
281
|
const p = createPrompter();
|
|
272
282
|
console.log(`\n ${c.bold}${c.magenta}Bang On It! init${c.reset}\n`);
|
|
@@ -282,7 +292,7 @@ async function initProject() {
|
|
|
282
292
|
s3Region = await p.ask("S3 region", "us-east-1");
|
|
283
293
|
s3Prefix = await p.ask("S3 prefix", "bangonit");
|
|
284
294
|
}
|
|
285
|
-
|
|
295
|
+
// --- Config file ---
|
|
286
296
|
let toml = `testplans = "${testplans}"\n`;
|
|
287
297
|
toml += `recordings_dir = "${recordingsDir}"\n`;
|
|
288
298
|
if (apiKey) {
|
|
@@ -304,58 +314,148 @@ async function initProject() {
|
|
|
304
314
|
}
|
|
305
315
|
const bangDir = path.join(process.cwd(), ".bangonit");
|
|
306
316
|
fs.mkdirSync(bangDir, { recursive: true });
|
|
307
|
-
const
|
|
308
|
-
fs.writeFileSync(
|
|
309
|
-
console.log(`\n ${c.green}Created${c.reset} .bangonit/config.toml
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const timeout = await p.ask("Test timeout in seconds", "300");
|
|
328
|
-
p.close();
|
|
329
|
-
const trigger = triggerInput === "pr" ? "pull_request" :
|
|
330
|
-
triggerInput === "push" ? "push" :
|
|
331
|
-
"both";
|
|
332
|
-
let triggerYaml;
|
|
333
|
-
if (trigger === "both") {
|
|
334
|
-
triggerYaml = `on:
|
|
335
|
-
push:
|
|
336
|
-
branches: [main, master]
|
|
337
|
-
pull_request:`;
|
|
338
|
-
}
|
|
339
|
-
else if (trigger === "pull_request") {
|
|
340
|
-
triggerYaml = `on:
|
|
341
|
-
pull_request:`;
|
|
342
|
-
}
|
|
343
|
-
else {
|
|
344
|
-
triggerYaml = `on:
|
|
345
|
-
push:
|
|
346
|
-
branches: [main, master]`;
|
|
347
|
-
}
|
|
348
|
-
const workflow = `name: Bang On It! Tests
|
|
317
|
+
const configOutPath = path.join(bangDir, "config.toml");
|
|
318
|
+
fs.writeFileSync(configOutPath, toml);
|
|
319
|
+
console.log(`\n ${c.green}Created${c.reset} .bangonit/config.toml`);
|
|
320
|
+
// --- Test plan directories ---
|
|
321
|
+
const testplanBase = path.join(process.cwd(), testplans);
|
|
322
|
+
const dirs = ["smoke", "acceptance", "regression"];
|
|
323
|
+
for (const dir of dirs) {
|
|
324
|
+
const dirPath = path.join(testplanBase, dir);
|
|
325
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
326
|
+
const gitkeep = path.join(dirPath, ".gitkeep");
|
|
327
|
+
if (!fs.existsSync(gitkeep))
|
|
328
|
+
fs.writeFileSync(gitkeep, "");
|
|
329
|
+
console.log(` ${c.green}Created${c.reset} ${testplans}/${dir}/`);
|
|
330
|
+
}
|
|
331
|
+
// Write example smoke test
|
|
332
|
+
const smokeExample = path.join(testplanBase, "smoke", "homepage.md");
|
|
333
|
+
if (!fs.existsSync(smokeExample)) {
|
|
334
|
+
fs.writeFileSync(smokeExample, `---
|
|
335
|
+
name: Homepage loads
|
|
336
|
+
---
|
|
349
337
|
|
|
350
|
-
|
|
338
|
+
## Steps
|
|
339
|
+
1. Navigate to http://localhost:3000
|
|
340
|
+
2. Verify the page loads with a heading
|
|
341
|
+
3. Verify there are no console errors
|
|
342
|
+
`);
|
|
343
|
+
console.log(` ${c.green}Created${c.reset} ${testplans}/smoke/homepage.md`);
|
|
344
|
+
}
|
|
345
|
+
// --- Claude Code skills ---
|
|
346
|
+
const claudeSkillsDir = path.join(process.cwd(), ".claude", "skills");
|
|
347
|
+
const testSkillDir = path.join(claudeSkillsDir, "test");
|
|
348
|
+
fs.mkdirSync(testSkillDir, { recursive: true });
|
|
349
|
+
fs.writeFileSync(path.join(testSkillDir, "SKILL.md"), `---
|
|
350
|
+
name: test
|
|
351
|
+
description: Run Bang On It! E2E tests locally. Pass test plan files or a filter as $ARGUMENTS (e.g. "testplans/smoke/" or "-t login"). With no arguments, runs all test plans.
|
|
352
|
+
tools: Bash, Read
|
|
353
|
+
---
|
|
351
354
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
355
|
+
# Run E2E Tests
|
|
356
|
+
|
|
357
|
+
Run Bang On It! end-to-end tests locally.
|
|
358
|
+
|
|
359
|
+
**Arguments:** $ARGUMENTS
|
|
360
|
+
|
|
361
|
+
## Instructions
|
|
358
362
|
|
|
363
|
+
1. If $ARGUMENTS is empty, run all test plans:
|
|
364
|
+
\`\`\`bash
|
|
365
|
+
boi run --record
|
|
366
|
+
\`\`\`
|
|
367
|
+
|
|
368
|
+
2. If $ARGUMENTS contains file paths or directories, run those:
|
|
369
|
+
\`\`\`bash
|
|
370
|
+
boi run $ARGUMENTS --record
|
|
371
|
+
\`\`\`
|
|
372
|
+
|
|
373
|
+
3. If $ARGUMENTS contains a filter (e.g. "login", "checkout"), run with filter:
|
|
374
|
+
\`\`\`bash
|
|
375
|
+
boi run -t $ARGUMENTS --record
|
|
376
|
+
\`\`\`
|
|
377
|
+
|
|
378
|
+
4. Wait for tests to complete. Report results and recording paths.
|
|
379
|
+
|
|
380
|
+
5. If tests fail, read the test plan file and the output to diagnose the failure. Suggest whether the test plan needs updating or there's a real bug.
|
|
381
|
+
`);
|
|
382
|
+
console.log(` ${c.green}Created${c.reset} .claude/skills/test/SKILL.md`);
|
|
383
|
+
const createTestSkillDir = path.join(claudeSkillsDir, "create-test");
|
|
384
|
+
fs.mkdirSync(createTestSkillDir, { recursive: true });
|
|
385
|
+
fs.writeFileSync(path.join(createTestSkillDir, "SKILL.md"), `---
|
|
386
|
+
name: create-test
|
|
387
|
+
description: Create new Bang On It! test plan(s). Pass a description of what to test as $ARGUMENTS, or omit to auto-generate from git changes.
|
|
388
|
+
tools: Bash, Read, Write, Glob, Grep
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
# Create Test Plan
|
|
392
|
+
|
|
393
|
+
Create new Bang On It! test plan files.
|
|
394
|
+
|
|
395
|
+
**What to test:** $ARGUMENTS
|
|
396
|
+
|
|
397
|
+
## Instructions
|
|
398
|
+
|
|
399
|
+
### Step 0: Determine what to test
|
|
400
|
+
|
|
401
|
+
- If $ARGUMENTS is provided, use it as the description of what to test.
|
|
402
|
+
- If $ARGUMENTS is empty, auto-discover from git changes:
|
|
403
|
+
1. Run \`git log master..HEAD --oneline\` and \`git diff master...HEAD --stat\` to see what changed on this branch.
|
|
404
|
+
2. If no branch divergence, run \`git diff HEAD --stat\` and \`git diff HEAD\` for uncommitted changes.
|
|
405
|
+
3. If still nothing, run \`git log -1 --format="%H %s"\` and \`git show HEAD --stat\` for the latest commit.
|
|
406
|
+
4. Analyze the changes and create test plan(s) covering them. Bug fixes get regression tests, new features get acceptance tests.
|
|
407
|
+
5. Skip changes that are already covered by existing test plans, pure refactors, docs, CI, or dependency updates.
|
|
408
|
+
|
|
409
|
+
### Step 1: Determine which directory the test belongs in
|
|
410
|
+
- \`${testplans}/smoke/\` — Quick sanity checks (app loads, critical path works). Keep smoke tests minimal — they run on every commit so they must be fast. Only add here if it tests truly fundamental functionality. Prefer acceptance/ for most tests.
|
|
411
|
+
- \`${testplans}/acceptance/\` — Core user journeys and happy paths. This is the default for most new tests.
|
|
412
|
+
- \`${testplans}/regression/\` — Bug fixes and edge cases. Use when the description references a bug or issue.
|
|
413
|
+
|
|
414
|
+
2. Read existing test plans in that directory to understand conventions:
|
|
415
|
+
\`\`\`bash
|
|
416
|
+
ls ${testplans}/smoke/ ${testplans}/acceptance/ ${testplans}/regression/
|
|
417
|
+
\`\`\`
|
|
418
|
+
|
|
419
|
+
3. Read the codebase to understand what UI elements and flows are involved. Look at routes, components, and pages relevant to the test.
|
|
420
|
+
|
|
421
|
+
4. Create the test plan file:
|
|
422
|
+
- Filename: kebab-case, e.g. \`password-reset.md\`
|
|
423
|
+
- Use this format:
|
|
424
|
+
|
|
425
|
+
\`\`\`markdown
|
|
426
|
+
---
|
|
427
|
+
name: Descriptive test name
|
|
428
|
+
retries: 1
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
## Steps
|
|
432
|
+
1. Navigate to the relevant page
|
|
433
|
+
2. Perform the action being tested
|
|
434
|
+
3. Verify the expected outcome
|
|
435
|
+
\`\`\`
|
|
436
|
+
|
|
437
|
+
5. Keep steps concise and actionable. Write from the user's perspective — describe what to click, type, and verify. Don't reference CSS selectors or implementation details.
|
|
438
|
+
|
|
439
|
+
6. Output the path to the created file.
|
|
440
|
+
`);
|
|
441
|
+
console.log(` ${c.green}Created${c.reset} .claude/skills/create-test/SKILL.md`);
|
|
442
|
+
// --- CI setup ---
|
|
443
|
+
console.log("");
|
|
444
|
+
const setupCi = await p.askChoice("Set up GitHub Actions CI?", ["y", "n"], "y");
|
|
445
|
+
if (setupCi === "y") {
|
|
446
|
+
console.log("");
|
|
447
|
+
const baseImage = await p.ask("GitHub Actions runner", "ubuntu-latest");
|
|
448
|
+
const nodeVersion = await p.ask("Node.js version", "20");
|
|
449
|
+
const setupCommand = await p.ask("Setup command", "npm install && npm run build");
|
|
450
|
+
const startCommand = await p.ask("Command to start your web server", "npm start &");
|
|
451
|
+
const waitUrl = await p.ask("URL to wait for before testing", "http://localhost:3000");
|
|
452
|
+
const timeout = await p.ask("Test timeout in seconds", "300");
|
|
453
|
+
const tzNames = Object.keys(TIMEZONE_OFFSETS).join(", ");
|
|
454
|
+
const tz = await p.ask(`Timezone for full run (${tzNames})`, "us/eastern");
|
|
455
|
+
const utcOffset = TIMEZONE_OFFSETS[tz.toLowerCase()] ?? -5;
|
|
456
|
+
const fullUtcHour = localHourToUtc(18, utcOffset);
|
|
457
|
+
const timeoutMinutes = Math.ceil(parseInt(timeout) / 60) + 5;
|
|
458
|
+
const stepsYaml = `
|
|
359
459
|
steps:
|
|
360
460
|
- uses: actions/checkout@v4
|
|
361
461
|
|
|
@@ -378,12 +478,25 @@ jobs:
|
|
|
378
478
|
run: npx wait-on ${waitUrl} --timeout 30000
|
|
379
479
|
|
|
380
480
|
- name: Install bangonit
|
|
381
|
-
run: npm install -g bangonit
|
|
481
|
+
run: npm install -g bangonit`;
|
|
482
|
+
const smokeWorkflow = `name: "Bang On It! Smoke Tests"
|
|
382
483
|
|
|
383
|
-
|
|
484
|
+
on:
|
|
485
|
+
push:
|
|
486
|
+
branches: [main, master]
|
|
487
|
+
pull_request:
|
|
488
|
+
|
|
489
|
+
jobs:
|
|
490
|
+
smoke:
|
|
491
|
+
runs-on: ${baseImage}
|
|
492
|
+
timeout-minutes: ${timeoutMinutes}
|
|
493
|
+
env:
|
|
494
|
+
ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
|
|
495
|
+
${stepsYaml}
|
|
496
|
+
|
|
497
|
+
- name: Run smoke tests
|
|
384
498
|
run: |
|
|
385
|
-
xvfb-run --auto-servernum boi run ${
|
|
386
|
-
--headless --exit \\
|
|
499
|
+
xvfb-run --auto-servernum boi run ${testplans}/smoke/ \\
|
|
387
500
|
--timeout ${timeout} \\
|
|
388
501
|
--output bangonit-output.json --record
|
|
389
502
|
|
|
@@ -391,19 +504,57 @@ jobs:
|
|
|
391
504
|
if: always()
|
|
392
505
|
uses: actions/upload-artifact@v4
|
|
393
506
|
with:
|
|
394
|
-
name: bangonit-results
|
|
507
|
+
name: bangonit-smoke-results
|
|
395
508
|
path: |
|
|
396
509
|
bangonit-output.json
|
|
397
510
|
recordings/
|
|
398
511
|
if-no-files-found: ignore
|
|
399
512
|
`;
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
513
|
+
const fullWorkflow = `name: "Bang On It! Full Tests"
|
|
514
|
+
|
|
515
|
+
on:
|
|
516
|
+
schedule:
|
|
517
|
+
- cron: '0 ${fullUtcHour} * * *'
|
|
518
|
+
workflow_dispatch:
|
|
519
|
+
|
|
520
|
+
jobs:
|
|
521
|
+
full:
|
|
522
|
+
runs-on: ${baseImage}
|
|
523
|
+
timeout-minutes: ${timeoutMinutes * 3}
|
|
524
|
+
env:
|
|
525
|
+
ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
|
|
526
|
+
${stepsYaml}
|
|
527
|
+
|
|
528
|
+
- name: Run all tests
|
|
529
|
+
run: |
|
|
530
|
+
xvfb-run --auto-servernum boi run \\
|
|
531
|
+
--timeout ${timeout} \\
|
|
532
|
+
--output bangonit-output.json --record
|
|
533
|
+
|
|
534
|
+
- name: Upload test results
|
|
535
|
+
if: always()
|
|
536
|
+
uses: actions/upload-artifact@v4
|
|
537
|
+
with:
|
|
538
|
+
name: bangonit-full-results
|
|
539
|
+
path: |
|
|
540
|
+
bangonit-output.json
|
|
541
|
+
recordings/
|
|
542
|
+
if-no-files-found: ignore
|
|
543
|
+
`;
|
|
544
|
+
const outDir = path.join(process.cwd(), ".github", "workflows");
|
|
545
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
546
|
+
const smokePath = path.join(outDir, "bangonit-smoke.yml");
|
|
547
|
+
fs.writeFileSync(smokePath, smokeWorkflow);
|
|
548
|
+
console.log(`\n ${c.green}Created${c.reset} ${path.relative(process.cwd(), smokePath)}`);
|
|
549
|
+
const fullPath = path.join(outDir, "bangonit-full.yml");
|
|
550
|
+
fs.writeFileSync(fullPath, fullWorkflow);
|
|
551
|
+
console.log(` ${c.green}Created${c.reset} ${path.relative(process.cwd(), fullPath)}`);
|
|
552
|
+
console.log(`\n ${c.yellow}Required GitHub secret:${c.reset}`);
|
|
553
|
+
console.log(` ${c.dim} ANTHROPIC_API_KEY${c.reset} — your Anthropic API key`);
|
|
554
|
+
console.log(`\n ${c.dim}Smoke tests run on every push/PR.${c.reset}`);
|
|
555
|
+
console.log(` ${c.dim}Full tests run all test plans daily at 6pm ${tz} (${fullUtcHour}:00 UTC).${c.reset}`);
|
|
556
|
+
}
|
|
557
|
+
p.close();
|
|
407
558
|
console.log("");
|
|
408
559
|
}
|
|
409
560
|
async function run(argv, config) {
|
|
@@ -418,6 +569,13 @@ async function run(argv, config) {
|
|
|
418
569
|
const recordingsDir = config.recordings_dir
|
|
419
570
|
? path.resolve(process.cwd(), config.recordings_dir)
|
|
420
571
|
: path.join(process.cwd(), "recordings");
|
|
572
|
+
// Validate test plan files exist before launching anything
|
|
573
|
+
for (const file of argv.files) {
|
|
574
|
+
const absPath = path.isAbsolute(file) ? file : path.join(process.cwd(), file);
|
|
575
|
+
if (!fs.existsSync(absPath)) {
|
|
576
|
+
die(`Test plan file not found: ${file}`);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
421
579
|
// Discover test plans if no files/plan/auto specified
|
|
422
580
|
const files = [...argv.files];
|
|
423
581
|
if (files.length === 0 && !argv.plan && !argv.auto && config.testplans) {
|
|
@@ -433,30 +591,31 @@ async function run(argv, config) {
|
|
|
433
591
|
else if (files.length === 0 && !argv.plan && !argv.auto && argv.filter) {
|
|
434
592
|
die(`--filter requires a testplans directory configured in .bangonit/config.toml`);
|
|
435
593
|
}
|
|
436
|
-
//
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
594
|
+
// Hint when launching interactive UI with no config
|
|
595
|
+
if (files.length === 0 && !argv.plan && !argv.auto) {
|
|
596
|
+
if (!config.testplans) {
|
|
597
|
+
console.log(`${c.dim}No test plans specified. Launching interactive UI.${c.reset}`);
|
|
598
|
+
console.log(`${c.dim}Tip: Run ${c.reset}boi init${c.dim} to set up a config file, or pass test plan files directly.${c.reset}\n`);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
// Build structured args for Electron (passed via BANGONIT_ARGS env var)
|
|
602
|
+
const electronArgs = {
|
|
603
|
+
testPlanFiles: files,
|
|
604
|
+
headless: argv.headless,
|
|
605
|
+
exit: argv.exit,
|
|
606
|
+
json: argv.json,
|
|
607
|
+
console: argv.console,
|
|
608
|
+
record: argv.record,
|
|
609
|
+
retries: argv.retries ?? 0,
|
|
610
|
+
auto: argv.auto,
|
|
611
|
+
output: argv.output || null,
|
|
612
|
+
plan: argv.plan || null,
|
|
613
|
+
prompt: argv.prompt || null,
|
|
614
|
+
concurrency: argv.concurrency ?? 1,
|
|
615
|
+
timeout: argv.timeout ?? 0,
|
|
616
|
+
cwd: process.cwd(),
|
|
617
|
+
recordingsDir: argv.record ? recordingsDir : null,
|
|
618
|
+
};
|
|
460
619
|
const PORT = await getFreePort();
|
|
461
620
|
fs.mkdirSync(LOGS_DIR, { recursive: true });
|
|
462
621
|
const nextDir = path.join(WEBAPP_DIR, ".next");
|
|
@@ -479,7 +638,15 @@ async function run(argv, config) {
|
|
|
479
638
|
const webappLog = fs.createWriteStream(path.join(LOGS_DIR, "webapp.log"));
|
|
480
639
|
webappProc.stdout.pipe(webappLog);
|
|
481
640
|
webappProc.stderr.pipe(webappLog);
|
|
641
|
+
let webappCrashed = false;
|
|
642
|
+
webappProc.on("exit", (code) => {
|
|
643
|
+
if (!webappCrashed && code !== null && code !== 0) {
|
|
644
|
+
webappCrashed = true;
|
|
645
|
+
die(`Webapp server crashed (exit code ${code}). Check logs/webapp.log for details.`);
|
|
646
|
+
}
|
|
647
|
+
});
|
|
482
648
|
const cleanup = () => {
|
|
649
|
+
webappCrashed = true; // suppress crash message during normal shutdown
|
|
483
650
|
try {
|
|
484
651
|
webappProc.kill();
|
|
485
652
|
}
|
|
@@ -512,15 +679,11 @@ async function run(argv, config) {
|
|
|
512
679
|
die("Error: electron not found. Run `npm install` in app/desktopapp.");
|
|
513
680
|
}
|
|
514
681
|
}
|
|
515
|
-
const
|
|
516
|
-
".",
|
|
517
|
-
...electronArgs,
|
|
518
|
-
"--cwd", process.cwd(),
|
|
519
|
-
];
|
|
520
|
-
const electronProc = (0, child_process_1.spawn)(electronPath, finalArgs, {
|
|
682
|
+
const electronProc = (0, child_process_1.spawn)(electronPath, ["."], {
|
|
521
683
|
cwd: DESKTOP_DIR,
|
|
522
684
|
env: {
|
|
523
685
|
...process.env,
|
|
686
|
+
BANGONIT_ARGS: JSON.stringify(electronArgs),
|
|
524
687
|
WEBAPP_URL: `http://localhost:${PORT}/app`,
|
|
525
688
|
NODE_ENV: process.env.NODE_ENV || "production",
|
|
526
689
|
},
|
|
@@ -560,8 +723,7 @@ const ciDefaults = is_ci_1.default ? { headless: true, exit: true } : {};
|
|
|
560
723
|
(0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
|
|
561
724
|
.scriptName("boi")
|
|
562
725
|
.usage("Usage: $0 <command> [options]")
|
|
563
|
-
.command("init", "
|
|
564
|
-
.command("init-ci", "Generate a GitHub Actions workflow", {}, () => { initCi(); })
|
|
726
|
+
.command("init", "Set up Bang On It! (config, test plans, and optionally CI)", {}, () => { initProject(); })
|
|
565
727
|
.command(["run [files..]", "$0"], "Run test plans (or launch interactive UI)", (y) => y
|
|
566
728
|
.positional("files", { type: "string", array: true, default: [], describe: "Test plan files" })
|
|
567
729
|
.option("filter", { alias: "t", type: "string", describe: "Filter test plans by name substring" })
|
|
@@ -570,6 +732,7 @@ const ciDefaults = is_ci_1.default ? { headless: true, exit: true } : {};
|
|
|
570
732
|
.option("auto", { type: "boolean", default: false, describe: "Auto-generate test plan from git state" })
|
|
571
733
|
.option("prompt", { type: "string", describe: "Additional instructions appended to test plan" })
|
|
572
734
|
.option("record", { type: "boolean", default: false, describe: "Record session replay" })
|
|
735
|
+
.option("retries", { type: "number", describe: "Retry failed tests N times (overrides test plan frontmatter)" })
|
|
573
736
|
.option("headless", { type: "boolean", default: ciDefaults.headless ?? false, describe: "Run without showing the browser window" })
|
|
574
737
|
.option("exit", { type: "boolean", default: ciDefaults.exit ?? false, describe: "Exit immediately after tests complete" })
|
|
575
738
|
.option("json", { type: "boolean", default: false, describe: "Stream NDJSON events to stdout" })
|
|
@@ -585,6 +748,7 @@ const ciDefaults = is_ci_1.default ? { headless: true, exit: true } : {};
|
|
|
585
748
|
auto: argv.auto,
|
|
586
749
|
prompt: argv.prompt,
|
|
587
750
|
record: argv.record,
|
|
751
|
+
retries: argv.retries,
|
|
588
752
|
headless: argv.headless,
|
|
589
753
|
exit: argv.exit,
|
|
590
754
|
json: argv.json,
|
|
@@ -599,8 +763,7 @@ const ciDefaults = is_ci_1.default ? { headless: true, exit: true } : {};
|
|
|
599
763
|
.example("$0 run --auto", "Auto-discover from git state")
|
|
600
764
|
.example("$0 run -t checkout", "Run test plans matching 'checkout'")
|
|
601
765
|
.example("$0 run", "Launch interactive UI")
|
|
602
|
-
.example("$0 init", "
|
|
603
|
-
.example("$0 init-ci", "Set up GitHub Actions")
|
|
766
|
+
.example("$0 init", "Set up config, test plans, and CI")
|
|
604
767
|
.strict()
|
|
605
768
|
.help()
|
|
606
769
|
.alias("h", "help")
|