# RunBook: E2E Playwright CI 定期排程設定

> **類型**: 操作型 RunBook  
> **優先級**: 🔴 P0  
> **建立**: 2026-03-29 12:38 (台北)  
> **建立者**: Antigravity  
> **工時預估**: 30 分鐘  
> **前置條件**: Playwright 測試可在本機 `pnpm test:e2e` 成功執行

---

## 背景與現況

### 🔍 精確現況診斷

**已有的 12 個 E2E 測試檔案**（`apps/web/tests/e2e/`）：

| 測試檔案 | 測試範圍 |
|---------|---------|
| `dashboard-acceptance.spec.ts` | 首頁 Dashboard 驗收 |
| `multisig-security.spec.ts` | 多重簽核安全性 |
| `approval-card-verify.spec.ts` | 簽核卡片驗證 |
| `phase11-conversational.spec.ts` | 對話式 Phase 11 功能 |
| `phase19-production-verification.spec.ts` | Phase 19 生產驗證 |
| `action-log.spec.ts` | 行動日誌 |
| `cpo102-visual.spec.ts` | 視覺截圖測試 |
| `visual-armor-upgrade.spec.ts` | 視覺升級驗證 |
| `debug-error.spec.ts` | 錯誤頁面 |
| `rbac-screenshot.spec.ts` | RBAC 截圖驗證 |
| `phase4-final-demo.spec.ts` | Phase 4 Demo |
| `phase4-timeline.spec.ts` | Phase 4 時間軸 |

**缺口**：`playwright.config.ts` 已有配置，但 `.github/workflows/` 中**無定期執行排程**。

---

## Step 1: 建立 E2E 定期排程 Workflow

建立 `.github/workflows/e2e-weekly.yaml`：

```yaml
name: 🎭 E2E Playwright 週期驗收

on:
  # 每週一凌晨 02:30 執行（台北時間，即 UTC 18:30 週日）
  schedule:
    - cron: '30 18 * * 0'
  
  # 允許手動觸發
  workflow_dispatch:
    inputs:
      environment:
        description: '測試環境 URL'
        required: false
        default: 'https://192.168.0.120:32335'
      test_suite:
        description: '測試套件 (all / smoke / visual)'
        required: false
        default: 'all'

jobs:
  e2e-test:
    name: 🎭 E2E Playwright (${{ matrix.browser }})
    runs-on: [self-hosted, harbor]  # 使用 .110 的 GitHub Runner
    
    strategy:
      fail-fast: false              # 一個瀏覽器失敗不影響其他
      matrix:
        browser: [chromium, firefox]
    
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      
      - name: Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 9

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Install Playwright Browsers
        run: pnpm --filter web exec playwright install --with-deps ${{ matrix.browser }}

      - name: 🎭 Run E2E Tests
        run: |
          cd apps/web
          pnpm exec playwright test \
            --project=${{ matrix.browser }} \
            --reporter=html \
            ${SUITE_FILTER}
        env:
          SUITE_FILTER: ${{ github.event.inputs.test_suite == 'smoke' && '--grep @smoke' || '' }}
          BASE_URL: ${{ github.event.inputs.environment || 'http://192.168.0.120:32335' }}
          CI: true

      - name: 📁 Upload Test Report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report-${{ matrix.browser }}-${{ github.run_id }}
          path: apps/web/playwright-report/
          retention-days: 14

      - name: 📸 Upload Screenshots on Failure
        uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: e2e-screenshots-${{ matrix.browser }}-${{ github.run_id }}
          path: apps/web/test-results/
          retention-days: 7

      - name: 🚨 Notify Telegram on Failure
        if: failure()
        run: |
          curl -s -X POST "https://api.telegram.org/bot${TG_BOT_TOKEN}/sendMessage" \
            -d chat_id="${TG_CHAT_ID}" \
            -d parse_mode="HTML" \
            -d text="🎭 <b>E2E 週期測試失敗</b>

  瀏覽器：${{ matrix.browser }}
  觸發：${{ github.event_name }}
  時間：$(TZ='Asia/Taipei' date '+%Y-%m-%d %H:%M:%S')
  
  報告：${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
  
  🔍 請立即調查 UI 回歸問題"
        env:
          TG_BOT_TOKEN: ${{ secrets.OPENCLAW_TG_BOT_TOKEN }}
          TG_CHAT_ID: ${{ env.TELEGRAM_ALERT_CHAT_ID }}

  # 視覺截圖比對工作（獨立 job，只在排程時執行）
  visual-regression:
    name: 📸 視覺回歸比對
    runs-on: [self-hosted, harbor]
    if: github.event_name == 'schedule'  # 只在定期排程時執行
    needs: e2e-test
    
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      
      - name: Install pnpm & deps
        run: |
          pnpm install --frozen-lockfile
          pnpm --filter web exec playwright install --with-deps chromium

      - name: 📸 Generate Visual Snapshots
        run: |
          cd apps/web
          pnpm exec playwright test \
            --project=chromium \
            --grep @visual \
            --reporter=html \
            --update-snapshots=missing   # 新截圖自動建立 baseline
        env:
          BASE_URL: 'http://192.168.0.120:32335'
          CI: true

      - name: 📁 Upload Visual Snapshots
        uses: actions/upload-artifact@v4
        with:
          name: visual-snapshots-${{ github.run_id }}
          path: apps/web/tests/e2e/__snapshots__/
          retention-days: 30

      - name: 🚨 Notify Telegram on Visual Regression
        if: failure()
        run: |
          curl -s -X POST "https://api.telegram.org/bot${TG_BOT_TOKEN}/sendMessage" \
            -d chat_id="${TG_CHAT_ID}" \
            -d parse_mode="HTML" \
            -d text="📸 <b>視覺回歸測試失敗</b>

  AWOOOI 前端出現視覺變化！
  時間：$(TZ='Asia/Taipei' date '+%Y-%m-%d %H:%M:%S')
  
  請統帥審查截圖比對報告：
  ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
        env:
          TG_BOT_TOKEN: ${{ secrets.OPENCLAW_TG_BOT_TOKEN }}
          TG_CHAT_ID: ${{ env.TELEGRAM_ALERT_CHAT_ID }}
```

