552 lines
21 KiB
JavaScript
552 lines
21 KiB
JavaScript
// Functional Test Script for Bill Tracker
|
||
// Tests: Notes feature (per-bill per-month), Bill creation, Autopay/2FA toggles, Payments
|
||
|
||
const { chromium } = require('playwright');
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const BASE_URL = 'http://localhost:3033';
|
||
const TEST_USER = 'admin';
|
||
const TEST_PASS = 'admin123';
|
||
|
||
// Test Results
|
||
const results = {
|
||
startTime: new Date().toISOString(),
|
||
login: 'PENDING',
|
||
billsCreated: 'PENDING',
|
||
notesFeature: {
|
||
perBillPerMonth: 'PENDING',
|
||
persistence: 'PENDING',
|
||
monthSwitching: 'PENDING',
|
||
issues: []
|
||
},
|
||
otherFeatures: {
|
||
billCreation: 'PENDING',
|
||
autopayToggle: 'PENDING',
|
||
twoFactorToggle: 'PENDING',
|
||
paymentTracking: 'PENDING',
|
||
billEdits: 'PENDING',
|
||
issues: []
|
||
},
|
||
finalSummary: 'PENDING'
|
||
};
|
||
|
||
async function runTests() {
|
||
console.log('🚀 Starting functional tests...');
|
||
|
||
const browser = await chromium.launch({
|
||
headless: true,
|
||
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
||
});
|
||
|
||
const context = await browser.newContext({
|
||
ignoreHTTPSErrors: true,
|
||
viewport: { width: 1920, height: 1080 }
|
||
});
|
||
|
||
const page = await context.newPage();
|
||
|
||
try {
|
||
// 1. Login
|
||
console.log('\n1️⃣ Testing Login...');
|
||
await testLogin(page);
|
||
|
||
// 2. Create 20 test bills
|
||
console.log('\n2️⃣ Creating 20 test bills...');
|
||
await createTestBills(page);
|
||
|
||
// 3. Test Notes Feature (per-bill, per-month)
|
||
console.log('\n3️⃣ Testing Notes Feature...');
|
||
await testNotesFeature(page);
|
||
|
||
// 4. Test other features
|
||
console.log('\n4️⃣ Testing Other Features...');
|
||
await testOtherFeatures(page);
|
||
|
||
// 5. Summary
|
||
console.log('\n5️⃣ Generating Summary...');
|
||
generateSummary();
|
||
|
||
} catch (error) {
|
||
console.error('❌ Test failed:', error.message);
|
||
results.finalSummary = 'FAILED - ' + error.message;
|
||
} finally {
|
||
await browser.close();
|
||
saveResults();
|
||
}
|
||
}
|
||
|
||
async function testLogin(page) {
|
||
try {
|
||
await page.goto(BASE_URL);
|
||
await page.waitForSelector('input[name="username"]');
|
||
|
||
await page.fill('input[name="username"]', TEST_USER);
|
||
await page.fill('input[name="password"]', TEST_PASS);
|
||
await page.click('button[type="submit"]');
|
||
|
||
await page.waitForSelector('.tracker-container', { timeout: 10000 });
|
||
|
||
results.login = 'PASS';
|
||
console.log('✅ Login successful');
|
||
} catch (error) {
|
||
results.login = 'FAIL';
|
||
console.error('❌ Login failed:', error.message);
|
||
}
|
||
}
|
||
|
||
async function createTestBills(page) {
|
||
try {
|
||
await page.goto(BASE_URL + '/bills');
|
||
await page.waitForSelector('button:has-text("Add Bill")');
|
||
|
||
// Create 20 bills with varied data
|
||
const bills = [
|
||
// Mix of categories: Housing, Utilities, Food, Transport, Entertainment, Health, Subscriptions, Other
|
||
{ name: 'Rent', category: 'Housing', dueDay: 1, amount: 1200, autopay: true, twoFA: false },
|
||
{ name: 'Electric', category: 'Utilities', dueDay: 5, amount: 85, autopay: true, twoFA: false },
|
||
{ name: 'Groceries', category: 'Food', dueDay: 10, amount: 400, autopay: false, twoFA: false },
|
||
{ name: 'Gas', category: 'Transport', dueDay: 15, amount: 50, autopay: true, twoFA: true },
|
||
{ name: 'Netflix', category: 'Subscriptions', dueDay: 20, amount: 15, autopay: true, twoFA: false },
|
||
{ name: 'Gym', category: 'Health', dueDay: 1, amount: 30, autopay: true, twoFA: false },
|
||
{ name: 'Phone', category: 'Subscriptions', dueDay: 3, amount: 60, autopay: true, twoFA: true },
|
||
{ name: 'Water', category: 'Utilities', dueDay: 8, amount: 45, autopay: false, twoFA: false },
|
||
{ name: 'Internet', category: 'Utilities', dueDay: 12, amount: 70, autopay: true, twoFA: false },
|
||
{ name: 'Netflix Family', category: 'Subscriptions', dueDay: 20, amount: 20, autopay: true, twoFA: false },
|
||
{ name: 'Amazon Prime', category: 'Subscriptions', dueDay: 22, amount: 13, autopay: true, twoFA: false },
|
||
{ name: 'Microsoft 365', category: 'Subscriptions', dueDay: 25, amount: 10, autopay: true, twoFA: true },
|
||
{ name: 'Spotify', category: 'Subscriptions', dueDay: 28, amount: 10, autopay: true, twoFA: false },
|
||
{ name: 'Dental', category: 'Health', dueDay: 15, amount: 100, autopay: false, twoFA: false },
|
||
{ name: 'Insurance', category: 'Health', dueDay: 1, amount: 200, autopay: true, twoFA: true },
|
||
{ name: 'Car Payment', category: 'Transport', dueDay: 5, amount: 350, autopay: true, twoFA: false },
|
||
{ name: 'Parking', category: 'Transport', dueDay: 15, amount: 25, autopay: false, twoFA: false },
|
||
{ name: 'Movies', category: 'Entertainment', dueDay: 10, amount: 40, autopay: false, twoFA: false },
|
||
{ name: 'Restaurant', category: 'Food', dueDay: 20, amount: 80, autopay: false, twoFA: false },
|
||
{ name: 'Other', category: 'Other', dueDay: 25, amount: 50, autopay: false, twoFA: true },
|
||
];
|
||
|
||
for (let i = 0; i < bills.length; i++) {
|
||
const bill = bills[i];
|
||
|
||
await page.click('button:has-text("Add Bill")');
|
||
await page.waitForSelector('text=Add Bill');
|
||
|
||
await page.fill('input[name="name"]', bill.name);
|
||
await page.fill('input[name="expected_amount"]', String(bill.amount));
|
||
|
||
// Fill due day
|
||
await page.fill('input[name="due_day"]', String(bill.dueDay));
|
||
|
||
// Select category
|
||
await page.click('button:has-text("Select category")');
|
||
await page.waitForSelector(`button:has-text("${bill.category}")`);
|
||
await page.click(`button:has-text("${bill.category}")`);
|
||
|
||
// Set autopay if specified
|
||
if (bill.autopay) {
|
||
await page.click('label:has-text("Autopay")');
|
||
}
|
||
|
||
// Set 2FA if specified
|
||
if (bill.twoFA) {
|
||
await page.click('label:has-text("Two-factor")');
|
||
}
|
||
|
||
await page.click('button:has-text("Save")');
|
||
await page.waitForTimeout(500);
|
||
}
|
||
|
||
// Verify all bills were created
|
||
const billCount = await page.locator('.bill-row').count();
|
||
|
||
if (billCount >= 20) {
|
||
results.billsCreated = `PASS (${billCount} bills)`;
|
||
console.log(`✅ Created ${billCount} test bills`);
|
||
} else {
|
||
results.billsCreated = `FAIL (expected 20, got ${billCount})`;
|
||
console.error(`❌ Only created ${billCount} bills`);
|
||
}
|
||
|
||
} catch (error) {
|
||
results.billsCreated = `FAIL - ${error.message}`;
|
||
console.error('❌ Bill creation failed:', error.message);
|
||
}
|
||
}
|
||
|
||
async function testNotesFeature(page) {
|
||
try {
|
||
await page.goto(BASE_URL + '/tracker');
|
||
await page.waitForSelector('.tracker-container', { timeout: 10000 });
|
||
|
||
// Test 1: Add notes to all 20 bills for current month
|
||
console.log(' Testing: Add notes to all bills...');
|
||
const noteInputs = await page.locator('.notes-cell input, input[type="text"][placeholder*="notes"]');
|
||
|
||
// Wait for bills to load
|
||
await page.waitForTimeout(2000);
|
||
|
||
// Get all bill rows
|
||
const billRows = await page.locator('.bill-row, .react-flow__node, .bill-card').all();
|
||
|
||
console.log(` Found ${billRows.length} bill rows`);
|
||
|
||
if (billRows.length < 20) {
|
||
results.notesFeature.perBillPerMonth = `FAIL (only ${billRows.length} bills on page)`;
|
||
results.notesFeature.issues.push(`Expected 20 bills, found ${billRows.length}`);
|
||
console.error(` ⚠️ Expected 20 bills, found ${billRows.length}`);
|
||
return;
|
||
}
|
||
|
||
// Add notes to each bill
|
||
const notesAdded = [];
|
||
for (let i = 0; i < Math.min(20, billRows.length); i++) {
|
||
try {
|
||
// Try to find notes input in the row
|
||
const row = billRows[i];
|
||
await row.hover();
|
||
|
||
// Wait for notes input to be ready
|
||
await page.waitForTimeout(100);
|
||
|
||
// Find and fill notes
|
||
const notesSelector = 'input[placeholder*="notes"], input[placeholder*="Notes"], input.notes-input';
|
||
const notesInput = await row.locator(notesSelector).first();
|
||
|
||
if (await notesInput.count() > 0) {
|
||
const billName = await row.locator('.bill-name, h3, .name').first().textContent() || `Bill ${i + 1}`;
|
||
const noteText = `Test note for ${billName} - ${new Date().toISOString().slice(0, 10)}`;
|
||
|
||
await notesInput.fill(noteText);
|
||
await page.waitForTimeout(500);
|
||
await notesInput.blur();
|
||
|
||
notesAdded.push({ index: i + 1, bill: billName, note: noteText });
|
||
}
|
||
} catch (err) {
|
||
console.error(` Error on bill ${i + 1}:`, err.message);
|
||
}
|
||
}
|
||
|
||
if (notesAdded.length > 0) {
|
||
results.notesFeature.perBillPerMonth = 'PASS (added notes to ' + notesAdded.length + ' bills)';
|
||
console.log(` ✅ Added notes to ${notesAdded.length} bills`);
|
||
} else {
|
||
results.notesFeature.perBillPerMonth = 'FAIL (no notes inputs found)';
|
||
results.notesFeature.issues.push('No notes input elements found');
|
||
}
|
||
|
||
// Test 2: Verify notes persist after refresh
|
||
console.log(' Testing: Notes persistence after refresh...');
|
||
await page.reload();
|
||
await page.waitForTimeout(2000);
|
||
|
||
// Check if notes are still there
|
||
const pageContent = await page.content();
|
||
const notesPersisted = notesAdded.filter(n => pageContent.includes(n.note.substring(0, 20)));
|
||
|
||
if (notesPersisted.length >= Math.floor(notesAdded.length * 0.8)) { // Allow 20% tolerance
|
||
results.notesFeature.persistence = 'PASS';
|
||
console.log(` ✅ Notes persisted (${notesPersisted.length}/${notesAdded.length})`);
|
||
} else {
|
||
results.notesFeature.persistence = 'FAIL';
|
||
results.notesFeature.issues.push(`Only ${notesPersisted.length}/${notesAdded.length} notes persisted`);
|
||
console.error(` ❌ Notes did not persist well`);
|
||
}
|
||
|
||
// Test 3: Test month switching
|
||
console.log(' Testing: Month switching behavior...');
|
||
|
||
// Change to a different month
|
||
const nextMonthBtn = await page.locator('.month-nav .chevron-right, button:has-text(">"), button:has-text("Next")').first();
|
||
if (await nextMonthBtn.count() > 0) {
|
||
await nextMonthBtn.click();
|
||
await page.waitForTimeout(1000);
|
||
|
||
// Verify notes are blank (or reset) for the new month
|
||
const newMonthNotes = await page.locator('.notes-cell input, input.notes-input').count();
|
||
console.log(` Found ${newMonthNotes} notes inputs in new month`);
|
||
|
||
// Change back to original month
|
||
const prevMonthBtn = await page.locator('.month-nav .chevron-left, button:has-text("<"), button:has-text("Previous")').first();
|
||
if (await prevMonthBtn.count() > 0) {
|
||
await prevMonthBtn.click();
|
||
await page.waitForTimeout(1000);
|
||
|
||
// Verify original notes are preserved
|
||
const contentAfterSwitch = await page.content();
|
||
const preservedCount = notesAdded.filter(n => contentAfterSwitch.includes(n.note.substring(0, 20))).length;
|
||
|
||
if (preservedCount >= Math.floor(notesAdded.length * 0.8)) {
|
||
results.notesFeature.monthSwitching = 'PASS';
|
||
console.log(` ✅ Notes preserved after month switch (${preservedCount}/${notesAdded.length})`);
|
||
} else {
|
||
results.notesFeature.monthSwitching = 'FAIL';
|
||
results.notesFeature.issues.push(`Only ${preservedCount}/${notesAdded.length} notes preserved after month switch`);
|
||
console.error(` ❌ Notes not preserved well after month switch`);
|
||
}
|
||
}
|
||
} else {
|
||
results.notesFeature.monthSwitching = 'SKIP (no month navigation found)';
|
||
console.log(' ⚠️ Could not test month switching (no navigation found)');
|
||
}
|
||
|
||
} catch (error) {
|
||
results.notesFeature.perBillPerMonth = 'FAIL - ' + error.message;
|
||
console.error('❌ Notes feature test failed:', error.message);
|
||
}
|
||
}
|
||
|
||
async function testOtherFeatures(page) {
|
||
try {
|
||
await page.goto(BASE_URL + '/tracker');
|
||
await page.waitForTimeout(2000);
|
||
|
||
// Test 1: Autopay toggle
|
||
console.log(' Testing: Autopay toggle...');
|
||
const autopayToggle = await page.locator('.autopay-toggle, input[type="checkbox"][name*="autopay"], .autopay-switch').first();
|
||
if (await autopayToggle.count() > 0) {
|
||
const isChecked = await autopayToggle.isChecked();
|
||
await autopayToggle.click();
|
||
await page.waitForTimeout(500);
|
||
|
||
// Verify state changed
|
||
const newState = await autopayToggle.isChecked();
|
||
if (newState !== isChecked) {
|
||
results.otherFeatures.autopayToggle = 'PASS';
|
||
console.log(' ✅ Autopay toggle works');
|
||
} else {
|
||
results.otherFeatures.autopayToggle = 'FAIL';
|
||
results.otherFeatures.issues.push('Autopay toggle did not change state');
|
||
console.error(' ❌ Autopay toggle did not change state');
|
||
}
|
||
} else {
|
||
results.otherFeatures.autopayToggle = 'SKIP (no toggle found)';
|
||
console.log(' ⚠️ Autopay toggle not found');
|
||
}
|
||
|
||
// Test 2: Two-factor toggle
|
||
console.log(' Testing: Two-factor toggle...');
|
||
const twoFactorToggle = await page.locator('.two-factor-toggle, input[type="checkbox"][name*="2fa"], .two-factor-switch').first();
|
||
if (await twoFactorToggle.count() > 0) {
|
||
const isChecked = await twoFactorToggle.isChecked();
|
||
await twoFactorToggle.click();
|
||
await page.waitForTimeout(500);
|
||
|
||
const newState = await twoFactorToggle.isChecked();
|
||
if (newState !== isChecked) {
|
||
results.otherFeatures.twoFactorToggle = 'PASS';
|
||
console.log(' ✅ Two-factor toggle works');
|
||
} else {
|
||
results.otherFeatures.twoFactorToggle = 'FAIL';
|
||
results.otherFeatures.issues.push('Two-factor toggle did not change state');
|
||
console.error(' ❌ Two-factor toggle did not change state');
|
||
}
|
||
} else {
|
||
results.otherFeatures.twoFactorToggle = 'SKIP (no toggle found)';
|
||
console.log(' ⚠️ Two-factor toggle not found');
|
||
}
|
||
|
||
// Test 3: Payment tracking
|
||
console.log(' Testing: Payment tracking...');
|
||
const unpaidBills = await page.locator('.bill-row.paid-0, .bill-row.unpaid, .bill.status-unpaid').count();
|
||
|
||
if (unpaidBills > 0) {
|
||
const firstUnpaid = await page.locator('.bill-row.paid-0, .bill-row.unpaid, .bill.status-unpaid').first();
|
||
await firstUnpaid.hover();
|
||
await page.waitForTimeout(500);
|
||
|
||
// Try to mark as paid
|
||
const payBtn = await firstUnpaid.locator('button:has-text("Pay"), button:has-text("Mark Paid"), .pay-btn').first();
|
||
if (await payBtn.count() > 0) {
|
||
await payBtn.click();
|
||
await page.waitForTimeout(1000);
|
||
|
||
// Verify it moved to paid status
|
||
const paidStatus = await page.locator('.status-paid, .paid, .bg-emerald').count();
|
||
if (paidStatus > 0) {
|
||
results.otherFeatures.paymentTracking = 'PASS';
|
||
console.log(' ✅ Payment tracking works');
|
||
} else {
|
||
results.otherFeatures.paymentTracking = 'FAIL';
|
||
results.otherFeatures.issues.push('Payment did not mark as paid');
|
||
console.error(' ❌ Payment did not mark as paid');
|
||
}
|
||
} else {
|
||
results.otherFeatures.paymentTracking = 'SKIP (no pay button found)';
|
||
console.log(' ⚠️ Pay button not found');
|
||
}
|
||
} else {
|
||
results.otherFeatures.paymentTracking = 'SKIP (no unpaid bills)';
|
||
console.log(' ⚠️ No unpaid bills to test');
|
||
}
|
||
|
||
// Test 4: Bill edits
|
||
console.log(' Testing: Bill edits...');
|
||
const billsPage = await page.goto(BASE_URL + '/bills');
|
||
await page.waitForSelector('button:has-text("Edit")');
|
||
|
||
const editBtn = await page.locator('button:has-text("Edit")').first();
|
||
if (await editBtn.count() > 0) {
|
||
await editBtn.click();
|
||
await page.waitForTimeout(1000);
|
||
|
||
// Try to change the bill name
|
||
const nameInput = await page.locator('input[name="name"]').first();
|
||
if (await nameInput.count() > 0) {
|
||
const originalName = await nameInput.inputValue();
|
||
await nameInput.fill(originalName + ' (edited)');
|
||
await page.click('button:has-text("Save")');
|
||
await page.waitForTimeout(1000);
|
||
|
||
// Verify the change
|
||
const content = await page.content();
|
||
if (content.includes(originalName + ' (edited)')) {
|
||
results.otherFeatures.billEdits = 'PASS';
|
||
console.log(' ✅ Bill edits work');
|
||
} else {
|
||
results.otherFeatures.billEdits = 'FAIL';
|
||
results.otherFeatures.issues.push('Bill edit did not persist');
|
||
console.error(' ❌ Bill edit did not persist');
|
||
}
|
||
} else {
|
||
results.otherFeatures.billEdits = 'SKIP (name input not found)';
|
||
console.log(' ⚠️ Name input not found');
|
||
}
|
||
} else {
|
||
results.otherFeatures.billEdits = 'SKIP (no edit button found)';
|
||
console.log(' ⚠️ Edit button not found');
|
||
}
|
||
|
||
} catch (error) {
|
||
results.otherFeatures.billEdits = 'FAIL - ' + error.message;
|
||
console.error('❌ Other features test failed:', error.message);
|
||
}
|
||
}
|
||
|
||
function generateSummary() {
|
||
let allPassed = true;
|
||
let issues = [];
|
||
|
||
// Check login
|
||
if (results.login !== 'PASS') {
|
||
allPassed = false;
|
||
issues.push('Login test failed');
|
||
}
|
||
|
||
// Check bills created
|
||
if (!results.billsCreated.startsWith('PASS')) {
|
||
allPassed = false;
|
||
issues.push('Bill creation test failed');
|
||
}
|
||
|
||
// Check notes feature
|
||
if (results.notesFeature.perBillPerMonth !== 'PASS') {
|
||
allPassed = false;
|
||
issues.push('Notes per-bill-per-month test failed');
|
||
}
|
||
|
||
if (results.notesFeature.persistence !== 'PASS') {
|
||
allPassed = false;
|
||
issues.push('Notes persistence test failed');
|
||
}
|
||
|
||
if (results.notesFeature.monthSwitching !== 'PASS' && results.notesFeature.monthSwitching !== 'SKIP') {
|
||
allPassed = false;
|
||
issues.push('Month switching test failed');
|
||
}
|
||
|
||
// Check other features
|
||
if (results.otherFeatures.autopayToggle === 'PASS' && results.otherFeatures.twoFactorToggle === 'PASS' &&
|
||
results.otherFeatures.paymentTracking === 'PASS' && results.otherFeatures.billEdits === 'PASS') {
|
||
// Good
|
||
} else {
|
||
allPassed = false;
|
||
issues.push('Other features test failed');
|
||
}
|
||
|
||
// Collect all issues
|
||
issues.push(...results.notesFeature.issues);
|
||
issues.push(...results.otherFeatures.issues);
|
||
|
||
results.finalSummary = allPassed ? 'ALL TESTS PASSED ✅' : 'SOME TESTS FAILED ❌';
|
||
results.allIssues = issues;
|
||
|
||
console.log('\n' + '='.repeat(50));
|
||
console.log('FINAL SUMMARY:', results.finalSummary);
|
||
if (issues.length > 0) {
|
||
console.log('\nIssues Found:');
|
||
issues.forEach((issue, i) => console.log(` ${i + 1}. ${issue}`));
|
||
}
|
||
console.log('='.repeat(50));
|
||
}
|
||
|
||
function saveResults() {
|
||
const reviewPath = path.join(__dirname, 'REVIEW.md');
|
||
let reviewContent = '';
|
||
|
||
try {
|
||
reviewContent = fs.readFileSync(reviewPath, 'utf8');
|
||
} catch (e) {
|
||
reviewContent = '# Bill Tracker Multi-Agent Review\n\n';
|
||
}
|
||
|
||
// Add new section at the end
|
||
const timestamp = new Date().toLocaleString('en-US', {
|
||
timeZone: 'America/Chicago',
|
||
dateStyle: 'full',
|
||
timeStyle: 'long'
|
||
});
|
||
|
||
const newSection = `
|
||
## Functional Testing Results - ${timestamp}
|
||
|
||
### Overview
|
||
- **Date:** ${new Date().toISOString().slice(0, 10)}
|
||
- **Login:** ${results.login}
|
||
- **Bills Created:** ${results.billsCreated}
|
||
- **Final Result:** ${results.finalSummary}
|
||
|
||
### Notes Feature Test Results
|
||
- **Per-Bill Per-Month:** ${results.notesFeature.perBillPerMonth}
|
||
- **Persistence:** ${results.notesFeature.persistence}
|
||
- **Month Switching:** ${results.notesFeature.monthSwitching}
|
||
|
||
### Other Features Test Results
|
||
- **Bill Creation:** ${results.otherFeatures.billCreation}
|
||
- **Autopay Toggle:** ${results.otherFeatures.autopayToggle}
|
||
- **Two-Factor Toggle:** ${results.otherFeatures.twoFactorToggle}
|
||
- **Payment Tracking:** ${results.otherFeatures.paymentTracking}
|
||
- **Bill Edits:** ${results.otherFeatures.billEdits}
|
||
|
||
### Bugs Found
|
||
${
|
||
results.notesFeature.issues.length > 0 || results.otherFeatures.issues.length > 0
|
||
? results.notesFeature.issues.map(i => `- ${i}`).join('\n') +
|
||
(results.notesFeature.issues.length > 0 && results.otherFeatures.issues.length > 0 ? '\n' : '') +
|
||
results.otherFeatures.issues.map(i => `- ${i}`).join('\n')
|
||
: 'None'
|
||
}
|
||
|
||
### Notes Feature Status
|
||
The notes feature is implemented as **per-bill AND per-month**. Each bill has its own notes field, and each month has its own separate notes. This means:
|
||
- Bill A January notes ≠ Bill B January notes
|
||
- Bill A January notes ≠ Bill A February notes
|
||
- All bills on the Tracker page have editable notes for the current month
|
||
|
||
---
|
||
|
||
`;
|
||
|
||
// Remove the old "Functional Testing Results" section if it exists
|
||
const updatedContent = reviewContent.replace(
|
||
/## Functional Testing Results - .*?(?=##|$)/s,
|
||
''
|
||
) + newSection;
|
||
|
||
fs.writeFileSync(reviewPath, updatedContent, 'utf8');
|
||
console.log('\n✅ Test results saved to REVIEW.md');
|
||
}
|
||
|
||
// Run the tests
|
||
runTests().catch(console.error);
|