<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Web on Tarragon</title><link>https://tarrragon.github.io/blog/tags/web/</link><description>Recent content in Web on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 19 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/web/index.xml" rel="self" type="application/rss+xml"/><item><title>Playwright 瀏覽器驗證流程</title><link>https://tarrragon.github.io/blog/testing/04-ui-automation/playwright-verification/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/testing/04-ui-automation/playwright-verification/</guid><description>&lt;p>Playwright 是瀏覽器自動化工具，在真實瀏覽器中執行 UI 操作並驗證結果。和 Flutter 的 widget test 不同，Playwright 操作的是瀏覽器中的 DOM 元素，驗證的是使用者在瀏覽器中實際看到的畫面。&lt;/p>
&lt;h2 id="playwright-和-widget-test-的互補">Playwright 和 widget test 的互補&lt;/h2>
&lt;p>Widget test 在 Flutter test framework 中執行，不需要瀏覽器，驗證的是 widget tree 的結構和狀態。Playwright 在真實瀏覽器中執行，驗證的是渲染後的 DOM 和視覺呈現。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>Widget test&lt;/th>
 &lt;th>Playwright&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>執行環境&lt;/td>
 &lt;td>Flutter test framework&lt;/td>
 &lt;td>真實瀏覽器（Chromium 等）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>驗證對象&lt;/td>
 &lt;td>Widget tree 結構&lt;/td>
 &lt;td>DOM 元素和視覺呈現&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>速度&lt;/td>
 &lt;td>毫秒級&lt;/td>
 &lt;td>秒級&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>穩定性&lt;/td>
 &lt;td>高（無瀏覽器差異）&lt;/td>
 &lt;td>中（瀏覽器行為差異）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>適用場景&lt;/td>
 &lt;td>邏輯驗證、狀態覆蓋&lt;/td>
 &lt;td>視覺驗證、跨瀏覽器相容&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>CSS 驗證&lt;/td>
 &lt;td>無法驗證 CSS 渲染&lt;/td>
 &lt;td>可以驗證 CSS 效果&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>兩者的分工：widget test 驗證「邏輯上正確」（該有的元素存在、該觸發的事件發生），Playwright 驗證「視覺上正確」（元素在正確的位置、顏色和大小符合設計）。&lt;/p>
