diff --git a/.agents/skills/05-awoooi-sre-qa.md b/.agents/skills/05-awoooi-sre-qa.md index 6681fc4f..be315c96 100644 --- a/.agents/skills/05-awoooi-sre-qa.md +++ b/.agents/skills/05-awoooi-sre-qa.md @@ -28,6 +28,27 @@ --- +## 🔴 交付前絕對硬性規定 (憲法條款 14/15) + +**任何宣稱「部署完成」或「功能驗收」之前,必須執行以下腳本:** + +```bash +# 無頭偵察兵 - Playwright 前端視覺驗證 +cd apps/web && node scripts/verify-frontend.js +``` + +### 驗證通過標準 + +| 項目 | 標準 | +|------|------| +| Console 錯誤 | **必須為 0** | +| 事件 ID (INC-*) | 建議有 (已注入告警時) | +| RPS 數據 | 建議有 (GlobalPulse 已啟用時) | + +**禁止僅依賴 curl 驗收!必須親眼確認 DOM 渲染結果!** + +--- + ## Playwright 自動化規範 ### 測試腳本結構 diff --git a/apps/web/scripts/verify-frontend.js b/apps/web/scripts/verify-frontend.js new file mode 100644 index 00000000..58b29e40 --- /dev/null +++ b/apps/web/scripts/verify-frontend.js @@ -0,0 +1,167 @@ +/** + * AWOOOI 無頭偵察兵 - Playwright 前端視覺驗證 + * ================================================== + * + * 職責: + * 1. 驅動真實瀏覽器訪問正式環境 + * 2. 擷取 Console 錯誤 (紅字檢測) + * 3. 檢查關鍵 DOM 元素是否渲染真實數據 + * 4. 等待 SSE 串流與 React Hydration + * + * 用法: + * node scripts/verify-frontend.js + * + * 憲法條款 14: 嚴禁依賴 curl 盲目驗收 + * 憲法條款 15: 必須親眼確認 DOM 渲染結果 + */ + +const { chromium } = require('playwright'); + +const TARGET_URL = process.env.TARGET_URL || 'https://awoooi.wooo.work'; +const WAIT_TIME = parseInt(process.env.WAIT_TIME || '8000', 10); + +async function runQA() { + console.log('============================================================'); + console.log(' AWOOOI 無頭偵察兵 - Playwright 視覺驗證'); + console.log('============================================================'); + console.log(`目標: ${TARGET_URL}`); + console.log(`等待時間: ${WAIT_TIME}ms`); + console.log(''); + + const browser = await chromium.launch({ headless: true }); + const context = await browser.newContext({ + viewport: { width: 1920, height: 1080 }, + userAgent: 'AWOOOI-QA-Bot/1.0 Playwright', + }); + const page = await context.newPage(); + + // 收集 Console 錯誤 + const errors = []; + const warnings = []; + + page.on('console', (msg) => { + if (msg.type() === 'error') { + errors.push(msg.text()); + } else if (msg.type() === 'warning') { + warnings.push(msg.text()); + } + }); + + page.on('pageerror', (err) => { + errors.push(`[PageError] ${err.message}`); + }); + + // Request 失敗追蹤 + page.on('requestfailed', (request) => { + errors.push(`[RequestFailed] ${request.url()} - ${request.failure()?.errorText}`); + }); + + try { + console.log('🚀 導航至目標頁面...'); + await page.goto(TARGET_URL, { + waitUntil: 'domcontentloaded', + timeout: 60000, + }); + + console.log(`⏳ 等待 ${WAIT_TIME}ms (SSE 串流 + React Hydration)...`); + await page.waitForTimeout(WAIT_TIME); + + // ========================================================================= + // DOM 內容檢查 + // ========================================================================= + console.log('\n📋 DOM 內容分析...'); + + const bodyText = await page.textContent('body'); + const pageTitle = await page.title(); + + // 關鍵指標檢查 + const checks = { + // 事件數據檢查 + hasIncidentId: bodyText.includes('INC-'), + hasAlertKeywords: bodyText.includes('告警') || bodyText.includes('警告') || bodyText.includes('事件'), + + // GlobalPulse 指標檢查 (非零數據) + hasRpsData: /\d+\.\d+\s*(req\/s|RPS)/i.test(bodyText), + hasLatencyData: /\d+\s*(ms|毫秒)/i.test(bodyText), + + // 基本頁面結構 + hasTitle: pageTitle.length > 0, + hasNavigation: bodyText.includes('戰情室') || bodyText.includes('Dashboard'), + }; + + // 截圖保存 + const screenshotPath = `/tmp/awoooi-qa-${Date.now()}.png`; + await page.screenshot({ path: screenshotPath, fullPage: true }); + console.log(`📸 截圖已保存: ${screenshotPath}`); + + // ========================================================================= + // 報告輸出 + // ========================================================================= + console.log('\n============================================================'); + console.log(' 🔍 視覺驗證報告'); + console.log('============================================================'); + console.log(`📄 頁面標題: ${pageTitle}`); + console.log(''); + + // Console 錯誤報告 + console.log('🔴 Console 錯誤:'); + if (errors.length === 0) { + console.log(' ✅ 零錯誤 (Clean)'); + } else { + console.log(` ❌ 發現 ${errors.length} 個錯誤:`); + errors.slice(0, 10).forEach((e, i) => { + console.log(` ${i + 1}. ${e.substring(0, 100)}...`); + }); + } + + // Console 警告報告 + if (warnings.length > 0) { + console.log(`\n⚠️ Console 警告: ${warnings.length} 個`); + } + + // DOM 數據檢查報告 + console.log('\n📊 DOM 數據檢查:'); + console.log(` ${checks.hasIncidentId ? '✅' : '❌'} 事件 ID (INC-*)`); + console.log(` ${checks.hasAlertKeywords ? '✅' : '❌'} 告警關鍵字`); + console.log(` ${checks.hasRpsData ? '✅' : '❌'} RPS 數據`); + console.log(` ${checks.hasLatencyData ? '✅' : '❌'} Latency 數據`); + console.log(` ${checks.hasNavigation ? '✅' : '❌'} 導航元素`); + + // 最終判定 + const hasRealData = checks.hasIncidentId || checks.hasAlertKeywords || checks.hasRpsData; + const isClean = errors.length === 0; + + console.log('\n============================================================'); + console.log(' 📋 最終判定'); + console.log('============================================================'); + console.log(`Console 狀態: ${isClean ? '✅ 零紅字' : '❌ 有錯誤'}`); + console.log(`數據渲染狀態: ${hasRealData ? '✅ 有真實數據' : '⚠️ 數據不足'}`); + console.log(''); + + await browser.close(); + + // 退出碼決定 + if (!isClean) { + console.log('❌ 驗收失敗: Console 存在錯誤'); + process.exit(1); + } + + // 數據不足不阻塞,但輸出警告 + if (!hasRealData) { + console.log('⚠️ 警告: 畫面數據不足,可能需要注入告警或等待 SSE'); + } + + console.log('✅ 視覺驗證通過'); + process.exit(0); + + } catch (err) { + console.error('❌ 驗證過程發生錯誤:', err.message); + await browser.close(); + process.exit(1); + } +} + +runQA().catch((err) => { + console.error('❌ 腳本執行失敗:', err); + process.exit(1); +}); diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index c0b42bf3..17b2db54 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -32,6 +32,10 @@ | 時間 | 事件 | 負責人 | |------|------|--------| +| 2026-03-23 01:10 | **🚀 Signal Worker 啟用**: `replicas: 0→1` + Redis Streams Consumer 正式上線 + Incident Engine 處理鏈完整 | CTO + Claude Code | +| 2026-03-23 01:05 | **🎯 實彈告警發射成功**: 4 發告警注入 Redis Streams (HarborOOMKilled/HighCPU/DBTimeout/RedisMemory) + message_id 確認 | CTO + Claude Code | +| 2026-03-23 00:55 | **📊 GlobalPulse 脈搏恢復**: SignOz v3 表修正 + RPS 5.4/Error 25%/P99 3s 真實數據顯示 | CTO + Claude Code | +| 2026-03-23 00:45 | **📱 Telegram 通知 UX 升級**: HTML 結構化格式 + Inline Keyboard (查看紀錄/開啟正式站) + 拔除冗長 URL | CTO + Claude Code | | 2026-03-23 00:25 | **🤖 Claude Skills 兵營建置**: 6 大專屬 Skill 模組 (`01-frontend` ~ `06-monorepo`) + Skill Router 寫入主憲法 + Auto-Pilot 驗收機制 | CTO + Claude Code | | 2026-03-23 00:10 | **🔧 OTEL 神經修復**: Port 4317→24317 修正 (SigNoz Host Port) + NetworkPolicy Egress 開通 + ConfigMap 更新 + Traces 正常匯出 | CTO + Claude Code | | 2026-03-22 23:55 | **🔧 Phase 8 NodePort 修復**: NetworkPolicy `allow-nginx-ingress` 新增 K3s Node IP (120/121) + Pod CIDR (10.42.0.0/16) + 502 Bad Gateway 根治 | CTO + Claude Code |