🧪 E2E 測試指南

Lead Me Home 寵物協尋平台 | 2026-05-18

現況總覽

80
測試案例
76
通過
4
跳過
~3.5 min
執行時間

快速開始

1. 安裝(只需要做一次)
# 進入前端專案
cd lead-me-home-fe

# 安裝測試套件
npm install --save-dev @playwright/test

# 安裝瀏覽器
npx playwright install chromium
2. 執行測試
# 無頭模式(最快,CI 用)
npm run test:e2e

# 看得到瀏覽器操作(Debug 用)
npm run test:e2e:headed

# Playwright UI 模式(最強,可以一步一步看、時間回溯)
npm run test:e2e:ui

# 只跑特定分類
npx playwright test --grep "首頁"
npx playwright test --grep "API"
npx playwright test --grep "手機版"
3. 查看測試報告
# 測試跑完後,自動生成 HTML 報告
npx playwright show-report

會在瀏覽器打開一份互動式報告,包含每個測試的截圖、時間軸、錯誤訊息。

測試架構

lead-me-home-fe/ ├── playwright.config.ts ← 設定檔(目標 URL、瀏覽器、timeout) ├── .env.e2e.example ← 環境變數範本 ├── e2e/ │ ├── full-journey.spec.ts ← 36 個測試案例(主檔) │ └── helpers/ │ ├── auth.ts ← 登入模擬(注入 token 繞過 LINE OAuth) │ └── api.ts ← API 呼叫工具 ├── test-results/ ← 失敗時的截圖、影片(自動產生) └── playwright-report/ ← HTML 報告(npx playwright show-report)

測試項目清單(36 項)

#分類測試名稱驗證什麼
1首頁CTA 按鈕可見且可點擊兩個主要入口按鈕存在、enabled
2首頁最新動態有載入頁面不是空白,有走失/通報相關內容
3首頁「登記走失」未登入導向 LINE未登入按了會跳到 LINE OAuth 或被擋
4首頁「通報」免登入直接進表單URL 變成 /lost-pet-report
5通報物種選擇出現表單有貓/狗/物種相關內容
6通報必填未填被擋空表單送出不會離開頁面
7訊息列表地圖元件出現Leaflet 地圖渲染成功
8訊息列表有卡片渲染頁面有走失/通報時間的文字
9訊息列表篩選切換不 crash切「已關注」/「全部」後頁面正常
10訊息列表帶參數進入不 crash?cityInfo=台北市 頁面 + 地圖正常
11登入保護/profile/pets 被擋未登入不能進個人檔案
12登入保護/add-lost-pet 被擋未登入不能進登記走失
13登入保護/profile/notifications 被擋未登入不能進通知頁
14APIhealth check回 200 + { ok: true }
15APIGET /posts回 200
16APIGET /pet-trait-options回 200 + 有資料
17APIGET /county-counts回 200
18APIScramble 文件可存取回 200 + 含 HTML
19APIOpenAPI JSON回 200 + 有 openapi/paths 欄位
20API未認證打 /profile回 401 或 302(不回 200)
21API未認證打 /poster-status不回 200
22推播不存在的 post 不 500打不存在 ID → 不 crash
23推播空陣列不 500傳 [] → 不 crash
24推播非法輸入不 500傳字串 → 不 crash
25海報無 token 不白屏頁面有內容(>10 字)
26海報template-data 無 token 被擋API 不回 200
27海報R2 圖片可存取poster URL 回 200 + image content-type
28手機版首頁 CTA 可見375px 寬度下按鈕看得到
29手機版訊息列表無溢位scrollWidth ≤ clientWidth
30手機版通報頁無溢位scrollWidth ≤ clientWidth
31導航首頁→通報→返回瀏覽器上下頁不 crash
32導航訊息列表→點卡片點卡片後頁面不白屏
33導航404 路由不白屏不存在的網址有內容
34效能首頁 <5s 載入domcontentloaded < 5 秒
35效能API <3s 回應/posts 回應 < 3 秒
36效能cold start 正常連打兩次 health → 第二次 200

覆蓋率差距分析