&lt;h2 id="playwright-test-的基本結構">Playwright test 的基本結構&lt;/h2>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">test&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">expect&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;@playwright/test&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="nx">test&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;terminal screen shows connection status&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="p">({&lt;/span> &lt;span class="nx">page&lt;/span> &lt;span class="p">})&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="nx">page&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="kr">goto&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;http://localhost:8080&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> 
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 點擊連線按鈕
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">page&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">click&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;text=Connect Terminal&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> 
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 等待畫面轉換
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">page&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">waitForSelector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;[data-testid=&amp;#34;terminal-screen&amp;#34;]&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> 
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 驗證連線狀態顯示
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">status&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">page&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">locator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;[data-testid=&amp;#34;connection-status&amp;#34;]&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="nx">expect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">status&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">toBeVisible&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="p">});&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="三個位置的斷言">三個位置的斷言&lt;/h3>
&lt;p>Playwright test 中的斷言放在三個位置，各自驗證不同的東西：&lt;/p>
&lt;p>&lt;strong>假設斷言（test 開頭）&lt;/strong>：驗證 test 的前置條件。頁面載入成功、初始狀態正確。如果假設斷言失敗，test 的後續結果不可信。&lt;/p>
&lt;p>&lt;strong>行為斷言（操作之後）&lt;/strong>：驗證 UI 操作的即時效果。點擊按鈕後 dialog 出現、表單提交後顯示成功訊息。&lt;/p>
&lt;p>&lt;strong>互動斷言（流程結束）&lt;/strong>：驗證完整操作流程的最終狀態。多步驟操作完成後畫面回到預期狀態。&lt;/p>
&lt;h2 id="selector-策略">Selector 策略&lt;/h2>
&lt;p>Playwright 用 selector 定位 DOM 元素。Selector 的穩定性決定了 test 的維護成本。&lt;/p>
&lt;h3 id="推薦data-testid">推薦：data-testid&lt;/h3>
&lt;p>在 HTML 元素上加 &lt;code>data-testid&lt;/code> 屬性，Playwright 用 &lt;code>[data-testid=&amp;quot;xxx&amp;quot;]&lt;/code> 定位。&lt;code>data-testid&lt;/code> 不受 CSS class 改名、文字內容變更、DOM 結構調整影響。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">button&lt;/span> &lt;span class="na">data-testid&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;connect-button&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Connect Terminal&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">button&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="可接受文字內容">可接受：文字內容&lt;/h3>
&lt;p>用 &lt;code>text=Connect Terminal&lt;/code> 定位。在按鈕文字穩定的場景下可用，但多語系支援或文案調整時會斷。&lt;/p>
&lt;h3 id="避免css-selector">避免：CSS selector&lt;/h3>
&lt;p>用 &lt;code>.btn-primary&lt;/code> 或 &lt;code>#main-content &amp;gt; div:nth-child(2)&lt;/code> 定位。CSS class 和 DOM 結構的改動頻率高，test 頻繁因無關變更而失敗。&lt;/p>
&lt;h2 id="和開發伺服器的整合">和開發伺服器的整合&lt;/h2>
&lt;p>Playwright test 需要一個正在運行的 web 應用。整合方式：&lt;/p>
&lt;p>&lt;strong>手動啟動&lt;/strong>：開發者先啟動 dev server，再跑 Playwright test。適合本地開發。&lt;/p>
&lt;p>&lt;strong>自動啟動&lt;/strong>：Playwright 設定檔中指定 &lt;code>webServer&lt;/code> 配置，Playwright 自動啟動 dev server，test 結束後自動停止。適合 CI。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1">// playwright.config.ts
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">defineConfig&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="nx">webServer&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="nx">command&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;npm run dev&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="nx">port&lt;/span>: &lt;span class="kt">8080&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="nx">reuseExistingServer&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="o">!&lt;/span>&lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">CI&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">&lt;span class="p">});&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>視覺比對 → &lt;a href="https://tarrragon.github.io/blog/testing/04-ui-automation/visual-regression/" data-link-title="螢幕截圖比對" data-link-desc="Visual regression testing — 用螢幕截圖比對偵測非預期的視覺變化、baseline 管理和 diff 閾值設定">螢幕截圖比對&lt;/a>&lt;/li>
&lt;li>狀態覆蓋策略 → &lt;a href="https://tarrragon.github.io/blog/testing/04-ui-automation/state-coverage-strategy/" data-link-title="Widget test 的狀態覆蓋策略" data-link-desc="從畫面狀態矩陣推導 widget test case — 每個狀態的顯示、操作、退出路徑都是獨立的斷言目標">Widget test 的狀態覆蓋策略&lt;/a>&lt;/li>
&lt;li>導航路徑 test → &lt;a href="https://tarrragon.github.io/blog/testing/04-ui-automation/navigation-path-test/" data-link-title="導航路徑 test" data-link-desc="Back 按鈕、route 可達性、go vs push 語意 — 驗證使用者能從任何畫面回到預期的位置">導航路徑 test&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Playwright 是瀏覽器自動化工具，在真實瀏覽器中執行 UI 操作並驗證結果。和 Flutter 的 widget test 不同，Playwright 操作的是瀏覽器中的 DOM 元素，驗證的是使用者在瀏覽器中實際看到的畫面。</p>
<h2 id="playwright-和-widget-test-的互補">Playwright 和 widget test 的互補</h2>
<p>Widget test 在 Flutter test framework 中執行，不需要瀏覽器，驗證的是 widget tree 的結構和狀態。Playwright 在真實瀏覽器中執行，驗證的是渲染後的 DOM 和視覺呈現。</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>Widget test</th>
          <th>Playwright</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>執行環境</td>
          <td>Flutter test framework</td>
          <td>真實瀏覽器（Chromium 等）</td>
      </tr>
      <tr>
          <td>驗證對象</td>
          <td>Widget tree 結構</td>
          <td>DOM 元素和視覺呈現</td>
      </tr>
      <tr>
          <td>速度</td>
          <td>毫秒級</td>
          <td>秒級</td>
      </tr>
      <tr>
          <td>穩定性</td>
          <td>高（無瀏覽器差異）</td>
          <td>中（瀏覽器行為差異）</td>
      </tr>
      <tr>
          <td>適用場景</td>
          <td>邏輯驗證、狀態覆蓋</td>
          <td>視覺驗證、跨瀏覽器相容</td>
      </tr>
      <tr>
          <td>CSS 驗證</td>
          <td>無法驗證 CSS 渲染</td>
          <td>可以驗證 CSS 效果</td>
      </tr>
  </tbody>
</table>
<p>兩者的分工：widget test 驗證「邏輯上正確」（該有的元素存在、該觸發的事件發生），Playwright 驗證「視覺上正確」（元素在正確的位置、顏色和大小符合設計）。</p>
<h2 id="playwright-test-的基本結構">Playwright test 的基本結構</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">test</span><span class="p">,</span> <span class="nx">expect</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;@playwright/test&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">test</span><span class="p">(</span><span class="s1">&#39;terminal screen shows connection status&#39;</span><span class="p">,</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">page</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s1">&#39;http://localhost:8080&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="c1">// 點擊連線按鈕
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"></span>  <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">click</span><span class="p">(</span><span class="s1">&#39;text=Connect Terminal&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="c1">// 等待畫面轉換
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span>  <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForSelector</span><span class="p">(</span><span class="s1">&#39;[data-testid=&#34;terminal-screen&#34;]&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="c1">// 驗證連線狀態顯示
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"></span>  <span class="kr">const</span> <span class="nx">status</span> <span class="o">=</span> <span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s1">&#39;[data-testid=&#34;connection-status&#34;]&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">status</span><span class="p">).</span><span class="nx">toBeVisible</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><h3 id="三個位置的斷言">三個位置的斷言</h3>
<p>Playwright test 中的斷言放在三個位置，各自驗證不同的東西：</p>
<p><strong>假設斷言（test 開頭）</strong>：驗證 test 的前置條件。頁面載入成功、初始狀態正確。如果假設斷言失敗，test 的後續結果不可信。</p>
<p><strong>行為斷言（操作之後）</strong>：驗證 UI 操作的即時效果。點擊按鈕後 dialog 出現、表單提交後顯示成功訊息。</p>
<p><strong>互動斷言（流程結束）</strong>：驗證完整操作流程的最終狀態。多步驟操作完成後畫面回到預期狀態。</p>
<h2 id="selector-策略">Selector 策略</h2>
<p>Playwright 用 selector 定位 DOM 元素。Selector 的穩定性決定了 test 的維護成本。</p>
<h3 id="推薦data-testid">推薦：data-testid</h3>
<p>在 HTML 元素上加 <code>data-testid</code> 屬性，Playwright 用 <code>[data-testid=&quot;xxx&quot;]</code> 定位。<code>data-testid</code> 不受 CSS class 改名、文字內容變更、DOM 結構調整影響。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">&lt;</span><span class="nt">button</span> <span class="na">data-testid</span><span class="o">=</span><span class="s">&#34;connect-button&#34;</span><span class="p">&gt;</span>Connect Terminal<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span></span></span></code></pre></div><h3 id="可接受文字內容">可接受：文字內容</h3>
<p>用 <code>text=Connect Terminal</code> 定位。在按鈕文字穩定的場景下可用，但多語系支援或文案調整時會斷。</p>
<h3 id="避免css-selector">避免：CSS selector</h3>
<p>用 <code>.btn-primary</code> 或 <code>#main-content &gt; div:nth-child(2)</code> 定位。CSS class 和 DOM 結構的改動頻率高，test 頻繁因無關變更而失敗。</p>
<h2 id="和開發伺服器的整合">和開發伺服器的整合</h2>
<p>Playwright test 需要一個正在運行的 web 應用。整合方式：</p>
<p><strong>手動啟動</strong>：開發者先啟動 dev server，再跑 Playwright test。適合本地開發。</p>
<p><strong>自動啟動</strong>：Playwright 設定檔中指定 <code>webServer</code> 配置，Playwright 自動啟動 dev server，test 結束後自動停止。適合 CI。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// playwright.config.ts
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="kr">export</span> <span class="k">default</span> <span class="nx">defineConfig</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nx">webServer</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nx">command</span><span class="o">:</span> <span class="s1">&#39;npm run dev&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="nx">port</span>: <span class="kt">8080</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="nx">reuseExistingServer</span><span class="o">:</span> <span class="o">!</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">CI</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><h2 id="下一步路由">下一步路由</h2>
<ul>
<li>視覺比對 → <a href="/blog/testing/04-ui-automation/visual-regression/" data-link-title="螢幕截圖比對" data-link-desc="Visual regression testing — 用螢幕截圖比對偵測非預期的視覺變化、baseline 管理和 diff 閾值設定">螢幕截圖比對</a></li>
<li>狀態覆蓋策略 → <a href="/blog/testing/04-ui-automation/state-coverage-strategy/" data-link-title="Widget test 的狀態覆蓋策略" data-link-desc="從畫面狀態矩陣推導 widget test case — 每個狀態的顯示、操作、退出路徑都是獨立的斷言目標">Widget test 的狀態覆蓋策略</a></li>
<li>導航路徑 test → <a href="/blog/testing/04-ui-automation/navigation-path-test/" data-link-title="導航路徑 test" data-link-desc="Back 按鈕、route 可達性、go vs push 語意 — 驗證使用者能從任何畫面回到預期的位置">導航路徑 test</a></li>
</ul>
]]></content:encoded></item><item><title>Zellij Web Client 外網連線教學</title><link>https://tarrragon.github.io/blog/linux/tools/cli/zellij-remote-web-client/</link><pubDate>Mon, 09 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/tools/cli/zellij-remote-web-client/</guid><description>&lt;p>Zellij Web Client 讓他人透過瀏覽器連線到指定的 Zellij session，承擔的責任是把終端機多工環境分享給沒有 SSH 連線的協作者。本文承接 &lt;a href="https://tarrragon.github.io/blog/linux/tools/cli/cli-graphical-tools-overview/" data-link-title="終端機圖形化工具總覽：遠端操作下的 TUI、文字圖表與多工器" data-link-desc="在純文字終端機裡用 ASCII 與製圖字元做出監控儀表板、資料圖表與多視窗操作的工具總覽，並針對 SSH 伺服器、手機平板、低頻寬三種遠端情境給出選型判讀。">終端機圖形化工具總覽&lt;/a> 的多工器分類；zellij 的本機 pane 操作見 &lt;a href="https://tarrragon.github.io/blog/linux/tools/cli/zellij-pane/" data-link-title="Zellij 多終端機操作指南" data-link-desc="Zellij pane 的佈局查看、內容讀取、大小調整等 CLI 操作方式，適合搭配 AI 工具使用。">Zellij 多終端機操作指南&lt;/a>、tmux 的持久化基礎見 &lt;a href="https://tarrragon.github.io/blog/linux/tools/cli/tmux-persistence-and-basics/" data-link-title="tmux 基礎：遠端 session 持久化與基本操作" data-link-desc="tmux 終端機多工器的遠端使用核心：detach/reattach 讓 session 脫離連線生命週期、prefix key 與 window/pane 操作、手機友善的快捷鍵調校，以及 tmux 與 zellij 的選型對照。">tmux 基礎&lt;/a>。&lt;/p>
&lt;hr>
&lt;h2 id="安裝-zellij">安裝 Zellij&lt;/h2>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># macOS&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">brew install zellij
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># Linux（使用安裝腳本）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">bash &amp;lt;&lt;span class="o">(&lt;/span>curl -L zellij.dev/launch&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="c1"># Windows（需要支援原生 Windows 的版本，詳見 GitHub Releases）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">&lt;span class="c1"># 從 https://github.com/zellij-org/zellij/releases 下載 Windows 版 .zip&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl">&lt;span class="c1"># 解壓後將 zellij.exe 加入 PATH&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>確認版本（需 v0.43.0 以上）：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">zellij --version&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="事前準備">事前準備&lt;/h2>
&lt;ul>
&lt;li>一個網域名稱（或固定 IP）&lt;/li>
&lt;li>SSL 憑證（對外連線強制要求）&lt;/li>
&lt;li>SSH 連線能力（如需遠端操作主機）→ 參考 &lt;a href="https://tarrragon.github.io/blog/work-log/ssh-key-%E8%A8%AD%E5%AE%9A%E7%AD%86%E8%A8%98macos-/-linux-/-windows/">SSH Key 設定筆記&lt;/a>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="步驟一取得-ssl-憑證">步驟一：取得 SSL 憑證&lt;/h2>
&lt;p>外網連線強制使用 HTTPS，必須提供 SSL 憑證。&lt;/p>
&lt;blockquote>
&lt;p>取得 Let&amp;rsquo;s Encrypt 憑證的 &lt;code>certbot&lt;/code> 指令需真實網域、本機未實機驗證；自簽憑證的 &lt;code>openssl&lt;/code> 指令、以及 zellij web server 啟停與 token 管理已在 localhost 實機驗證。&lt;/p>&lt;/blockquote>
&lt;h3 id="使用-lets-encrypt免費推薦">使用 Let&amp;rsquo;s Encrypt（免費，推薦）&lt;/h3>
&lt;p>需要先安裝 &lt;code>certbot&lt;/code>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># macOS&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">brew install certbot
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># Ubuntu / Debian&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">sudo apt install certbot
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="c1"># Windows（使用 Chocolatey）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">choco install certbot
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl">&lt;span class="c1"># 或使用 win-acme（Windows 原生替代方案）：https://www.win-acme.com/&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>申請憑證（將 &lt;code>your-domain.com&lt;/code> 換成實際網域）：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">sudo certbot certonly --standalone -d your-domain.com&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>Windows 上若未使用 WSL，建議改用 &lt;a href="https://www.win-acme.com/">win-acme&lt;/a>，操作更直覺。&lt;/p>&lt;/blockquote>
&lt;p>憑證預設存放在：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl"># macOS / Linux
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">/etc/letsencrypt/live/your-domain.com/fullchain.pem # 憑證
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">/etc/letsencrypt/live/your-domain.com/privkey.pem # 私鑰
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"># Windows（certbot）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">C:\Certbot\live\your-domain.com\fullchain.pem
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">C:\Certbot\live\your-domain.com\privkey.pem&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="使用自簽憑證測試用">使用自簽憑證（測試用）&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days &lt;span class="m">365&lt;/span> -nodes&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>注意：自簽憑證會讓瀏覽器在連線時顯示安全警告，於測試環境手動選擇繼續即可；正式對外服務改用上面的 Let&amp;rsquo;s Encrypt 憑證。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="步驟二開放防火牆-port">步驟二：開放防火牆 Port&lt;/h2>
&lt;p>Zellij web server 預設只綁本機 &lt;code>127.0.0.1:8082&lt;/code>，要讓外網連入必須顯式綁到對外位址（見步驟四的 &lt;code>--ip 0.0.0.0&lt;/code>）並開放對應 port。本教學以 port &lt;code>3000&lt;/code> 為例（port 可自選），需對外開放這個 port：&lt;/p></description><content:encoded><![CDATA[<p>Zellij Web Client 讓他人透過瀏覽器連線到指定的 Zellij session，承擔的責任是把終端機多工環境分享給沒有 SSH 連線的協作者。本文承接 <a href="/blog/linux/tools/cli/cli-graphical-tools-overview/" data-link-title="終端機圖形化工具總覽：遠端操作下的 TUI、文字圖表與多工器" data-link-desc="在純文字終端機裡用 ASCII 與製圖字元做出監控儀表板、資料圖表與多視窗操作的工具總覽，並針對 SSH 伺服器、手機平板、低頻寬三種遠端情境給出選型判讀。">終端機圖形化工具總覽</a> 的多工器分類；zellij 的本機 pane 操作見 <a href="/blog/linux/tools/cli/zellij-pane/" data-link-title="Zellij 多終端機操作指南" data-link-desc="Zellij pane 的佈局查看、內容讀取、大小調整等 CLI 操作方式，適合搭配 AI 工具使用。">Zellij 多終端機操作指南</a>、tmux 的持久化基礎見 <a href="/blog/linux/tools/cli/tmux-persistence-and-basics/" data-link-title="tmux 基礎：遠端 session 持久化與基本操作" data-link-desc="tmux 終端機多工器的遠端使用核心：detach/reattach 讓 session 脫離連線生命週期、prefix key 與 window/pane 操作、手機友善的快捷鍵調校，以及 tmux 與 zellij 的選型對照。">tmux 基礎</a>。</p>
<hr>
<h2 id="安裝-zellij">安裝 Zellij</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># macOS</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">brew install zellij
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># Linux（使用安裝腳本）</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">bash &lt;<span class="o">(</span>curl -L zellij.dev/launch<span class="o">)</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># Windows（需要支援原生 Windows 的版本，詳見 GitHub Releases）</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"># 從 https://github.com/zellij-org/zellij/releases 下載 Windows 版 .zip</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="c1"># 解壓後將 zellij.exe 加入 PATH</span></span></span></code></pre></div><p>確認版本（需 v0.43.0 以上）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">zellij --version</span></span></code></pre></div><hr>
<h2 id="事前準備">事前準備</h2>
<ul>
<li>一個網域名稱（或固定 IP）</li>
<li>SSL 憑證（對外連線強制要求）</li>
<li>SSH 連線能力（如需遠端操作主機）→ 參考 <a href="https://tarrragon.github.io/blog/work-log/ssh-key-%E8%A8%AD%E5%AE%9A%E7%AD%86%E8%A8%98macos-/-linux-/-windows/">SSH Key 設定筆記</a></li>
</ul>
<hr>
<h2 id="步驟一取得-ssl-憑證">步驟一：取得 SSL 憑證</h2>
<p>外網連線強制使用 HTTPS，必須提供 SSL 憑證。</p>
<blockquote>
<p>取得 Let&rsquo;s Encrypt 憑證的 <code>certbot</code> 指令需真實網域、本機未實機驗證；自簽憑證的 <code>openssl</code> 指令、以及 zellij web server 啟停與 token 管理已在 localhost 實機驗證。</p></blockquote>
<h3 id="使用-lets-encrypt免費推薦">使用 Let&rsquo;s Encrypt（免費，推薦）</h3>
<p>需要先安裝 <code>certbot</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># macOS</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">brew install certbot
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># Ubuntu / Debian</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">sudo apt install certbot
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># Windows（使用 Chocolatey）</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">choco install certbot
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="c1"># 或使用 win-acme（Windows 原生替代方案）：https://www.win-acme.com/</span></span></span></code></pre></div><p>申請憑證（將 <code>your-domain.com</code> 換成實際網域）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">sudo certbot certonly --standalone -d your-domain.com</span></span></code></pre></div><blockquote>
<p>Windows 上若未使用 WSL，建議改用 <a href="https://www.win-acme.com/">win-acme</a>，操作更直覺。</p></blockquote>
<p>憑證預設存放在：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl"># macOS / Linux
</span></span><span class="line"><span class="ln">2</span><span class="cl">/etc/letsencrypt/live/your-domain.com/fullchain.pem   # 憑證
</span></span><span class="line"><span class="ln">3</span><span class="cl">/etc/letsencrypt/live/your-domain.com/privkey.pem      # 私鑰
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"># Windows（certbot）
</span></span><span class="line"><span class="ln">6</span><span class="cl">C:\Certbot\live\your-domain.com\fullchain.pem
</span></span><span class="line"><span class="ln">7</span><span class="cl">C:\Certbot\live\your-domain.com\privkey.pem</span></span></code></pre></div><h3 id="使用自簽憑證測試用">使用自簽憑證（測試用）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days <span class="m">365</span> -nodes</span></span></code></pre></div><blockquote>
<p>注意：自簽憑證會讓瀏覽器在連線時顯示安全警告，於測試環境手動選擇繼續即可；正式對外服務改用上面的 Let&rsquo;s Encrypt 憑證。</p></blockquote>
<hr>
<h2 id="步驟二開放防火牆-port">步驟二：開放防火牆 Port</h2>
<p>Zellij web server 預設只綁本機 <code>127.0.0.1:8082</code>，要讓外網連入必須顯式綁到對外位址（見步驟四的 <code>--ip 0.0.0.0</code>）並開放對應 port。本教學以 port <code>3000</code> 為例（port 可自選），需對外開放這個 port：</p>
<blockquote>
<p>以下防火牆指令（<code>ufw</code> / <code>pf</code> / Windows Defender）依各平台官方用法、環境特定、本機未實機驗證。</p></blockquote>
<h3 id="linuxufw">Linux（ufw）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">sudo ufw allow 3000/tcp
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># 或指定來源 IP（更安全）</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">sudo ufw allow from 1.2.3.4 to any port <span class="m">3000</span></span></span></code></pre></div><h3 id="macos">macOS</h3>
<p>macOS 內建的防火牆是應用程式層級的，無法直接開放特定 port。通常有兩種做法：</p>
<ol>
<li><strong>系統偏好設定</strong> → 網路 → 防火牆 → 確認沒有擋住 Zellij</li>
<li><strong>使用 <code>pf</code></strong>（進階，通常不需要）：</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 新增規則到 /etc/pf.conf</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;pass in proto tcp from any to any port 3000&#34;</span> <span class="p">|</span> sudo tee -a /etc/pf.conf
</span></span><span class="line"><span class="ln">3</span><span class="cl">sudo pfctl -f /etc/pf.conf</span></span></code></pre></div><blockquote>
<p>macOS 預設防火牆通常不會擋住主動開啟的服務，多數情況下不需要額外設定。如果是在家用網路，記得在路由器設定 port forwarding。</p></blockquote>
<h3 id="windows">Windows</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="ln">1</span><span class="cl"><span class="c"># 使用 Windows Defender Firewall（以系統管理員執行 PowerShell）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">New-NetFirewallRule</span> <span class="n">-DisplayName</span> <span class="s2">&#34;Zellij Web&#34;</span> <span class="n">-Direction</span> <span class="n">Inbound</span> <span class="n">-Protocol</span> <span class="n">TCP</span> <span class="n">-LocalPort</span> <span class="mf">3000</span> <span class="n">-Action</span> <span class="n">Allow</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c"># 或限制來源 IP（更安全）</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="nb">New-NetFirewallRule</span> <span class="n">-DisplayName</span> <span class="s2">&#34;Zellij Web&#34;</span> <span class="n">-Direction</span> <span class="n">Inbound</span> <span class="n">-Protocol</span> <span class="n">TCP</span> <span class="n">-LocalPort</span> <span class="mf">3000</span> <span class="n">-RemoteAddress</span> <span class="mf">1.2</span><span class="p">.</span><span class="py">3</span><span class="p">.</span><span class="py">4</span> <span class="n">-Action</span> <span class="n">Allow</span></span></span></code></pre></div><blockquote>
<p>Zellij 已支援原生 Windows，直接在 PowerShell 或 Windows Terminal 中執行即可。</p></blockquote>
<p>如果是雲端主機（AWS、GCP、Azure 等），記得同步在後台的安全群組開放 port 3000。</p>
<hr>
<h2 id="步驟三啟動-zellij">步驟三：啟動 Zellij</h2>
<p>先啟動一個 Zellij session：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">zellij</span></span></code></pre></div><hr>
<h2 id="步驟四啟動-web-server">步驟四：啟動 Web Server</h2>
<p>在 Zellij 內，按 <code>Ctrl+o</code> 然後按 <code>s</code> 開啟 share plugin，從 UI 啟動 web server。</p>
<p>或直接用 CLI 啟動並指定憑證：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">zellij web <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --ip 0.0.0.0 --port <span class="m">3000</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --cert /etc/letsencrypt/live/your-domain.com/fullchain.pem <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --key /etc/letsencrypt/live/your-domain.com/privkey.pem</span></span></code></pre></div><p>背景執行（daemon 模式）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">zellij web -d <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --ip 0.0.0.0 --port <span class="m">3000</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --cert /path/to/cert.pem <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --key /path/to/key.pem</span></span></code></pre></div><p>停止 web server：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">zellij web --stop</span></span></code></pre></div><p>確認 web server 執行狀態：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">zellij web --status</span></span></code></pre></div><p>Zellij web 預設綁 <code>127.0.0.1:8082</code>、只接受本機連線；對外服務必須用 <code>--ip 0.0.0.0</code> 顯式綁到對外位址、並用 <code>--port</code> 指定埠（本教學用 <code>3000</code>）。改用其他 port 時把 <code>--port</code> 一併調整（例如 <code>--port 8443</code>），防火牆規則也要同步改成該 port。</p>
<hr>
<h2 id="步驟五產生登入-token">步驟五：產生登入 Token</h2>
<p>為了安全，別人連線前需要用 token 登入，<strong>token 只會顯示一次</strong>，請立即複製。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">zellij web --create-token</span></span></code></pre></div><p>或在 share plugin（<code>Ctrl+o</code> + <code>s</code>）裡產生。</p>
<p>將 token 分享給要連線的人。</p>
<hr>
<h2 id="步驟六連線">步驟六：連線</h2>
<p>對方在瀏覽器輸入：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">https://your-domain.com:3000/實際-session-名稱</span></span></code></pre></div><p>首次連線會要求輸入 token，驗證後即可進入 session。若連線後畫面沒有回應，多半是 port 未對外開放，確認防火牆與雲端主機安全群組是否放行該 port。</p>
<hr>
<h2 id="連線後的行為">連線後的行為</h2>
<table>
  <thead>
      <tr>
          <th>情況</th>
          <th>結果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Session 正在執行</td>
          <td>直接 attach 進去</td>
      </tr>
      <tr>
          <td>Session 曾存在但已結束</td>
          <td>Zellij 自動重建（resurrection）</td>
      </tr>
      <tr>
          <td>全新 session 名稱</td>
          <td>建立新的 session</td>
      </tr>
  </tbody>
</table>
<p>多人連線時，每個人都有自己的游標，可以同時操作。</p>
<hr>
<h2 id="安全建議">安全建議</h2>
<ul>
<li>Token 用完後記得撤銷：從 share plugin 或 CLI 管理</li>
<li>盡量限制開放的來源 IP，避免對全網開放</li>
<li>不建議長期開啟 web server，用完就關</li>
<li>撤銷 token 時，所有對應的 session token 也會一併失效</li>
</ul>
<hr>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>zellij 的本機 pane 操作（查看佈局、讀取其他 pane、調整大小）：<a href="/blog/linux/tools/cli/zellij-pane/" data-link-title="Zellij 多終端機操作指南" data-link-desc="Zellij pane 的佈局查看、內容讀取、大小調整等 CLI 操作方式，適合搭配 AI 工具使用。">Zellij 多終端機操作指南</a>。</li>
<li>不需要瀏覽器、純 SSH 的多工器持久化：<a href="/blog/linux/tools/cli/tmux-persistence-and-basics/" data-link-title="tmux 基礎：遠端 session 持久化與基本操作" data-link-desc="tmux 終端機多工器的遠端使用核心：detach/reattach 讓 session 脫離連線生命週期、prefix key 與 window/pane 操作、手機友善的快捷鍵調校，以及 tmux 與 zellij 的選型對照。">tmux 基礎</a>。</li>
<li>多工器在整個遠端工具選型中的定位：<a href="/blog/linux/tools/cli/cli-graphical-tools-overview/" data-link-title="終端機圖形化工具總覽：遠端操作下的 TUI、文字圖表與多工器" data-link-desc="在純文字終端機裡用 ASCII 與製圖字元做出監控儀表板、資料圖表與多視窗操作的工具總覽，並針對 SSH 伺服器、手機平板、低頻寬三種遠端情境給出選型判讀。">終端機圖形化工具總覽</a>。</li>
</ul>
]]></content:encoded></item></channel></rss>