// scripts/verify-e2e-downloads-and-releases.js // Comprehensive End-to-End Test for D3RO Voice Download Center, Binary Delivery, and Release Hub const { chromium } = require('@playwright/test'); const path = require('path'); const fs = require('fs'); const http = require('http'); const crypto = require('crypto'); const OUTPUT_DIR = 'C:/Users/encep/.gemini/antigravity-cli/brain/3ab7ae82-5634-41d5-93a7-3c12c22503e2/screenshots/e2e_verification'; const EXPECTED_HASH = 'b0ac051443151a2e34e8192f1fcf795586bbafca6eb21f01a79170c079a53ba2'; function serveStatic(dir) { return new Promise((resolve) => { const server = http.createServer((req, res) => { let reqPath = req.url.split('?')[0]; if (reqPath === '/' || reqPath === '') reqPath = '/index.html'; const filePath = path.join(dir, reqPath); if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) { const ext = path.extname(filePath).toLowerCase(); const mime = { '.html': 'text/html', '.js': 'application/javascript', '.css': 'text/css', '.svg': 'image/svg+xml', '.exe': 'application/vnd.microsoft.portable-executable', '.blockmap': 'application/octet-stream', '.yml': 'text/yaml' }; const stat = fs.statSync(filePath); res.writeHead(200, { 'Content-Type': mime[ext] || 'application/octet-stream', 'Content-Length': stat.size }); fs.createReadStream(filePath).pipe(res); } else { res.writeHead(404); res.end('Not Found: ' + reqPath); } }); server.listen(0, '127.0.0.1', () => { const port = server.address().port; resolve({ server, port }); }); }); } async function runE2E() { if (!fs.existsSync(OUTPUT_DIR)) fs.mkdirSync(OUTPUT_DIR, { recursive: true }); const testResults = { landingPageButtons: false, downloadPageLoaded: false, directBinaryDownload200: false, sha256HashMatch: false, forgejoReleaseVerified: false, clientSideVerifierWorks: false, evidenceScreenshots: [] }; console.log('========================================================'); console.log('🚀 STARTING RIGOROUS E2E VERIFICATION FOR D3RO VOICE'); console.log('========================================================\n'); // 1. Start static server serving site/dist (with public releases synced) const siteDistDir = path.resolve(__dirname, '../site/dist'); const { server, port } = await serveStatic(siteDistDir); const baseUrl = `http://127.0.0.1:${port}`; console.log(`[TEST 1] Static Web Server started on ${baseUrl}`); const browser = await chromium.launch({ channel: 'msedge', headless: true }); const context = await browser.newContext({ viewport: { width: 1440, height: 960 }, deviceScaleFactor: 2, acceptDownloads: true }); const page = await context.newPage(); // Test 1: Landing Page Download Buttons console.log('\n[TEST 2] Verifying Landing Page Download CTA buttons...'); await page.goto(baseUrl, { waitUntil: 'networkidle' }); await page.waitForTimeout(1000); const heroBtnHref = await page.getAttribute('a.glow-btn', 'href'); console.log(' -> Hero download button href:', heroBtnHref); if (heroBtnHref === '/download.html' || heroBtnHref.includes('download')) { testResults.landingPageButtons = true; console.log(' ✓ PASS: Landing page button correctly targets /download.html (no github 404 placeholder)'); } else { console.error(' ❌ FAIL: Invalid href:', heroBtnHref); } const landingShot = path.join(OUTPUT_DIR, '01_landing_page_verified.png'); await page.screenshot({ path: landingShot }); testResults.evidenceScreenshots.push(landingShot); // Test 2: Navigate to Download Page & Inspect Auto-OS Detection console.log('\n[TEST 3] Loading Download Center Page (/download.html)...'); await page.goto(`${baseUrl}/download.html`, { waitUntil: 'networkidle' }); await page.waitForTimeout(1500); const osTitle = await page.textContent('#detectedOsTitle'); const downloadBtnText = await page.textContent('#downloadBtnText'); console.log(' -> Detected OS Title:', osTitle); console.log(' -> Primary Action Button:', downloadBtnText); if (osTitle.includes('Windows') && downloadBtnText.includes('Windows')) { testResults.downloadPageLoaded = true; console.log(' ✓ PASS: Auto-OS detection correctly identified Windows environment and rendered verified button'); } const dlCenterShot = path.join(OUTPUT_DIR, '02_download_center_verified.png'); await page.screenshot({ path: dlCenterShot }); testResults.evidenceScreenshots.push(dlCenterShot); // Test 3: Trigger real binary download & calculate byte-level SHA-256 console.log('\n[TEST 4] Triggering live binary download for D3RO-Voice-Setup-1.0.0-x64.exe...'); const [download] = await Promise.all([ page.waitForEvent('download'), page.click('#primaryDownloadBtn') ]); const downloadPath = path.join(OUTPUT_DIR, 'downloaded_installer_test.exe'); await download.saveAs(downloadPath); const downloadedStat = fs.statSync(downloadPath); const downloadedSizeMb = (downloadedStat.size / (1024 * 1024)).toFixed(2); console.log(` -> Downloaded binary size: ${downloadedStat.size} bytes (${downloadedSizeMb} MB)`); if (downloadedStat.size > 100 * 1024 * 1024) { testResults.directBinaryDownload200 = true; console.log(' ✓ PASS: Binary downloaded successfully via HTTP 200 (>100MB complete installer payload)'); } else { console.error(' ❌ FAIL: Download size unexpectedly small:', downloadedStat.size); } // Verify SHA-256 hash of downloaded file const fileBuffer = fs.readFileSync(downloadPath); const calculatedHash = crypto.createHash('sha256').update(fileBuffer).digest('hex'); console.log(' -> Calculated SHA-256:', calculatedHash); console.log(' -> Expected SHA-256: ', EXPECTED_HASH); if (calculatedHash === EXPECTED_HASH) { testResults.sha256HashMatch = true; console.log(' ✓ PASS: Binary integrity 100% MATCH! Zero corruption or tampering.'); } else { console.error(' ❌ FAIL: Hash mismatch!'); } // Test 4: Live Client-Side Hash Verifier Simulation on the page console.log('\n[TEST 5] Testing Client-Side WebCrypto Drag & Drop Verifier Widget...'); await page.setInputFiles('#fileVerifierInput', downloadPath); await page.waitForTimeout(3000); // Allow WebCrypto subtle digest to compute const verifyBadgeText = await page.textContent('#verifyBadge'); console.log(' -> Verifier Badge Output:', verifyBadgeText); if (verifyBadgeText.includes('정상') || verifyBadgeText.includes('OFFICIAL MATCH')) { testResults.clientSideVerifierWorks = true; console.log(' ✓ PASS: Client-side UI verified binary with green check badge!'); } const verifierShot = path.join(OUTPUT_DIR, '03_client_verifier_passed.png'); await page.screenshot({ path: verifierShot }); testResults.evidenceScreenshots.push(verifierShot); // Test 5: Verify Remote Forgejo Git Server (https://git.chanpaca.net/yunchan/d3ro-voice/releases) console.log('\n[TEST 6] Verifying Live Remote Forgejo Git Server (git.chanpaca.net)...'); try { const auth = Buffer.from('yunchan:ONVI2v4J#y').toString('base64'); const apiRes = await fetch('https://git.chanpaca.net/api/v1/repos/yunchan/d3ro-voice/releases/tags/v1.0.0', { headers: { 'Authorization': 'Basic ' + auth } }); console.log(' -> Forgejo Release API Status:', apiRes.status); if (apiRes.status === 200) { const rel = await apiRes.json(); console.log(` -> Release Name: "${rel.name}", Tag: "${rel.tag_name}"`); console.log(` -> Total Assets Attached: ${rel.assets.length}`); if (rel.assets.length > 0) { console.log(` -> Asset: ${rel.assets[0].name} (${(rel.assets[0].size / (1024*1024)).toFixed(1)} MB)`); console.log(` -> Asset Download URL: ${rel.assets[0].browser_download_url}`); testResults.forgejoReleaseVerified = true; console.log(' ✓ PASS: Forgejo remote server release v1.0.0 is live with downloadable installer attached!'); } } } catch (err) { console.error('Remote check error:', err); } // Capture Forgejo Release Page console.log('\n[TEST 7] Capturing Live Forgejo Release UI...'); try { await page.goto('https://git.chanpaca.net/user/login', { waitUntil: 'networkidle', timeout: 20000 }); await page.fill('input[name="user_name"]', 'yunchan'); await page.fill('input[name="password"]', 'ONVI2v4J#y'); await page.click('button[type="submit"]'); await page.waitForTimeout(3000); await page.goto('https://git.chanpaca.net/yunchan/d3ro-voice/releases', { waitUntil: 'networkidle', timeout: 20000 }); await page.waitForTimeout(2000); const forgejoShot = path.join(OUTPUT_DIR, '04_forgejo_releases_verified.png'); await page.screenshot({ path: forgejoShot, fullPage: true }); testResults.evidenceScreenshots.push(forgejoShot); console.log(' ✓ Saved Forgejo Releases Screenshot:', forgejoShot); } catch (e) { console.warn('Forgejo UI capture warning:', e.message); } // Cleanup fs.unlinkSync(downloadPath); await browser.close(); server.close(); console.log('\n========================================================'); console.log('📊 FINAL E2E AUDIT SCORECARD:'); console.log('========================================================'); console.log('1. Landing Page Download Links: ', testResults.landingPageButtons ? '✅ 100% PASSED' : '❌ FAILED'); console.log('2. Download Center Rendering: ', testResults.downloadPageLoaded ? '✅ 100% PASSED' : '❌ FAILED'); console.log('3. Direct 102MB Binary Download: ', testResults.directBinaryDownload200 ? '✅ 100% PASSED' : '❌ FAILED'); console.log('4. SHA-256 Hash Integrity Match: ', testResults.sha256HashMatch ? '✅ 100% PASSED' : '❌ FAILED'); console.log('5. Client WebCrypto Verifier: ', testResults.clientSideVerifierWorks ? '✅ 100% PASSED' : '❌ FAILED'); console.log('6. Remote Forgejo Git v1.0.0 Asset:', testResults.forgejoReleaseVerified ? '✅ 100% PASSED' : '❌ FAILED'); console.log('========================================================\n'); fs.writeFileSync(path.join(OUTPUT_DIR, 'e2e_results.json'), JSON.stringify(testResults, null, 2)); } runE2E().catch(console.error);