何時從靜態推理切換到量測工具、何時從 DevTools 升級到 Playwright、何時把 debug 過程寫成測試。

適用:CSS / DOM debug、layout 卡關、不確定該用哪個工具。 不適用:純邏輯 bug(這時 logging / debugger 比 layout 工具有用)。

自包含聲明:閱讀本文件不需要先讀其他 reference。本文件涵蓋四種工具的 ROI 對照、切換時機、最低門檻入口。


何時參閱本文件

訊號該做的第一件事
推理 ≥ 2 次失敗切到 playwright browser_evaluate
視覺截圖溝通迴圈卡住、雙方對「哪裡不對」沒共識切到 playwright + 量化資料(rect / style)
Layout 在某些狀態下錯、其他狀態下對切到 playwright、量不同狀態的 bounding rect
改 CSS 不生效、specificity 看起來對切到 playwright、量 computed style
同一個版型 bug 第 2 次出現切到「寫成 playwright 測試」固化
一次性確認 DOM 結構、不會重複查用 DevTools 即可、不需要起 server

為什麼工具切換要早、不該等到推理徹底失敗

CSS 行為由「規則 + DOM tree + 樣式繼承 + 框架渲染」四個變數共同決定。靜態推理只能基於假設的 DOM tree — 假設錯了、推理就錯。視覺截圖只能傳達「結果是什麼」、無法傳達「為什麼」。

Playwright 的 browser_evaluate 直接執行 JS 在 live page、返回真實的 DOM tree、computed style、bounding rect — 把四個變數全部變成已知

門檻在第 2 次:第 1 次推理快(假設正確時一次到位);第 2 次推理失敗 → 假設可能錯 → 繼續推理會在錯誤假設上累積。Playwright 起步成本中、但後續穩定。


四種工具的 ROI 對照

方法取得資訊量起步成本重複成本可寫成測試
靜態 CSS 推理低 — 全是假設0
視覺截圖溝通中 — 只有結果
瀏覽器 DevTools高 — DOM + computed
Playwright browser_evaluate最高 — 程式化任意查詢是 — 同樣 query 可寫測試

選擇順序

情境工具
第 1 次推理(簡單修改、假設正確機率高)靜態推理 + 截圖
一次性確認、不重複查DevTools
推理 ≥ 2 次失敗 / 反覆 debugPlaywright browser_evaluate
同個版型 bug 第 2 次以上Playwright 測試固化

Playwright 在開發循環的三個位置

位置 1:假設驗證(寫 CSS 規則前)

確認 DOM 結構符合假設。

1async () => {
2  const drawer = document.querySelector('.pagefind-ui__drawer');
3  let chain = []; let n = drawer;
4  while (n && n !== document.body) {
5    chain.push(`${n.tagName}.${n.className}`);
6    n = n.parentElement;
7  }
8  return chain;
9}

返回值對照假設、發現 drawerform 的 child(不是 sibling)→ grid-row 控制無效、改方向。

位置 2:行為驗證(layout 規則寫完後)

驗證實際 layout 結果。

1async () => ({
2  rect: document.querySelector('.target').getBoundingClientRect(),
3  computedTop: getComputedStyle(document.querySelector('.target')).top,
4  computedDisplay: getComputedStyle(document.querySelector('.target')).display,
5})

位置 3:互動驗證(使用者操作後的狀態)

1async () => {
2  const input = document.querySelector('.search-input');
3  input.value = 'pre';
4  input.dispatchEvent(new Event('input', { bubbles: true }));
5  await new Promise(r => setTimeout(r, 1000));
6  return Array.from(document.querySelectorAll('.result'))
7    .filter(el => getComputedStyle(el).display !== 'none')
8    .map(el => el.textContent.slice(0, 50));
9}

第 2 次同個 bug → 寫成測試固化

第 1 次 debug 完、bug 修好。第 2 次同個版型問題(不同 commit / 不同 viewport)再出現 → debug 完後把 query 寫成 playwright 測試

