> ## Documentation Index
> Fetch the complete documentation index at: https://extension.js.org/llms.txt
> Use this file to discover all available pages before exploring further.

# 不用點擊就能測試工具列動作或鍵盤指令

> 從終端機或 CI 觸發 chrome.action.onClicked 與 chrome.commands.onCommand — 無頭、可重現、支援 Chrome 與 Firefox。當你需要 activeTab 時，還能在 Chromium 149+ 上發出真實手勢的點擊。

在 CI 中你無法點擊工具列圖示，而沒有 popup 的動作只有在 `onClicked` 觸發時才會做事。Extension.js 讓你能直接觸發這些處理函式 — 工具列動作與鍵盤快速鍵指令 — 讓你可以無頭執行它們，並對結果進行斷言。

這之所以可行，是因為 Extension.js 會在建置時擷取你的 `chrome.action.onClicked` 與 `chrome.commands.onCommand` 監聽器（開發期執行階段會在你的 background 程式碼之前載入），並在需要時重播它們。沒有視窗、沒有滑鼠、沒有 flaky 測試。

<Note>
  需要以 `--allow-control` 啟動的開發 session — 參見[除錯總覽](/docs/debugging)。
</Note>

## 觸發工具列動作

```bash theme={null}
pnpm extension open action --allow-control --output json
```

如果該動作有 `default_popup`，這會將它開啟。如果沒有，就會重播 `onClicked` 監聽器：

```json theme={null}
{
  "ok": true,
  "value": { "triggered": "onClicked", "listeners": 1, "gesture": false }
}
```

## 觸發鍵盤指令

依名稱重播任何 `chrome.commands.onCommand` 處理函式 — 快速鍵本身完全不會被按下：

```bash theme={null}
pnpm extension open command --name toggle-theme --allow-control --output json
```

```json theme={null}
{
  "ok": true,
  "value": {
    "triggered": "command",
    "command": "toggle-theme",
    "listeners": 1,
    "gesture": false
  }
}
```

## 唯一的注意事項：沒有使用者手勢

重播會呼叫你的處理函式，但它**不是**真正的使用者點擊，所以瀏覽器不會附帶使用者手勢。實際影響如下：

<Warning>
  真正的工具列點擊會授予暫時性的 **`activeTab`** 權限，並能滿足需要手勢的 API
  （`chrome.permissions.request`、互動式的
  `chrome.identity.getAuthToken`）。重播不會。所以依賴 `activeTab` 的處理函式 —
  在作用中分頁上呼叫 `chrome.scripting.executeScript`、
  `chrome.tabs.captureVisibleTab` — 行為會與真實點擊不同。
</Warning>

Extension.js 會在這件事重要時告訴你：結果會回報 `gesture: false`，並在你的 manifest 宣告了 `activeTab` 時加上 `warning`：

```json theme={null}
{
  "ok": true,
  "value": {
    "triggered": "onClicked",
    "listeners": 1,
    "gesture": false,
    "warning": "replayed without a user gesture: activeTab is NOT granted, so APIs that depend on it (scripting on the active tab, captureVisibleTab) behave differently than a real click"
  }
}
```

對大多數處理邏輯而言 — 切換狀態、寫入儲存空間、開啟分頁、傳訊息 — 重播正是你想要的：可重現且可寫成腳本。只有在你明確需要手勢語意時，才需要採用下面所說的真實點擊。

## 需要真正帶手勢的點擊？

真正的動作點擊會授予 `activeTab` — 已在 Chrome 149 上確認。重播刻意**不**帶有該手勢（因此依賴 `activeTab` 的處理函式行為會不同）。在極少數真的需要手勢語意的情況下，使用 [chrome-devtools-mcp](https://github.com/ChromeDevTools/chrome-devtools-mcp) 的 `trigger_extension_action`（僅限 Chromium），它會透過 Puppeteer 經由支援的 pipe transport 進行真實呼叫。

可以考慮[與 Extension.js 並行使用](/docs/integrations/chrome-devtools-mcp)：讓它負責真正帶手勢的瀏覽器驅動，Extension.js 則負責你日常會用到的、感知建置、跨瀏覽器、無頭的重播。

## 在 CI 中：斷言你的動作處理函式有執行

啟動 session、觸發動作、對副作用進行斷言 — 不需要瀏覽器 UI、不需要 Playwright。這就是一個 GitHub Actions job 的完整流程：

```yaml theme={null}
- run: pnpm extension dev --browser=chromium --allow-control --wait --wait-format=json
- run: pnpm extension open action --browser=chromium --output json
- run: |
    pnpm extension storage get --key lastClickedAt --browser=chromium --output json \
      | node -e 'process.exit(JSON.parse(require("fs").readFileSync(0)).value?.lastClickedAt ? 0 : 1)'
```

由於觸發是可重現且無頭的，它是個可靠的檢核點 — Extension.js 自家的 `smoke:open-action` 檢查正是走相同路徑，它會觸發動作 *以及* 一個指令，再讀回 `chrome.storage` 來證明每個處理函式確實有執行。

完整 pipeline 請見 [CI 範本](/docs/workflows/ci-templates#test-handlers-without-a-browser-ui)。

## 跨瀏覽器

`open action` 與 `open command` 建構在瀏覽器內伴隨程式之上，因此能在 **Chrome 與 Firefox** 上執行 — 它們只觸及 `addListener`，沒有任何引擎專屬的程式碼，且兩個瀏覽器都已驗證。同一份 `background.service_worker` 原始碼也能在 Firefox 上運作：Extension.js 會在 Firefox 建置時把它轉換成 `background.scripts` 的 event page。（真實帶手勢的點擊僅限 Chromium，由 chrome-devtools-mcp 提供 — 見前述。）

## 下一步

* [除錯總覽](/docs/debugging) — 完整的控制介面。
* [CI 範本](/docs/workflows/ci-templates) — 將此整合進 PR 檢核流程。
* [同時執行兩個 MCP server](/docs/integrations/chrome-devtools-mcp) — 並列比較還原度與可攜性。