---

## Step 2: 標記現有測試為 Smoke / Visual 類別

```typescript
// 在各 spec 檔案第一行測試加上標籤：

// dashboard-acceptance.spec.ts（核心功能，標記為 @smoke）
test.describe('Dashboard Acceptance @smoke', () => { ... });

// cpo102-visual.spec.ts（視覺截圖，標記為 @visual）
test.describe('Visual Regression @visual', () => {
  test('CPO-102 首頁視覺', async ({ page }) => {
    await page.goto('/');
    await expect(page).toHaveScreenshot('dashboard-baseline.png', {
      fullPage: true,
      threshold: 0.02  // 允許 2% 像素差異
    });
  });
});
```

**建議標記方式**：

| 標籤 | 包含測試 | 執行頻率 |
|------|---------|---------|
| `@smoke` | dashboard, approval, action-log | 每次 CD 後 + 每週 |
| `@visual` | cpo102-visual, visual-armor | 只在每週排程 |
| （無標籤） | 所有其他測試 | 只在每週排程 |

---

## Step 3: 部署並驗證

```bash
# 1. 提交 Workflow 檔案
git add .github/workflows/e2e-weekly.yaml
git commit -m "feat(ci): add weekly E2E Playwright schedule with Telegram failure notification"
git push origin main

# 2. 手動觸發測試（確認 Workflow 運作正常）
gh workflow run e2e-weekly.yaml \
  -f environment="http://192.168.0.120:32335" \
  -f test_suite="smoke"

# 3. 監控 Workflow 執行
gh run watch

# 4. 確認 Telegram Bot 收到失敗通知（刻意讓一個測試失敗）
```

---

## 驗收標準

| 項目 | 通過條件 |
|------|---------|
| Workflow 存在 | `.github/workflows/e2e-weekly.yaml` 成功 push |
| 手動觸發正常 | `gh workflow run` 可執行且完成 |
| Smoke 測試通過 | `@smoke` 標籤測試全部 PASS |
| 失敗通知正常 | Telegram Bot 收到失敗訊息 |
| 報告上傳 | GitHub Actions Artifacts 中有 `playwright-report-*` |

---

## ⚠️ 架構安全補丁（2026-03-29 更新，部署前必讀）

> 來源：`ARCHITECTURAL_RISK_WAR_GAME.md` 深度沙盤推演，代碼確認級別

### 補丁 1：playwright.config.ts 必須加入 ignoreHTTPSErrors

**問題**：內網 K3s 使用自簽憑證（Self-signed cert），Playwright 連接 `https://192.168.0.120:32335` 時會遭遇 `NET::ERR_CERT_AUTHORITY_INVALID`，導致**所有測試在第一步就失敗**。

**修復**（`apps/web/playwright.config.ts`）：

```typescript
export default defineConfig({
  use: {
    baseURL: process.env.BASE_URL || 'http://192.168.0.120:32335',
    ignoreHTTPSErrors: true,   // 🆕 必須加入，否則自簽憑證全面阻擋
    viewport: { width: 1280, height: 720 },
    deviceScaleFactor: 1,      // 🆕 防止 Retina 螢幕 DPI 差異影響截圖比對
  },
  expect: {
    toHaveScreenshot: {
      threshold: 0.05,          // 🆕 允許 5% 差異（吸收跨平台字體渲染微差）
      maxDiffPixelRatio: 0.05,
    },
  },
});
```

---

### 補丁 2：Visual Baseline 必須在 Docker（Linux 環境）中產生

**問題**：Mac（CoreText 渲染）與 GitHub Actions CI（Linux FreeType 渲染）的字體像素不同。若在 Mac 本機產生 baseline，CI 比對時**100% 誤報失敗**。

> 🔴 **絕對禁止在本機 Mac 環境執行 `--update-snapshots`！**

**正確的 Baseline 更新流程**：

```bash
# 在 Mac 本機執行，但透過 Docker（Linux 環境）產生截圖
cd apps/web
docker run --rm \
  -v $(pwd):/work -w /work \
  -p 3000:3000 \
  mcr.microsoft.com/playwright:v1.44.0-jammy \
  pnpm exec playwright test \
    --update-snapshots \
    --project=chromium \
    --grep @visual

# Docker 產生的 .png 自動存入 tests/e2e/__snapshots__/
# 提 PR，標注 📸 VISUAL_UPDATE
# 統帥視覺審核截圖後方可合併
```

**加入 package.json scripts**：

```json
{
  "scripts": {
    "test:visual": "playwright test --project=chromium --grep @visual",
    "test:visual:update": "docker run --rm -v $(pwd):/work -w /work mcr.microsoft.com/playwright:v1.44.0-jammy pnpm exec playwright test --update-snapshots --project=chromium --grep @visual"
  }
}
```

---

### 補丁 3：CI Threshold 需與本 RunBook Step 2 標籤保持一致

Visual 測試中 `threshold: 0.02`（Step 2 示例代碼）與 `playwright.config.ts` 全局設定 `0.05` 會以**個別設定優先**。建議統一為：

```typescript
// 全局（playwright.config.ts）
threshold: 0.05  // 寬鬆（跨平台環境差異）

// 個別敏感組件（*.spec.ts）
await expect(page).toHaveScreenshot('component.png', {
  threshold: 0.02  // 嚴格（關鍵組件精確比對）
});
```