1test('search scope is between form and results', async ({ page }) => {
2  await page.goto('/search/?q=pre');
3  const formRect = await page.locator('.pagefind-ui__form').boundingBox();
4  const scopeRect = await page.locator('.scope-toggle').boundingBox();
5  const resultsRect = await page.locator('.results').boundingBox();
6  expect(scopeRect.y).toBeGreaterThan(formRect.y + formRect.height);
7  expect(resultsRect.y).toBeGreaterThan(scopeRect.y + scopeRect.height);
8});

未來 layout 改動觸發 regression、CI 立刻發現、不需要再人工 debug。


Playwright 引入的最低門檻

1# 起本地 server(任何方式)
2python3 -m http.server 8000 --directory public
3# 或 hugo server
4hugo server

Playwright MCP 提供的核心工具:

  • browser_navigate(url) — 開頁
  • browser_evaluate(fn) — 執行 JS 拿結果
  • browser_take_screenshot() — 截圖
  • browser_snapshot() — accessibility tree

寫一個 evaluate fn ≈ 30 行 JS。比反覆推理快得多。


主動切換訊號(不要等使用者打斷)

當以下任一觸發、執行者要主動提:「我推理 2 次失敗了、我們起 server、用 playwright 量 live DOM 確認假設」。不要等到第 5 次才切

訊號對外回報句式
同方向 CSS 規則改了 2 次都不生效「我假設 X 是 Y、playwright 一查就知道、要起 server?」
截圖看起來對 / 不對、但雙方對「為什麼」沒共識「用 playwright 量 bounding rect、量化比較好?」
改完 JS 後元素被還原「playwright 量 framework 重渲染週期、確認時機」
Layout 在某些 state 下錯、其他對「我用 playwright 各 state 量一次 rect、做對照」

Wrong vs Right 對照

範例 1:CSS 不生效

1/* 改了 3 次 specificity、還是沒生效 */
2.target { color: red; }                    /* 失敗 */
3.parent .target { color: red; }            /* 失敗 */
4.parent .container .target { color: red; } /* 失敗 */
5.parent .container .target { color: red !important; } /* 失敗 */

1.target { color: red; }

第 2 次失敗 → 切 playwright:

1async () => getComputedStyle(document.querySelector('.target')).color
2// 返回 "rgb(0, 0, 255)" — 不是我寫的紅色
1async () => {
2  const el = document.querySelector('.target');
3  return Array.from(getMatchedCSSRules?.(el) || [])
4    .map(r => r.cssText);
5}
6// 看到 vendor 的 .pagefind .target { color: blue !important } 在贏

→ 換方向:用 CSS Layers 把 vendor CSS 包進 layer、自家 unlayered 自動贏。

範例 2:Layout 在 mobile viewport 錯

反覆推理 + 在 DevTools 切 viewport 視覺確認 → 改 → 失敗 → 改 → 失敗。

第 2 次推理失敗、切 playwright:

1async () => {
2  await page.setViewportSize({ width: 375, height: 667 });
3  return {
4    h1: document.querySelector('h1').getBoundingClientRect(),
5    form: document.querySelector('form').getBoundingClientRect(),
6    scope: document.querySelector('.scope').getBoundingClientRect(),
7  };
8}

量化資料 → 立刻看到「scope 的 top 比 form 的 bottom 小 12px」→ overlap → 改 form margin-bottom。


自檢清單(dogfooding)

debug 卡關時:

  • 我推理失敗幾次了?≥ 2 次 → 該切換工具
  • 我能說出「假設是什麼、用什麼工具能驗證」嗎?
  • 切到 playwright 之前、有沒有試圖用更努力的推理多撐一次?(如果有 → 停)
  • 第 2 次同個版型 bug 出現時、有沒有寫成測試固化?
  • 對外回報切換工具的提案、有沒有寫得具體(要起哪個 server、量什麼)?

延伸閱讀

對應的事後檢討(在 content/report/):


Last Updated: 2026-04-26 Version: 0.1.0