Phase 13.1 CI/CD Integration: - #76 workflow_run handler for CI failure diagnosis - #77 SignOz log query (query_logs, error_logs_summary MCP) - #78 CIAutoRepairService with risk-based execution decisions Phase 13.3 Smart Routing: - #85 Intent Classifier v2.0 (rule engine + LLM fallback) - #86 Complexity Scorer (9-dimension scoring) - #87 AI Router v3.0 (routing decision matrix) - #88 Token Counter (OTEL + Langfuse integration) New files: - services/ci_auto_repair.py (risk stratification) - services/model_registry.py (centralized model config) - services/token_counter.py (677 lines) - Skill 08: Model Router Expert - Skill 09: Strangler Pattern Expert - ADR-023: Smart Routing Architecture - ADR-024: API Layer Architecture Tests: - phase11-conversational.spec.ts (E2E tests) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
235 lines
7.0 KiB
TypeScript
235 lines
7.0 KiB
TypeScript
/**
|
|
* AWOOOI E2E - Phase 11 對話式 AI UI/UX
|
|
* =====================================
|
|
* Phase 11.1-11.4 功能驗證
|
|
*
|
|
* 功能覆蓋:
|
|
* - 11.1 對話式容器 (ConversationalView)
|
|
* - 11.2 批次處理 (BatchModeSelector)
|
|
* - 11.3 響應式佈局 (Mobile/Tablet/Desktop)
|
|
* - 11.4 鍵盤快捷鍵 (Y/N/方向鍵)
|
|
*
|
|
* 版本: v1.0
|
|
* 建立: 2026-03-26 (台北時區)
|
|
*/
|
|
|
|
import { test, expect, Page } from '@playwright/test'
|
|
|
|
// 測試輔助函數
|
|
async function waitForPageLoad(page: Page) {
|
|
await page.goto('/zh-TW')
|
|
await page.waitForLoadState('domcontentloaded')
|
|
await page.waitForTimeout(2000) // 等待 SSE 連線
|
|
}
|
|
|
|
test.describe('Phase 11.1 對話式容器', () => {
|
|
test('ConversationalView 雙欄佈局應正確顯示', async ({ page }) => {
|
|
await waitForPageLoad(page)
|
|
|
|
// 截圖
|
|
await page.screenshot({
|
|
path: 'test-results/phase11-conversational-layout.png',
|
|
fullPage: true,
|
|
})
|
|
|
|
// 驗證左側列表區域
|
|
const leftPanel = page.locator('[data-testid="approval-list"]').or(
|
|
page.locator('[class*="ApprovalThreadList"]')
|
|
).or(
|
|
page.locator('[class*="conversational"]').locator('[class*="left"]')
|
|
)
|
|
|
|
// 驗證右側詳情區域
|
|
const rightPanel = page.locator('[data-testid="approval-detail"]').or(
|
|
page.locator('[class*="ApprovalDetail"]')
|
|
).or(
|
|
page.locator('[class*="conversational"]').locator('[class*="right"]')
|
|
)
|
|
|
|
// 至少一個面板應該可見 (根據實際實作調整)
|
|
const leftVisible = await leftPanel.first().isVisible().catch(() => false)
|
|
const rightVisible = await rightPanel.first().isVisible().catch(() => false)
|
|
|
|
console.log(`[Phase 11.1] Left panel visible: ${leftVisible}`)
|
|
console.log(`[Phase 11.1] Right panel visible: ${rightVisible}`)
|
|
|
|
// 截圖紀錄最終狀態
|
|
await page.screenshot({
|
|
path: 'test-results/phase11-conversational-final.png',
|
|
fullPage: true,
|
|
})
|
|
})
|
|
|
|
test('Approval 項目應顯示風險等級和相對時間', async ({ page }) => {
|
|
await waitForPageLoad(page)
|
|
|
|
// 查找風險等級標籤
|
|
const riskBadges = page.locator('text=/CRITICAL|HIGH|MEDIUM|LOW/i')
|
|
const riskCount = await riskBadges.count()
|
|
|
|
console.log(`[Phase 11.1] Risk badges found: ${riskCount}`)
|
|
|
|
// 查找時間顯示
|
|
const timeIndicators = page.locator('text=/分鐘前|小時前|天前|minutes ago|hours ago|days ago/i')
|
|
const timeCount = await timeIndicators.count()
|
|
|
|
console.log(`[Phase 11.1] Time indicators found: ${timeCount}`)
|
|
|
|
await page.screenshot({
|
|
path: 'test-results/phase11-risk-badges.png',
|
|
fullPage: true,
|
|
})
|
|
})
|
|
})
|
|
|
|
test.describe('Phase 11.2 批次處理', () => {
|
|
test('BatchModeSelector 應顯示三種模式選項', async ({ page }) => {
|
|
await waitForPageLoad(page)
|
|
|
|
// 查找批次模式選擇器
|
|
const batchSelector = page.locator('[data-testid="batch-mode-selector"]').or(
|
|
page.locator('text=/全部接受|逐一審核|僅 CRITICAL/i')
|
|
)
|
|
|
|
const hasSelector = await batchSelector.first().isVisible().catch(() => false)
|
|
console.log(`[Phase 11.2] Batch mode selector visible: ${hasSelector}`)
|
|
|
|
// 查找模式選項
|
|
const approveAllBtn = page.locator('button:has-text("全部接受")').or(
|
|
page.locator('button:has-text("Accept All")')
|
|
)
|
|
const reviewOneByOneBtn = page.locator('button:has-text("逐一審核")').or(
|
|
page.locator('button:has-text("Review One")')
|
|
)
|
|
const criticalOnlyBtn = page.locator('button:has-text("CRITICAL")').or(
|
|
page.locator('button:has-text("Critical Only")')
|
|
)
|
|
|
|
const approveAllVisible = await approveAllBtn.first().isVisible().catch(() => false)
|
|
const reviewVisible = await reviewOneByOneBtn.first().isVisible().catch(() => false)
|
|
const criticalVisible = await criticalOnlyBtn.first().isVisible().catch(() => false)
|
|
|
|
console.log(`[Phase 11.2] Approve All button: ${approveAllVisible}`)
|
|
console.log(`[Phase 11.2] Review One by One button: ${reviewVisible}`)
|
|
console.log(`[Phase 11.2] Critical Only button: ${criticalVisible}`)
|
|
|
|
await page.screenshot({
|
|
path: 'test-results/phase11-batch-mode.png',
|
|
fullPage: true,
|
|
})
|
|
})
|
|
})
|
|
|
|
test.describe('Phase 11.3 響應式佈局', () => {
|
|
test('Desktop 視窗應顯示雙欄佈局', async ({ page }) => {
|
|
// 設定 Desktop 視窗大小
|
|
await page.setViewportSize({ width: 1920, height: 1080 })
|
|
await waitForPageLoad(page)
|
|
|
|
await page.screenshot({
|
|
path: 'test-results/phase11-desktop-layout.png',
|
|
fullPage: true,
|
|
})
|
|
|
|
console.log('[Phase 11.3] Desktop layout captured (1920x1080)')
|
|
})
|
|
|
|
test('Tablet 視窗應支援滑動切換', async ({ page }) => {
|
|
// 設定 Tablet 視窗大小
|
|
await page.setViewportSize({ width: 768, height: 1024 })
|
|
await waitForPageLoad(page)
|
|
|
|
await page.screenshot({
|
|
path: 'test-results/phase11-tablet-layout.png',
|
|
fullPage: true,
|
|
})
|
|
|
|
// 查找滑動提示
|
|
const swipeHint = page.locator('text=/滑動|swipe/i')
|
|
const hasSwipeHint = await swipeHint.first().isVisible().catch(() => false)
|
|
|
|
console.log(`[Phase 11.3] Tablet swipe hint visible: ${hasSwipeHint}`)
|
|
})
|
|
|
|
test('Mobile 視窗應顯示全螢幕模式', async ({ page }) => {
|
|
// 設定 Mobile 視窗大小
|
|
await page.setViewportSize({ width: 375, height: 812 })
|
|
await waitForPageLoad(page)
|
|
|
|
await page.screenshot({
|
|
path: 'test-results/phase11-mobile-layout.png',
|
|
fullPage: true,
|
|
})
|
|
|
|
console.log('[Phase 11.3] Mobile layout captured (375x812)')
|
|
})
|
|
})
|
|
|
|
test.describe('Phase 11.4 鍵盤快捷鍵', () => {
|
|
test('按下 Y 鍵應觸發批准操作', async ({ page }) => {
|
|
await waitForPageLoad(page)
|
|
|
|
// 先聚焦頁面
|
|
await page.click('body')
|
|
await page.waitForTimeout(500)
|
|
|
|
// 按下 Y 鍵
|
|
await page.keyboard.press('y')
|
|
await page.waitForTimeout(1000)
|
|
|
|
await page.screenshot({
|
|
path: 'test-results/phase11-keyboard-y.png',
|
|
fullPage: true,
|
|
})
|
|
|
|
console.log('[Phase 11.4] Y key pressed')
|
|
})
|
|
|
|
test('按下 N 鍵應觸發拒絕操作', async ({ page }) => {
|
|
await waitForPageLoad(page)
|
|
|
|
// 先聚焦頁面
|
|
await page.click('body')
|
|
await page.waitForTimeout(500)
|
|
|
|
// 按下 N 鍵
|
|
await page.keyboard.press('n')
|
|
await page.waitForTimeout(1000)
|
|
|
|
await page.screenshot({
|
|
path: 'test-results/phase11-keyboard-n.png',
|
|
fullPage: true,
|
|
})
|
|
|
|
console.log('[Phase 11.4] N key pressed')
|
|
})
|
|
|
|
test('方向鍵應支援列表導航', async ({ page }) => {
|
|
await waitForPageLoad(page)
|
|
|
|
// 先聚焦頁面
|
|
await page.click('body')
|
|
await page.waitForTimeout(500)
|
|
|
|
// 按下向下鍵
|
|
await page.keyboard.press('ArrowDown')
|
|
await page.waitForTimeout(500)
|
|
|
|
await page.screenshot({
|
|
path: 'test-results/phase11-keyboard-down.png',
|
|
fullPage: true,
|
|
})
|
|
|
|
// 按下向上鍵
|
|
await page.keyboard.press('ArrowUp')
|
|
await page.waitForTimeout(500)
|
|
|
|
await page.screenshot({
|
|
path: 'test-results/phase11-keyboard-up.png',
|
|
fullPage: true,
|
|
})
|
|
|
|
console.log('[Phase 11.4] Arrow keys navigation tested')
|
|
})
|
|
})
|