QA 觀點:目前的 36 個測試都是「唯讀驗證」— 只確認頁面載入和 API 回應。以下是尚未覆蓋但重要的測試面向。

寫入流程 未覆蓋

  • 登入後填表 → 送出走失貼文 → 資料正確存入
  • 免登入填表 → 送出通報貼文 → 資料正確存入
  • 上傳圖片 → R2 儲存成功 → URL 能存取
  • 修改貼文 → 資料正確更新
  • 刪除貼文 → 狀態變更 → 卡片顯示「已移除」

需要:測試用 Sanctum token + 測試後自動清理資料

GPS / 地圖互動 未覆蓋

  • 模擬 GPS 座標 → 地圖中心正確
  • 地圖標記點位和資料一致
  • 點擊地圖標記 → 對應卡片展開
  • 通報表單地址自動填入(geolocation)
  • 定位授權拒絕後的降級處理

Playwright 支援:context.setGeolocation({ latitude, longitude })

LINE 推播端到端 部分覆蓋

  • ✔ API 安全性(不 crash、擋非法輸入)
  • 配對觸發後飼主確實收到推播
  • 推播內容正確(地點、時間、照片)
  • 重複配對不發重複通知
  • 未加好友時優雅降級

需要:LINE Messaging API mock 或讀 Render log 驗證

聊天功能 未覆蓋

  • 開聊天室 → 訊息送出 → 對方收到
  • 上傳圖片到聊天室
  • 未讀數正確顯示
  • 對話列表排序正確(最新在上)

需要:兩個測試帳號同時操作(Playwright 支援多 context)

登入後的完整流程 未覆蓋

  • 個人檔案頁正確顯示寵物資料
  • 通知開關切換(目前前端 disabled)
  • 「標記已找到」流程
  • 協尋海報下載

需要:測試用 Sanctum token 注入 localStorage

視覺回歸 可選

  • 關鍵頁面截圖比對(防止 CSS 回歸)
  • 海報產出品質檢查(字體、排版、QR Code)
  • 深色/淺色模式一致性

Playwright 支援:expect(page).toHaveScreenshot()

下一步建議

優先級要做的事預估時間需要什麼
P0 寫入流程測試 — 通報送出 + 走失登記送出 2-3 小時 測試用 Sanctum token
P0 GPS 定位模擬 — 地圖中心 + 表單地址 1 小時 Playwright geolocation API
P1 聊天功能 — 雙帳號收發訊息 2 小時 兩組 Sanctum token
P1 登入後流程 — 個人檔案 + 標記找到 1-2 小時 一組 Sanctum token
P2 視覺回歸 — 關鍵頁面截圖基線 1 小時 無(Playwright 內建)
P2 CI 整合 — 每次 push 自動跑測試 30 分鐘 GitHub Actions workflow

環境變數

檔案:.env.e2e(從 .env.e2e.example 複製)
# 前端網址
E2E_BASE_URL=https://lead-me-home.vercel.app

# 後端 API 網址
E2E_API_BASE=https://lead-me-home-be.onrender.com

# 測試用 Sanctum token(登入後流程需要)
# 格式:{token_id}|{plaintext_token}
E2E_OWNER_TOKEN=
E2E_REPORTER_TOKEN=

常見問題

Q: 測試失敗了怎麼看原因?

npx playwright show-report 打開 HTML 報告,失敗的測試會有截圖和錯誤訊息。也可以用 npm run test:e2e:ui 進入 UI 模式,一步一步回放。

Q: Render free tier 冷啟動會導致測試 timeout?

有可能。測試裡已經把 API timeout 設為 30 秒。如果還是 timeout,先手動打一次 https://lead-me-home-be.onrender.com/api/health 喚醒,再跑測試。

Q: 怎麼加新的測試案例?

e2e/ 資料夾裡新增 .spec.ts 檔案,Playwright 會自動偵測。也可以在現有的 full-journey.spec.ts 裡加 test()

Q: 怎麼只跑手機版測試?
npx playwright test --project=mobile
Q: 怎麼模擬 GPS 定位?
// 在測試裡加這段
test.use({
  geolocation: { latitude: 25.033, longitude: 121.565 },
  permissions: ['geolocation'],
});