<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:media="http://search.yahoo.com/mrss/"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Kenmingの鮮思維</title>
	<atom:link href="https://kenming.idv.tw/feed/" rel="self" type="application/rss+xml" />
	<link>https://kenming.idv.tw</link>
	<description>不用牽掛過去，不必擔心未來，踏實於現在，就與過去和未來同在！</description>
	<lastBuildDate>Wed, 30 Sep 2026 12:43:52 +0000</lastBuildDate>
	<language>zh-TW</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.6</generator>

<image>
	<url>https://kenming.idv.tw/wp-content/medias/cropped-favicon-32x32.png</url>
	<title>Kenmingの鮮思維</title>
	<link>https://kenming.idv.tw</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>關於 Codex 與 ChatGPT 協作模式的實踐</title>
		<link>https://kenming.idv.tw/chatgpt-codex-collaboration-puppymart/</link>
					<comments>https://kenming.idv.tw/chatgpt-codex-collaboration-puppymart/#respond</comments>
		
		<dc:creator><![CDATA[Wang Kenming]]></dc:creator>
		<pubDate>Wed, 30 Sep 2026 12:40:25 +0000</pubDate>
				<category><![CDATA[AI應用與開發]]></category>
		<category><![CDATA[軟體架構-含微服務架構]]></category>
		<category><![CDATA[未分類]]></category>
		<category><![CDATA[ChatGPT]]></category>
		<category><![CDATA[AIAgent]]></category>
		<category><![CDATA[Codex]]></category>
		<category><![CDATA[寵物購物系統]]></category>
		<category><![CDATA[architecture]]></category>
		<category><![CDATA[DDD]]></category>
		<guid isPermaLink="false">https://kenming.idv.tw/?p=24167</guid>

					<description><![CDATA[<p>經過三天的實踐，採用我在 FB 社團分享所提及的 ChatGPT + Codex 協作模式，完成了 Puppy [&#8230;]</p>
<p>〈<a rel="nofollow" href="https://kenming.idv.tw/chatgpt-codex-collaboration-puppymart/">關於 Codex 與 ChatGPT 協作模式的實踐</a>〉這篇文章最早發佈於《<a rel="nofollow" href="https://kenming.idv.tw">Kenmingの鮮思維</a>》。</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">經過三天的實踐，採用我在<a href="https://www.facebook.com/groups/softthinking" target="_blank" rel="noreferrer noopener"> FB 社團</a>分享所提及的 ChatGPT + Codex 協作模式，完成了 PuppyMart 寵物購物系統第一版。</p>



<p class="wp-block-paragraph">整體分工是：</p>



<ul class="wp-block-list">
<li>ChatGPT（Sol 中級模式）負責文檔、規劃與 Review</li>



<li>Codex（Luna 極高模式）負責實作、測試與驗證</li>



<li>兩者再透過第三方 Bridge 橋接，使用的是 <code>codex-with-chatgpt</code> skill</li>
</ul>



<p class="wp-block-paragraph">最後完成的系統已涵蓋多個業務功能模組，包括訂購、庫存、出貨、付款 Mock、基本資料維護等。</p>



<p class="wp-block-paragraph"><br>整個過程使用 Plus 帳戶，5 Hrs 限制一次都沒有觸發，週額度總共也只用了約 30%！</p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">這次實踐更加證明了：<br><strong>「AI Agent 開發真正重要的，不只是模型有多強，而是怎麼有效規範它。」</strong><br></p>



<p class="wp-block-paragraph">我會先依據專案性質制定 Agent 行為規範，例如 <code>AGENTS.md</code> 與相關細部規範（技術堆疊、前端 UI、分層架構、測試，乃至資料庫設計等）；再定義專案文件結構，包括調研、需求、架構、追蹤與交接記錄；程式本身則先確立基本分層，例如前後端分離，以及後端採簡化 DDD 分層。</p>



<p class="wp-block-paragraph">接下來的工作流程大致是：</p>



<ul class="wp-block-list">
<li>我先把需要討論的議題交給 ChatGPT，由它整理成需求、架構或設計文件，再進一步轉成具體可執行的計畫與 Prompt，交給 Codex 照表實作。</li>



<li>Codex 在執行過程中同時負責測試與自我驗證，完成後回報執行成果，再交回 ChatGPT Review。</li>



<li>Review 通過後，再整理工作日誌；如果工作有切分階段，則建立 hand-off 交接記錄；最後才交由 Codex Commit 並 Push 到 GitHub Repository。</li>
</ul>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">也就是：</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">ChatGPT 負責思考、規劃與審查，Codex 負責執行與驗證。</p>
</blockquote>



<p class="wp-block-paragraph"><br>不過，我並不會在專案一開始，就把所有目錄、規範與文件結構設計得非常龐大。<br>我的做法反而是先採用自己過去累積下來的「最小模板」，再視專案需要安裝必要的 Skill，例如前端開發、需求規格整理、UI 驗證等，通常不會超過 10 個。<br>之後再隨著開發進度，逐步調整目錄、規範與文件。</p>



<p class="wp-block-paragraph"><br>我認為這比一開始就建立一套規範龐雜、流程繁瑣，卻不一定符合實際需求的 AI 開發治理架構，要實際得多。</p>



<p class="wp-block-paragraph"><br>這次 PuppyMart 的開發方式，我採用的是「<strong>跨業務模組的水平切分</strong>」加上「<strong>技術層級的垂直切分</strong>」。</p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">技術層級主要分成：</p>



<ul class="wp-block-list">
<li>中間層 API</li>



<li>前端 UI（React）</li>



<li>後端邏輯與資料存取</li>
</ul>



<p class="wp-block-paragraph">每一層完成後（一次會執行多個功能模組的開發），先使用 Mock 做隔離，讓它可以獨立驗證與測試；等各層都穩定後，再進行全端整合。</p>



<p class="wp-block-paragraph"><br>最後則跑整合測試、接受度測試，再加上 UI 自動化操作驗證，確認完成一個 Release。<br></p>



<p class="wp-block-paragraph">這種方式確實不算快。</p>



<p class="wp-block-paragraph">但如果拿我過去帶領約 5 人以上團隊開發同等規模系統的經驗來比較，正常至少也會需要兩個月左右。<br></p>



<p class="wp-block-paragraph">現在是一個人，加上 AI Agent，在三天內完成第一個可運作版本。<br>當然，如果全部工作都直接丟進 Codex，使用高階模型一次一路跑到底，速度當然會更快。</p>



<p class="wp-block-paragraph">但我目前更重視的是：<br><strong>可控性，以及品質。（當然還有 AI 開發成本的考量）</strong></p>



<p class="wp-block-paragraph">例如整合測試完成後，我覺得 UI 風格還不夠理想，而且發現一些操作細節缺失，如商品頁面沒有提供購買數量選擇。<br>這時我只需要把問題交給 ChatGPT，請它分析並制定修正計畫，再交由 Codex 實作。</p>



<p class="wp-block-paragraph">ChatGPT 在規劃時還會明確指出：這次修改只屬於前端 UI 與互動機制，不需要變更後端 API，也不應牽動後端邏輯。</p>



<p class="wp-block-paragraph"><br>因此整個修改範圍非常清楚，實作與驗證也很快就能完成。</p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">這種「先界定影響範圍，再讓 Agent 動手」的方式，是我目前認為 AI 輔助開發非常重要的一環。</p>



<p class="wp-block-paragraph"><br>接下來，我打算再把 PuppyMart 修整得更完整一些，之後會放到 GitHub，包含完整程式碼與可執行環境。</p>



<p class="wp-block-paragraph"><br>後續也預計製作 C#、Java / Spring 版本，甚至延伸到微服務架構版本。</p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">至於 ChatGPT 與 Codex 到底要怎麼透過 Bridge 協作，包括：</p>



<ul class="wp-block-list">
<li>Bridge 如何安裝與設定</li>



<li>ChatGPT 與 Codex 如何分工</li>



<li>Agent 規範如何設計</li>



<li>專案文件與目錄如何安排</li>



<li>Skill 如何選擇與安裝</li>



<li>如何建立一套可以重複使用、持續擴充的 AI 開發專案模板</li>
</ul>



<p class="wp-block-paragraph">個人也已整理成一堂大約 3 小時的<a href="https://kenming.idv.tw/course/codex-chatgpt-collaboration-lecture/" target="_blank" rel="noreferrer noopener">實務經驗分享講座</a>，提供給苦於 Codex 開發額度一下就被耗光的使用者，以及希望更有效規範 AI Agent 開發流程的開發者，分享如何透過一套可控、可驗證的協作方式，大幅降低 AI Agent 開發成本，並提升整體開發品質的實務經驗。</p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">P.S. 首頁圖檔是我的小狗堡貝與玄鳳粉圓 😄</p>



<figure class="wp-block-video"><video controls src="https://images.kenming.idv.tw/software/puppymart-showcase-demo.mp4"></video></figure>
<p>〈<a rel="nofollow" href="https://kenming.idv.tw/chatgpt-codex-collaboration-puppymart/">關於 Codex 與 ChatGPT 協作模式的實踐</a>〉這篇文章最早發佈於《<a rel="nofollow" href="https://kenming.idv.tw">Kenmingの鮮思維</a>》。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://kenming.idv.tw/chatgpt-codex-collaboration-puppymart/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		<enclosure url="https://images.kenming.idv.tw/software/puppymart-showcase-demo.mp4" length="20284326" type="video/mp4" />

		<media:content url="https://images.kenming.idv.tw/software/puppymart-home-page.webp" medium="image"></media:content>
	</item>
		<item>
		<title>從 AI 協作思考到務實簡化的 DDD 架構設計</title>
		<link>https://kenming.idv.tw/ai-collaboration-pragmatic-simplified-ddd-design/</link>
					<comments>https://kenming.idv.tw/ai-collaboration-pragmatic-simplified-ddd-design/#respond</comments>
		
		<dc:creator><![CDATA[Wang Kenming]]></dc:creator>
		<pubDate>Wed, 30 Sep 2026 12:16:24 +0000</pubDate>
				<category><![CDATA[軟體架構-含微服務架構]]></category>
		<category><![CDATA[AI應用與開發]]></category>
		<category><![CDATA[architecture]]></category>
		<category><![CDATA[DDD]]></category>
		<category><![CDATA[AICollaboration]]></category>
		<category><![CDATA[ApplicationLayer]]></category>
		<category><![CDATA[SimplifiedDDD]]></category>
		<guid isPermaLink="false">https://kenming.idv.tw/?p=24162</guid>

					<description><![CDATA[<p>這兩日在開發某個系統專案時，和 AI 持續討論軟體架構與設計決策，其中有一段關於 DDD Applicatio [&#8230;]</p>
<p>〈<a rel="nofollow" href="https://kenming.idv.tw/ai-collaboration-pragmatic-simplified-ddd-design/">從 AI 協作思考到務實簡化的 DDD 架構設計</a>〉這篇文章最早發佈於《<a rel="nofollow" href="https://kenming.idv.tw">Kenmingの鮮思維</a>》。</p>
]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large is-resized img-border"><img decoding="async" src="https://images.kenming.idv.tw/software/ai-collaboration-pragmatic-simplified-ddd-design.webp" alt="從 AI 協作思考到務實簡化的 DDD 架構設計" style="width:620px"/></figure>



<p class="wp-block-paragraph">這兩日在開發某個系統專案時，和 AI 持續討論軟體架構與設計決策，其中有一段關於 DDD Application Layer 的討論，我覺得特別有意思。</p>



<p class="wp-block-paragraph">這也讓我更明確感受到：<strong>真正有價值的 AI 協作，不只是取得答案，而是在反覆討論、質疑、修正與形成共識的過程中，讓彼此的思考更清楚，甚至成為一種共同成長的方式。</strong></p>



<p class="wp-block-paragraph">我們先討論 Application Service 的「顆粒度（Granularity）」。</p>



<p class="wp-block-paragraph">過去我的習慣比較偏向以 <strong>Use Case</strong>，也就是「<strong>使用者操作目的</strong>」來界定 Service 的顆粒度。</p>



<p class="wp-block-paragraph">但到了 AI 時代，我開始重新思考這個界線。</p>



<p class="wp-block-paragraph">原因是 AI Agent 能夠一次理解、處理與修改的程式範圍，比過去人工開發時更廣。原本為了讓開發者容易掌握，而切得較細的 Service Boundary，未必還需要維持同樣的顆粒度。</p>



<p class="wp-block-paragraph">因此我開始傾向把 Application Service 的範圍放大，改以「業務功能模組」作為一個 Service，例如：</p>



<p class="wp-block-paragraph"><code>OrderService</code>、<code>PaymentService</code>、<code>InventoryService</code>。</p>



<p class="wp-block-paragraph">AI 接受這個方向，但建議把「功能模組」改成更精確的 <strong>Business Capability（業務能力）</strong>。</p>



<p class="wp-block-paragraph">這個建議我很認同。</p>



<p class="wp-block-paragraph">因為「功能模組」其實很容易受到 UI、系統切分方式或開發組織影響；Business Capability 則更接近「這個系統究竟具備什麼業務能力」，邊界會清楚很多。</p>



<p class="wp-block-paragraph">於是我們形成了一個共識：</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Application Service 可以用 Business Capability 作為主要邊界，而 Use Case 仍保留作為使用者操作目的與應用流程的設計單位。</p>
</blockquote>



<p class="wp-block-paragraph">接著又討論到另一個很典型的 DDD 問題：跨模組流程到底要怎麼處理？</p>



<p class="wp-block-paragraph">AI 一開始採取比較典型、嚴謹的分層思維，希望 Application Layer 主要負責<strong>流程協調（Orchestration）</strong>，商務規則則盡量交由 Domain 處理。</p>



<p class="wp-block-paragraph">我的看法則比較務實。</p>



<p class="wp-block-paragraph">如果是中小型的 單體應用架構（Monolithic）Application，沒有必要把內部模組設計成彼此高度隔離，甚至採 Microservices 那樣透過 API 才能合作。這樣雖然邊界漂亮，但設計、實作與交易控管的成本都會明顯提高。</p>



<p class="wp-block-paragraph">所以我的主張是：</p>



<p class="wp-block-paragraph"><strong>Application Service 可以負責控制邏輯，直接透過各 儲庫（Repository）存取所需資料，也可以先承擔簡單的 Business Logic。</strong></p>



<p class="wp-block-paragraph">例如某個跨業務能力的流程，需要同時處理訂單、付款與庫存資料時，所屬的 Application Service 可以直接協調對應的 Repository，不需要再透過多層 Service 彼此呼叫。</p>



<p class="wp-block-paragraph">AI 接受這個設計，但提出一個補充：</p>



<p class="wp-block-paragraph">按照較嚴謹的 DDD，Business Logic 最好還是放到 Domain Object。</p>



<p class="wp-block-paragraph">我的看法則是：<strong>不需要太早把設計職責切分得過細。</strong></p>



<p class="wp-block-paragraph">如果只是簡單的條件判斷，而且只在單一流程使用，先寫在 Service 裡完全可以接受。</p>



<p class="wp-block-paragraph">真正需要重構到 Domain 的時機，應該是：</p>



<p class="wp-block-paragraph">邏輯開始變複雜、有多個條件或 invariant；</p>



<p class="wp-block-paragraph">或同一段業務規則開始被多個 Service 共用；</p>



<p class="wp-block-paragraph">或這段邏輯本身已經形成一個明確的 Domain Concept。</p>



<p class="wp-block-paragraph">AI 最後也接受這個方向，而且反過來幫我補上了一套「什麼時候應該抽離 Domain Logic」的判斷規範。</p>



<p class="wp-block-paragraph">我覺得這種務實、簡化的 DDD 架構，特別適合中小型系統。</p>



<p class="wp-block-paragraph">它不像完整 DDD 那樣一開始就承擔較高的設計成本，但仍然保留清楚的分層、業務邊界與重構空間。</p>



<p class="wp-block-paragraph">也就是說，可以先兼顧開發效率；當系統複雜度提高時，又有足夠的結構可以逐步抽離 Domain Logic、調整 Service Boundary，而不必整套推翻重來。</p>



<p class="wp-block-paragraph">這整段討論，對我來說才是目前與 AI 協作最有價值的地方。</p>



<p class="wp-block-paragraph">不是：</p>



<p class="wp-block-paragraph">「AI，直接幫我把這個問題解決掉。」</p>



<p class="wp-block-paragraph">而是：</p>



<p class="wp-block-paragraph"><strong>我提出多年開發經驗形成的直覺，AI 用既有的架構原則挑戰它；我再根據實際系統規模提出取捨，最後雙方把模糊的經驗整理成更明確、可以落地的設計準則。</strong></p>



<p class="wp-block-paragraph">這種互動不只是提高開發效率。</p>



<p class="wp-block-paragraph">它其實也在迫使自己重新回答：</p>



<p class="wp-block-paragraph">「我以前一直這樣設計，但為什麼？」</p>



<p class="wp-block-paragraph">「這是原則，還是只是習慣？」</p>



<p class="wp-block-paragraph">「什麼情況下應該遵守？什麼情況下反而應該簡化？」</p>



<p class="wp-block-paragraph">我覺得這才是 AI 時代對資深開發者很有意思的一種學習方式。</p>



<p class="wp-block-paragraph">不是把思考交給 AI，而是利用 AI，把自己的思考磨得更清楚。</p>



<p class="wp-block-paragraph">我也把這次與 AI 討論後形成的 Application Layer Design 共識整理成一份文件作為範本：</p>



<p class="wp-block-paragraph">🔗 https://reurl.cc/mzpZb9</p>



<p class="wp-block-paragraph">這份文件不只是討論紀錄，而是把設計取捨整理成可以持續引用、檢視與調整的開發規範。</p>



<p class="wp-block-paragraph">對正在思考如何導入分層架構、Application Layer、Repository、Domain Logic Placement，或想採用較務實 Simplified DDD 的開發者來說，應該具有相當的參考價值。</p>
<p>〈<a rel="nofollow" href="https://kenming.idv.tw/ai-collaboration-pragmatic-simplified-ddd-design/">從 AI 協作思考到務實簡化的 DDD 架構設計</a>〉這篇文章最早發佈於《<a rel="nofollow" href="https://kenming.idv.tw">Kenmingの鮮思維</a>》。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://kenming.idv.tw/ai-collaboration-pragmatic-simplified-ddd-design/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Uncle Bob：我現在的策略，是不閱讀 AI Agent 寫出的任何程式碼</title>
		<link>https://kenming.idv.tw/uncle-bob-ai-agent-development-strategy/</link>
					<comments>https://kenming.idv.tw/uncle-bob-ai-agent-development-strategy/#respond</comments>
		
		<dc:creator><![CDATA[Wang Kenming]]></dc:creator>
		<pubDate>Tue, 25 Aug 2026 06:14:59 +0000</pubDate>
				<category><![CDATA[AI應用與開發]]></category>
		<category><![CDATA[軟體實作與編程技術]]></category>
		<category><![CDATA[AI 輔助開發]]></category>
		<category><![CDATA[Clean Architecture]]></category>
		<category><![CDATA[Clean Code]]></category>
		<category><![CDATA[Uncle Bob]]></category>
		<guid isPermaLink="false">https://kenming.idv.tw/uncle-bob-ai-agent-development-strategy/</guid>

					<description><![CDATA[<p>73 歲、從機器碼與 Assembler 時代一路寫過來的 Uncle Bob，現在的 AI Agent 開發 [&#8230;]</p>
<p>〈<a rel="nofollow" href="https://kenming.idv.tw/uncle-bob-ai-agent-development-strategy/">Uncle Bob：我現在的策略，是不閱讀 AI Agent 寫出的任何程式碼</a>〉這篇文章最早發佈於《<a rel="nofollow" href="https://kenming.idv.tw">Kenmingの鮮思維</a>》。</p>
]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image aligncenter size-large is-resized img-border"><img decoding="async" src="https://images.kenming.idv.tw/software/uncle-bob-ai-agent-development-strategy.webp" alt="封面圖：Uncle Bob：我現在的策略，是不閱讀 AI Agent 寫出的任何程式碼" style="width:620px"/></figure>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">73 歲、從機器碼與 Assembler 時代一路寫過來的 Uncle Bob，現在的 AI Agent 開發策略竟然是：</p>



<p class="wp-block-paragraph">“My current strategy is to not read any of the code written by my agents.”</p>



<p class="wp-block-paragraph">真正值得思考的，不是「還要不要自己寫 Code」，而是資深工程師多年累積的設計與工程經驗，如何轉換成 Agent 可以遵循與驗證的工作規範。</p>
</blockquote>



<p class="wp-block-paragraph">上個月底在 <a href="https://x.com/unclebobmartin/status/2080257779395154409?utm_source=chatgpt.com" target="_blank" data-type="link" data-id="https://x.com/unclebobmartin/status/2080257779395154409?utm_source=chatgpt.com" rel="noreferrer noopener">X</a> 上有這麼一篇討論：</p>



<p class="wp-block-paragraph">一位從 1983 年就開始寫程式的資深工程師 Ori Pomerantz 提到，他最近開始嘗試使用 Claude 協助開發，但心裡始終有個坎過不去：</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">如果最後這些程式碼要由自己負責，那麼自己是不是就應該理解每一段 Code？</p>
</blockquote>



<p class="wp-block-paragraph">所以即使使用 AI，他仍然不太放心讓 Agent 直接修改檔案。</p>



<p class="wp-block-paragraph">結果底下有一位軟體業界更 "老" 的前輩回了這麼一句：</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">“My current strategy is to not read any of the code written by my agents.”</p>
</blockquote>



<p class="wp-block-paragraph">意思就是：</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">「我目前的策略，是不閱讀 Agent 所寫的任何程式碼。」</p>
</blockquote>



<p class="wp-block-paragraph">完全沒想到，原來就是大大有名的 <strong>Bob 大叔，Uncle Bob</strong>，也就是 <strong>《Clean Code》</strong> 與 <strong>《Clean Architecture》</strong> 的作者。</p>



<p class="wp-block-paragraph">其中 《Clean Code》 我自己反覆讀過好幾遍，而且其中很多觀念都切實應用在我的開發實務裡。</p>



<p class="wp-block-paragraph">例如我自己會進一步落實成比較具體的開發規範：</p>



<ul class="wp-block-list">
<li>單一 Method 儘量控制在 20 行以內</li>



<li>Method 參數儘量不要超過 4 個</li>



<li>必然撰寫 單元測試（Unit Test）</li>



<li>命名必須具有完整而清楚的語意</li>
</ul>



<p class="wp-block-paragraph">有趣的是，進入 AI 輔助開發後，這些以前是要求「自己寫程式時要遵守」的原則，現在反而很適合直接轉換成 AI Agent 的開發規範。</p>



<p class="wp-block-paragraph">而 Bob 大叔真正想表達的，並非一般人所認為的那種「<strong>Vibe Coding</strong>」 —— 只求結果，而不太理會 Code 如何組織。</p>



<p class="wp-block-paragraph">剛好相反。</p>



<p class="wp-block-paragraph">他的做法是<strong>把開發者的角色，從親自撰寫、逐行閱讀實作（Implementation），往更高層次的 「約束（Constraint）」 與 「驗證（Verification）」 提昇</strong>，也更能體現軟體開發設計者（Designer）的角色。</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><br>（這點我深有同感，所以我把不親自寫程式，而專注於設計與組織軟體的開發人員，稱為「Vibe Coder」。）</p>
</blockquote>



<p class="wp-block-paragraph">例如：</p>



<ul class="wp-block-list">
<li>單元測試（Unit Tests）</li>



<li>驗收測試／Gherkin 規格（Acceptance Tests / Gherkin）</li>



<li>品質保證流程（QA Procedures）</li>



<li>測試覆蓋率（Test Coverage）</li>



<li>品質指標（Quality Metrics）</li>



<li>突變測試（Mutation Testing）</li>
</ul>



<p class="wp-block-paragraph">AI Agent 可以負責大量實作，但最後一定得通過這整套驗證機制。</p>



<p class="wp-block-paragraph">換句話說，不再是靠：</p>



<p class="wp-block-paragraph"><strong>「這份 Code 我每一行都看過，所以我相信它。」</strong></p>



<p class="wp-block-paragraph">而是改成：</p>



<p class="wp-block-paragraph"><strong>「這份實作已經通過我所定義的規格、測試與品質關卡，所以我可以相信它。」</strong></p>



<p class="wp-block-paragraph">我覺得這個轉變其實很值得資深軟體人員思考。</p>



<p class="wp-block-paragraph"><strong>Bob 大叔今年已經 73 歲了。</strong></p>



<p class="wp-block-paragraph">他老人家從 1960 年代就開始寫程式，早期甚至是從機器碼、PDP-8 Assembler，一路經歷 FORTRAN、COBOL、C、C++ 這些不同世代的語言走過來。</p>



<p class="wp-block-paragraph">這種真正從「上古時代」一路親手寫 Code 寫到今天的老人家，都已經開始思考如何從 Syntax 與 Implementation 抽離，把更多實作工作交給 AI Agent。</p>



<p class="wp-block-paragraph">那麼我輩中流，還有什麼理由拒絕呢？</p>



<p class="wp-block-paragraph">我自己這兩年也見識與聽聞過不少資深工程師，仍然非常相信自己那套「手板斧」功夫。</p>



<p class="wp-block-paragraph">凡事還是習慣親身下去「刻」程式碼。</p>



<p class="wp-block-paragraph">即使已經開始使用 AI，很多時候仍停留在：</p>



<p class="wp-block-paragraph">問問題 → 查資料 → 生成幾段 Code → 自己再接手寫</p>



<p class="wp-block-paragraph">這當然也算是 AI 輔助開發。</p>



<p class="wp-block-paragraph">但我現在越來越認為，真正值得學習的，並非是 「怎麼讓 AI 幫我多寫一點 Code」。</p>



<p class="wp-block-paragraph">而是：</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">如何把我們多年累積下來的軟體設計、架構（Architecture）、程式規約（Coding Convention）、測試（Testing）與團隊協作（Team Collaboration），轉換成 AI Agent 可以理解、遵循，而且可以被驗證的工作規範。</p>
</blockquote>
<p>〈<a rel="nofollow" href="https://kenming.idv.tw/uncle-bob-ai-agent-development-strategy/">Uncle Bob：我現在的策略，是不閱讀 AI Agent 寫出的任何程式碼</a>〉這篇文章最早發佈於《<a rel="nofollow" href="https://kenming.idv.tw">Kenmingの鮮思維</a>》。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://kenming.idv.tw/uncle-bob-ai-agent-development-strategy/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<media:content url="https://images.kenming.idv.tw/software/uncle-bob-ai-agent-development-strategy.webp" medium="image"></media:content>
	</item>
		<item>
		<title>AI Agent 如何協作 Playwright：從需求變更到可驗證的測試閉環</title>
		<link>https://kenming.idv.tw/ai-agent-playwright-testing-workflow/</link>
					<comments>https://kenming.idv.tw/ai-agent-playwright-testing-workflow/#respond</comments>
		
		<dc:creator><![CDATA[Wang Kenming]]></dc:creator>
		<pubDate>Thu, 20 Aug 2026 08:08:50 +0000</pubDate>
				<category><![CDATA[AI應用與開發]]></category>
		<category><![CDATA[軟體實作與編程技術]]></category>
		<category><![CDATA[軟體開發工具]]></category>
		<category><![CDATA[Playwright]]></category>
		<category><![CDATA[E2E Testing]]></category>
		<category><![CDATA[UI 自動化測試]]></category>
		<category><![CDATA[AI Agent]]></category>
		<category><![CDATA[軟體測試]]></category>
		<guid isPermaLink="false">https://kenming.idv.tw/?p=24111</guid>

					<description><![CDATA[<p>UI 自動化測試 - 使用 Playwright x AI Agent 系列 #04 當 Playwright [&#8230;]</p>
<p>〈<a rel="nofollow" href="https://kenming.idv.tw/ai-agent-playwright-testing-workflow/">AI Agent 如何協作 Playwright：從需求變更到可驗證的測試閉環</a>〉這篇文章最早發佈於《<a rel="nofollow" href="https://kenming.idv.tw">Kenmingの鮮思維</a>》。</p>
]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image aligncenter size-large is-resized img-border"><img decoding="async" src="https://images.kenming.idv.tw/software/ui-automation-test/ai-agent-playwright-workflow-cover.webp" alt="封面圖：AI Agent 與 Playwright Test 協作" style="width:620px"/></figure>



<div style="height:24px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="has-text-color has-link-color wp-elements-aadcf25f7fbc70172a3453caa1c80a8e wp-block-paragraph" style="color:#4f6b8a;font-size:16px"><strong><strong>UI 自動化測試 - 使用 Playwright x AI Agent</strong></strong> <strong>系列 #04</strong></p>



<div style="height:16px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">當 Playwright 測試已能把固定的使用者操作流程整理成可重複執行的腳本後，下一個問題通常不是「還能再寫哪些 API」，而是：</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">當需求持續變更時，如何讓 AI Agent 協助擴展、修正與審查測試，同時避免測試品質隨著自動生成而下降？</p>
</blockquote>



<p class="wp-block-paragraph">AI Agent 確實可以快速產生 Playwright 測試，但若只給一句「幫我測試折扣功能」，Agent 仍必須自行猜測帳號、測試資料、操作流程、預期結果與失敗判準。最後很可能得到一支「可以跑」，卻無法確認是否真正驗證需求的測試。</p>



<p class="wp-block-paragraph">較可靠的方式，是把 Agent 放進一個有明確輸入與可驗證輸出的測試閉環：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">需求變更
→ 功能交付
→ Agent 補上或調整 Playwright 測試
→ 審查測試意圖、Locator 與 Assertion
→ 執行測試
→ 檢視錯誤訊息與 Evidence
→ 回饋 Agent 修正
→ 再次執行確認結果</code></pre>



<p class="wp-block-paragraph">這個流程的核心，不是讓 Agent 一次產生完整測試平台，而是讓它在<strong>規格、測試資料、測試結果與失敗證據（Evidence）</strong>的約束下工作。</p>



<figure class="wp-block-image aligncenter size-large is-resized img-border"><img decoding="async" src="https://images.kenming.idv.tw/software/ui-automation-test/agent-pw-feedback-loop-qa.webp" alt="圖：AI Agent 與 Playwright 測試回饋閉環" style="width:620px"/></figure>



<p class="has-text-align-center has-small-font-size wp-block-paragraph">圖：AI Agent 與 Playwright 測試回饋閉環</p>



<h2 class="wp-block-heading">AI Agent 可以協助測試，但不能取代測試判斷</h2>



<p class="wp-block-paragraph">在 UI 自動化測試流程中，Agent 可以扮演多種角色：</p>



<ul class="wp-block-list">
<li>將需求規格整理成可執行的測試需求。</li>



<li>拆分主流程 Smoke Test 與功能分支測試。</li>



<li>產生 Playwright Test、測試資料與 helper function。</li>



<li>根據失敗訊息、Screenshot、Trace 或 Report 修正腳本。</li>



<li>審查 Locator、Assertion、測試資料與測試意圖。</li>
</ul>



<p class="wp-block-paragraph">問題在於，Agent 的「完成」不等於測試真的正確。</p>



<p class="wp-block-paragraph">例如測試失敗時，最容易出現的錯誤修正方式包括：</p>



<ul class="wp-block-list">
<li>刪除失敗的 expect</li>



<li>把精確金額驗證改成只檢查「NT$」</li>



<li>把指定商品改成任意商品</li>



<li>增加 waitForTimeout()</li>



<li>改用脆弱 CSS selector 讓 locator 暫時找得到</li>



<li>直接移除失敗的測試情境</li>
</ul>



<p class="wp-block-paragraph">這些做法可能讓測試由紅轉綠，但測試本身已經失去原本的驗證意圖。</p>



<p class="wp-block-paragraph">因此，Agent 需要有明確使用邊界：</p>



<ul class="wp-block-list">
<li>不得自行補完規格未定義的業務規則。</li>



<li>不得為了讓測試通過而弱化 Assertion。</li>



<li>不應只依程式碼推測 UI 狀態。</li>



<li>畫面與規格不一致時，先回報差異，而不是修改測試來迎合現況。</li>



<li>若缺少穩定 Locator，應提出可測試性補強，而不是直接改用脆弱 DOM 路徑。</li>
</ul>



<p class="wp-block-paragraph">Playwright 官方文件本身也強調以 Locator、auto-waiting、web-first assertions 與測試隔離建立可維護測試。Agent 協作沒有改變這些基本原則，只是讓它們更需要被明確寫進工作規範。</p>



<h2 class="wp-block-heading">不要只給 Agent 一句「幫我測試這個功能」</h2>



<p class="wp-block-paragraph">假設一個既有的購物流程原本只有一般會員結帳，現在加入：</p>



<ul class="wp-block-list">
<li>VIP 會員自動折扣</li>



<li>有效優惠碼折抵</li>



<li>無效優惠碼錯誤訊息</li>



<li>VIP 與優惠碼互斥規則</li>
</ul>



<p class="wp-block-paragraph">如果只要求：</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">請幫我測試新的折扣功能。</p>
</blockquote>



<p class="wp-block-paragraph">Agent 仍不知道：</p>



<ul class="wp-block-list">
<li>哪個帳號是一般會員、哪個是 VIP。</li>



<li>商品單價與數量是多少。</li>



<li>有效與無效優惠碼分別是什麼。</li>



<li>VIP 與優惠碼同時使用時應如何處理。</li>



<li>要驗證折扣名稱、折扣金額，還是只驗證按鈕可點擊。</li>



<li>原本的 Smoke Test 是否應修改。</li>



<li>可不可以直接修改產品功能。</li>
</ul>



<p class="wp-block-paragraph">一個較可執行的測試需求，至少應明確包含：</p>



<ul class="wp-block-list">
<li>測試目標</li>



<li>規格來源</li>



<li>已交付功能範圍</li>



<li>測試帳號與資料</li>



<li>需要新增或調整的測試案例</li>



<li>主要 Assertion</li>



<li>Locator 原則</li>



<li>可修改與不可修改範圍</li>



<li>驗證指令</li>



<li>回報格式</li>
</ul>



<p class="wp-block-paragraph">以折扣案例來說，測試資料可以先整理為：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">一般會員：normalUser
VIP 會員：vipUser
商品單價：680
數量：2
有效優惠碼：PET100
無效優惠碼：INVALID</code></pre>



<p class="wp-block-paragraph">規則則明確寫成：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">VIP：商品小計 9 折
PET100：一般會員折抵 100 元
INVALID：不折扣並顯示錯誤訊息
VIP + PET100：維持 VIP 折扣，並提示不可同時使用</code></pre>



<p class="wp-block-paragraph">這樣 Agent 才是在「依規格產生測試」，而不是「看程式碼猜一套測試」。</p>



<h2 class="wp-block-heading">需求增加，不代表把所有情境塞進同一支 E2E 測試</h2>



<p class="wp-block-paragraph">原本的購物主流程可以是一條 Smoke Test：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">登入
→ 選擇商品
→ 加入購物車
→ 進入結帳
→ 確認金額
→ 送出訂單
→ 驗證訂單成立</code></pre>



<p class="wp-block-paragraph">加入折扣需求後，最直接但不理想的做法，是繼續往這支測試後面追加 VIP、優惠碼、無效優惠碼與互斥規則。</p>



<p class="wp-block-paragraph">結果通常會變成：</p>



<ul class="wp-block-list">
<li>測試越來越長。</li>



<li>多組帳號與資料互相干擾。</li>



<li>失敗時難以定位是哪個規則出問題。</li>



<li>Agent 修正其中一段時，容易影響其他段。</li>



<li>測試名稱已無法反映真正的驗證目的。</li>
</ul>



<p class="wp-block-paragraph">較合理的做法，是保留原本的主流程 Smoke Test，再新增獨立的功能分支：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">基準 Smoke Test
→ 一般會員可以完成基本購物與結帳
折扣分支
→ VIP 會員結帳時自動套用 VIP 折扣
→ 一般會員使用有效優惠碼後折抵 100 元
→ 一般會員使用無效優惠碼後顯示錯誤訊息且金額不變
→ VIP 會員輸入優惠碼時顯示互斥提示並維持 VIP 折扣</code></pre>



<figure class="wp-block-image aligncenter size-large is-resized img-border"><img decoding="async" src="https://images.kenming.idv.tw/software/ui-automation-test/discount-v2-test-branches.webp" alt="圖：需求變更後需拆解測試結構" style="aspect-ratio:1.7661459028797792;width:622px;height:auto"/></figure>



<p class="has-text-align-center has-small-font-size wp-block-paragraph">圖：需求變更後需拆解測試結構</p>



<p class="wp-block-paragraph">程式結構也可以直接反映這個分工：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">tests/
├─ smoke/
│  └─ checkout.spec.ts
└─ discount/
   ├─ vip_discount.spec.ts
   ├─ coupon_discount.spec.ts
   ├─ invalid_coupon.spec.ts
   └─ discount_exclusion.spec.ts</code></pre>



<p class="wp-block-paragraph">重點不是一定要拆成四個檔案，而是每個 Test Case 應有<strong>單一且可辨識的驗證目的</strong>。</p>



<h2 class="wp-block-heading">測試資料與預期值不要散落在腳本裡</h2>



<p class="wp-block-paragraph">需求簡單時，直接寫：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">await expect(page.getByTestId(&#039;checkout-total&#039;))
  .toHaveText(&#039;NT$ 1224&#039;);</code></pre>



<p class="wp-block-paragraph">並沒有立即問題。</p>



<p class="wp-block-paragraph">但當測試開始包含不同會員身分、優惠碼與互斥規則，如果多支測試都散落：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">NT$ 1360
NT$ 136
NT$ 1224
NT$ 1260</code></pre>



<p class="wp-block-paragraph">Agent 後續修改時，很容易只修到其中幾個數字。</p>



<p class="wp-block-paragraph">較好的方式，是將測試資料與簡單預期值計算集中管理：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">export const users = {
  normal: {
    username: &#039;user1&#039;,
    password: &#039;user1&#039;,
  },
  vip: {
    username: &#039;user2&#039;,
    password: &#039;user2&#039;,
  },
} as const;
export const product = {
  unitPrice: 680,
  quantity: 2,
} as const;
export const coupons = {
  valid: &#039;PET100&#039;,
  invalid: &#039;INVALID&#039;,
} as const;</code></pre>



<p class="wp-block-paragraph">再用一個保持簡單的 helper 推導預期結果：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">type MemberType = &#039;normal&#039; | &#039;vip&#039;;
export function calculateExpectedAmount(
  unitPrice: number,
  quantity: number,
  memberType: MemberType,
  couponCode?: string,
) {
  const subtotal = unitPrice * quantity;
  const vipDiscount =
    memberType === &#039;vip&#039;
      ? Math.round(subtotal * 0.1)
      : 0;
  const couponDiscount =
    memberType === &#039;normal&#039; &amp;&amp; couponCode === &#039;PET100&#039;
      ? 100
      : 0;
  return {
    subtotal,
    vipDiscount,
    couponDiscount,
    total: subtotal - vipDiscount - couponDiscount,
  };
}</code></pre>



<p class="wp-block-paragraph">這裡有一個重要限制：<strong>測試 helper 不應完整複製產品端的商業邏輯。</strong></p>



<p class="wp-block-paragraph">如果產品與測試使用完全相同的折扣實作，產品邏輯寫錯時，兩邊可能一起算出相同的錯誤結果。helper 的用途只是讓測試預期值有單一、可讀、可核對的來源，而不是再做一套產品規則引擎。</p>



<h2 class="wp-block-heading">Agent 產生測試後，還需要審查 Locator 與 Assertion</h2>



<p class="wp-block-paragraph">Agent 很擅長快速產生「能定位到元素」的 selector，但能找到元素，不代表是好的 Locator。</p>



<p class="wp-block-paragraph">Playwright 官方建議優先使用具使用者語意、可重試且較穩定的定位方式。實務上可依下列方向檢查：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">getByRole
→ getByLabel
→ getByText
→ getByTestId
→ 穩定屬性 selector
→ 結構型 CSS / XPath 作為最後手段</code></pre>



<p class="wp-block-paragraph">例如：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">await page.getByRole(&#039;button&#039;, { name: &#039;套用優惠碼&#039; }).click();
await expect(
  page.getByTestId(&#039;coupon-discount-amount&#039;)
).toHaveText(&#039;NT$ 100&#039;);</code></pre>



<p class="wp-block-paragraph">不建議 Agent 為了快速通過，改成：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">await page.locator(
  &#039;#app &gt; main &gt; div:nth-child(3) &gt; button&#039;
).click();</code></pre>



<p class="wp-block-paragraph">同樣地，Assertion 也不能只驗證「操作有執行」。</p>



<p class="wp-block-paragraph">折扣測試至少應視情境驗證：</p>



<ul class="wp-block-list">
<li>商品小計。</li>



<li>折扣名稱。</li>



<li>折扣金額。</li>



<li>訂單總金額。</li>



<li>成功或錯誤訊息。</li>



<li>VIP / 優惠碼互斥提示。</li>



<li>最終訂單成立頁的金額。</li>
</ul>



<p class="wp-block-paragraph">例如有效優惠碼案例，不應只寫：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">await expect(page.getByText(&#039;PET100&#039;)).toBeVisible();</code></pre>



<p class="wp-block-paragraph">更有意義的是：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">await expect(page.getByTestId(&#039;coupon-name&#039;))
  .toHaveText(&#039;PET100&#039;);
await expect(page.getByTestId(&#039;coupon-discount-amount&#039;))
  .toHaveText(&#039;NT$ 100&#039;);
await expect(page.getByTestId(&#039;checkout-total&#039;))
  .toHaveText(&#039;NT$ 1260&#039;);</code></pre>



<p class="wp-block-paragraph">這樣測試才是在驗證「折扣規則」，而不是只驗證畫面上曾經出現某段文字。</p>



<h2 class="wp-block-heading">測試失敗後，不要立刻叫 Agent 改程式碼</h2>



<p class="wp-block-paragraph">當 Playwright Test 失敗時，真正有價值的是失敗 Evidence。</p>



<p class="wp-block-paragraph">常見 Evidence 包括：</p>



<ul class="wp-block-list">
<li>終端機錯誤訊息</li>



<li>Screenshot</li>



<li>Trace</li>



<li>Video</li>



<li>HTML Report</li>
</ul>



<p class="wp-block-paragraph">比較可靠的流程是：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">測試失敗
→ 讀取失敗測試與行號
→ 判斷是 action、locator 還是 assertion
→ 檢查 Screenshot / Trace / Report
→ 比對預期與實際 UI
→ 分類問題來源
→ 再決定修正測試或回報產品缺陷</code></pre>



<figure class="wp-block-image aligncenter size-large is-resized img-border"><img decoding="async" src="https://images.kenming.idv.tw/software/ui-automation-test/pw-failure-evidence-to-agent.webp" alt="圖：需求變更後需拆解測試結構" style="aspect-ratio:1.7661459028797792;width:622px;height:auto"/></figure>



<p class="has-text-align-center has-small-font-size wp-block-paragraph">圖：測試失敗證據轉成 Agent 失敗摘要</p>



<p class="wp-block-paragraph">例如：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">Expected: NT$ 1260
Received: NT$ 1360</code></pre>



<p class="wp-block-paragraph">不能立即推論「產品折扣功能壞了」。</p>



<p class="wp-block-paragraph">仍需確認：</p>



<ul class="wp-block-list">
<li>PET100 是否真的已成功套用。</li>



<li>畫面是否顯示優惠碼成功訊息。</li>



<li>測試是否取得正確的總金額欄位。</li>



<li>測試資料是否殘留前一次狀態。</li>



<li>規格中的預期值是否被測試 helper 算錯。</li>
</ul>



<p class="wp-block-paragraph">Agent 比較適合接收的，不是「測試失敗，請修」，而是已整理的診斷輸入：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">失敗測試：
一般會員輸入 PET100 後應折抵 100 元
失敗步驟：
checkout-total Assertion
預期：
NT$ 1260
實際：
NT$ 1360
Evidence：
- 畫面顯示優惠碼 PET100 已套用
- coupon-discount-amount 顯示 NT$ 0
- Trace 顯示套用按鈕已成功點擊
請判斷：
1. 較可能是產品功能、測試腳本或測試資料問題。
2. 是否應修改測試。
3. 若需修改測試，不得降低原有 Assertion。</code></pre>



<p class="wp-block-paragraph">這樣 Agent 才是在做「根據 Evidence 的診斷」，而不是猜測。</p>



<h2 class="wp-block-heading">讓 Agent 修正測試時，明確禁止「弱化驗證」</h2>



<p class="wp-block-paragraph">當 Agent 收到失敗測試後，可以直接加入一些負面限制：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">不得：
- 刪除折扣相關 Assertion
- 將精確金額改成模糊文字比對
- 移除 VIP、有效優惠碼、無效優惠碼或互斥測試
- 使用 waitForTimeout() 掩蓋同步問題
- 使用脆弱 CSS selector / XPath 取代穩定 Locator
- 修改產品功能來配合測試</code></pre>



<p class="wp-block-paragraph">並要求修正完成後重新執行：</p>


<pre class="language-powershell no-line-numbers"><code class="language-powershell no-line-numbers">npx playwright test tests/discount \
  --project=chromium \
  --workers=1</code></pre>



<p class="wp-block-paragraph">最後回報：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">修改了哪些測試
實際執行的指令
哪些測試通過
哪些仍失敗
是否有新的失敗
使用了哪些 Evidence
是否需要回報產品功能差異</code></pre>



<p class="wp-block-paragraph">Agent 回覆「已修正」沒有驗證價值；重新執行後的測試結果才有。</p>



<h2 class="wp-block-heading">長提示詞不是終點：把固定流程移到 AGENTS.md 與專案規範</h2>



<p class="wp-block-paragraph">如果每次要求 Agent 產生或修正 Playwright 測試，都要重新貼上：</p>



<ul class="wp-block-list">
<li>Locator 原則</li>



<li>Assertion 原則</li>



<li>測試資料規則</li>



<li>禁止修改範圍</li>



<li>失敗 Evidence 格式</li>



<li>重新驗證方式</li>



<li>回報格式</li>
</ul>



<p class="wp-block-paragraph">代表這些內容已經不是「這次任務的提示詞」，而是<strong>專案固定工作規範</strong>。</p>



<p class="wp-block-paragraph">此時可以將規範拆出：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">project/
├─ AGENTS.md
└─ .agent-rules/
   ├─ project-structure.md
   ├─ tech-stack.md
   └─ playwright-qa-workflow.md</code></pre>



<p class="wp-block-paragraph">其中 <code>playwright-qa-workflow.md</code> 可以固定記錄：</p>



<ul class="wp-block-list">
<li>測試任務必讀哪些規格</li>



<li>Agent 可修改與不可修改的目錄</li>



<li>Smoke Test 與分支測試拆分原則</li>



<li>Locator 優先順序</li>



<li>Assertion 最低要求</li>



<li>測試資料與 helper 管理方式</li>



<li>測試執行指令</li>



<li>Evidence 回報格式</li>



<li>禁止弱化驗證的規則</li>
</ul>



<p class="wp-block-paragraph"><code>AGENTS.md</code> 則只需要成為入口索引：</p>


<pre class="language-markdown no-line-numbers"><code class="language-markdown no-line-numbers">## 分離規範
- 專案目錄結構：`.agent-rules/project-structure.md`
- 技術堆疊與實作邊界：`.agent-rules/tech-stack.md`
- Playwright QA 工作流程：`.agent-rules/playwright-qa-workflow.md`
涉及 Playwright 測試生成、修正、審查、
執行或失敗分析時，必須讀取
`.agent-rules/playwright-qa-workflow.md`。</code></pre>



<p class="wp-block-paragraph">之後單次提示詞便可以縮短成：</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">請依 AGENTS.md 與 Playwright QA 規範，
根據指定規格補上折扣測試。

先提出測試設計，不要直接修改檔案。</p>
</blockquote>



<p class="wp-block-paragraph">固定規則由專案文件承載，提示詞只描述這次任務的差異。</p>



<figure class="wp-block-image aligncenter size-large is-resized img-border"><img decoding="async" src="https://images.kenming.idv.tw/software/ui-automation-test/discount-v2-agent-workflow.webp" alt="圖：從提示詞操作到專案規範" style="aspect-ratio:1.7661459028797792;width:622px;height:auto"/></figure>



<p class="has-text-align-center has-small-font-size wp-block-paragraph">圖：從提示詞操作到專案規範</p>



<h2 class="wp-block-heading">從「AI 幫忙寫測試」走向「可驗證的 Agent 測試流程」</h2>



<p class="wp-block-paragraph">AI Agent 最容易展現價值的地方，是加速重複且結構化的工作，包括測試案例擴展、腳本生成、Locator 整理、Assertion 審查與失敗摘要。</p>



<p class="wp-block-paragraph">但要讓這些能力真正進入開發流程，需要先建立幾個基本約束：</p>



<ul class="wp-block-list">
<li>規格先於生成</li>



<li>測試資料先於猜測</li>



<li>Evidence 先於修正</li>



<li>驗證結果先於「已完成」</li>



<li>專案規範先於超長提示詞</li>
</ul>



<p class="wp-block-paragraph">Playwright 在這裡不只是瀏覽器自動化工具。</p>



<p class="wp-block-paragraph">當測試案例具備明確意圖、穩定 Locator、具體 Assertion、可觀察 Evidence 與可重複執行的驗證流程後，它就能成為 Agent 任務的可執行驗收條件。</p>



<p class="wp-block-paragraph">而 AI Agent 的價值，也不再只是「幫忙產生測試程式碼」，而是參與：</p>


<pre class="language-test no-line-numbers"><code class="language-test no-line-numbers">需求理解
→ 測試設計
→ 測試實作
→ 執行驗證
→ Evidence 診斷
→ 修正
→ 再驗證</code></pre>



<p class="wp-block-paragraph">這才是 AI Agent 與 Playwright 協作真正值得標準化的部分！</p>
<p>〈<a rel="nofollow" href="https://kenming.idv.tw/ai-agent-playwright-testing-workflow/">AI Agent 如何協作 Playwright：從需求變更到可驗證的測試閉環</a>〉這篇文章最早發佈於《<a rel="nofollow" href="https://kenming.idv.tw">Kenmingの鮮思維</a>》。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://kenming.idv.tw/ai-agent-playwright-testing-workflow/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<media:content url="https://images.kenming.idv.tw/software/ui-automation-test/ai-agent-playwright-workflow-cover.webp" medium="image"></media:content>
	</item>
		<item>
		<title>為 Bricks Builder 打造一個開發輔助 Skill</title>
		<link>https://kenming.idv.tw/bricks-builder-development-skill/</link>
					<comments>https://kenming.idv.tw/bricks-builder-development-skill/#respond</comments>
		
		<dc:creator><![CDATA[Wang Kenming]]></dc:creator>
		<pubDate>Tue, 11 Aug 2026 00:35:59 +0000</pubDate>
				<category><![CDATA[應用軟體使用分享]]></category>
		<category><![CDATA[AI應用與開發]]></category>
		<category><![CDATA[軟體實作與編程技術]]></category>
		<category><![CDATA[wordpress]]></category>
		<category><![CDATA[AI Agent]]></category>
		<category><![CDATA[Bricks Builder]]></category>
		<category><![CDATA[Agent Skill]]></category>
		<guid isPermaLink="false">https://kenming.idv.tw/?p=24099</guid>

					<description><![CDATA[<p>去年重新整理自己的 WordPress Blog 時，我最後選擇使用 Bricks Builder 作為網站主 [&#8230;]</p>
<p>〈<a rel="nofollow" href="https://kenming.idv.tw/bricks-builder-development-skill/">為 Bricks Builder 打造一個開發輔助 Skill</a>〉這篇文章最早發佈於《<a rel="nofollow" href="https://kenming.idv.tw">Kenmingの鮮思維</a>》。</p>
]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image aligncenter size-large is-resized img-border"><img decoding="async" src="https://images.kenming.idv.tw/software/bricks-builder/bricks-builder-skill-cover.webp" alt="封面圖：發布 Bricks Builder 開發輔助 Skill" style="width:620px"/></figure>



<p class="wp-block-paragraph">去年重新整理自己的 WordPress Blog 時，我最後選擇使用 Bricks Builder 作為網站主要的 Theme 與頁面建置工具。</p>



<p class="wp-block-paragraph">當時選擇 Bricks Builder，一個很重要的原因是它不只是一套視覺化 Page Builder，本身也保留了相當大的開發彈性。對熟悉 CSS、PHP 與 WordPress 開發的人來說，可以在視覺化建置與客製程式之間取得不錯的平衡。</p>



<p class="wp-block-paragraph">關於當時為什麼選擇 Bricks Builder，我曾另外整理過一篇文章：</p>



<p class="wp-block-paragraph"><a href="https://kenming.idv.tw/2025_site_rebuild_using_bricksbuilder/">2025 年網站重新建置：使用 Bricks Builder</a></p>



<h3 class="wp-block-heading">最初只是想讓 AI 比較容易查 Bricks 文件</h3>



<p class="wp-block-paragraph">隨著使用 Bricks Builder 的程度越來越深入，我開始頻繁查閱 Bricks Academy（官方文檔）。</p>



<p class="wp-block-paragraph">Bricks 官方其實提供了不少文件，包括 Builder 功能、Dynamic Data、Query Loop、Components、Hooks、Filters、Custom Elements 與各種 Developer API。不過實際使用下來，我一直覺得官方文件的分類與查詢並不是那麼方便，尤其當要找的是某個比較細節的開發資訊時，常常得花一些時間才能找到真正相關的文件。</p>



<p class="wp-block-paragraph">如果改成讓 AI Agent 自己到 Bricks Academy 搜尋，同樣會遇到類似問題：搜尋、開啟不同頁面、判斷哪些文件真正相關，不但效率不高，也會消耗不少 Context Token。</p>



<p class="wp-block-paragraph">所以我一開始做這個 Skill 的目的其實很單純：</p>



<p class="wp-block-paragraph"><strong>把 Bricks Academy 官方文件預先下載並轉換成 Markdown，建立一套可以讓 AI Agent 在本機直接搜尋的文件庫。</strong></p>



<p class="wp-block-paragraph">目前這套文件庫已同步超過 700 篇 Bricks Academy 文件以及相關圖片，Agent 可以先從本機 corpus 搜尋真正需要的內容，而不需要每次都重新到官方網站尋找。</p>



<p class="wp-block-paragraph">同時我也做了文件同步機制，可以檢查 Bricks Academy 是否有新的或異動的文件，需要時再重新下載、轉換與建立索引。因此它並不是建立一次之後就不再更新的靜態知識庫，而是可以持續與官方文件保持同步。</p>



<h3 class="wp-block-heading">從文件查詢逐漸擴展成開發輔助 Skill</h3>



<p class="wp-block-paragraph">實際使用一段時間後，我發現單純「找得到文件」仍然不完全足以協助 Bricks Builder 開發。</p>



<p class="wp-block-paragraph">例如處理 Bricks element tree、page JSON、responsive settings、global classes、Dynamic Data、Query Loop 或 Custom Elements 時，除了官方文件之外，Agent 還需要知道一些基本的資料模型、修改原則與驗證流程。</p>



<p class="wp-block-paragraph">因此後來又逐步加入了一些精簡的 development references，讓 Skill 除了負責文件查詢之外，也可以提供 Bricks Builder 開發時需要的工作流程與約束。</p>



<p class="wp-block-paragraph">目前這個 Skill 大致涵蓋：</p>



<ul class="wp-block-list">
<li>本機搜尋 Bricks Academy 官方文件。</li>



<li>Bricks element tree 與資料結構的基本模型。</li>



<li>Page / Element JSON 的建立與修改流程。</li>



<li>Custom Elements 開發。</li>



<li>Responsive settings、Theme Styles、Global Classes、Variables 與 Components。</li>



<li>Dynamic Data、Hooks、Query Loop 與 Forms。</li>



<li>修改後在 Builder 與 Frontend 的驗證流程。</li>
</ul>



<p class="wp-block-paragraph">另外有些 Bricks 的實作細節會隨版本改變，例如 control keys、hook signatures、JSON shapes 或內部資料結構。因此這個 Skill 也不會單純依賴既有文件或模型記憶，在有實際 Bricks 開發環境可以查驗時，會以目前安裝版本作為最後的驗證依據。</p>



<p class="wp-block-paragraph">換句話說，我希望它扮演的角色不是一套「什麼都知道的 Bricks 知識庫」，而是一個知道<strong>去哪裡找資料、如何進行開發，以及什麼資訊需要進一步查證</strong>的輕量開發輔助 Skill。</p>



<h3 class="wp-block-heading">Bricks Builder Skill</h3>



<p class="wp-block-paragraph">目前我已將這個 Skill 整理後放到 GitHub 公開：</p>



<p class="wp-block-paragraph"><strong>GitHub：</strong><br><a href="https://github.com/kenming/bricks-builder-skill" target="_blank" rel="noreferrer noopener">https://github.com/kenming/bricks-builder-skill</a></p>



<p class="wp-block-paragraph">如果平常也使用 AI Agent 協助 Bricks Builder 網站開發，可以自行下載使用。</p>



<p class="wp-block-paragraph">Repository 的 README 已整理目前支援的能力、安裝方式、Skill 觸發方式、文件同步工具，以及版本查驗與授權邊界等相關說明。</p>



<p class="wp-block-paragraph">這個專案目前主要還是配合我自己的 Bricks Builder 網站開發持續使用與調整；後續只要 Bricks Academy 文件或實際開發需求有所變化，也會持續更新。</p>



<h3 class="wp-block-heading">Screenshots</h3>



<figure class="wp-block-image aligncenter size-large is-resized img-border"><img decoding="async" src="https://images.kenming.idv.tw/software/bricks-builder/evidence-workflow.webp" alt="Workflow：How the Bricks Builder Skill Builds an Answer" style="width:620px"/></figure>



<p class="has-text-align-center has-small-font-size wp-block-paragraph">​​圖：local-first Agent Skill，用來查詢、開發、修改與審核 Bricks Builder 網站</p>



<figure class="wp-block-image aligncenter size-large is-resized img-border"><img decoding="async" src="https://images.kenming.idv.tw/software/bricks-builder/chat-container-query.webp" alt="提出明確的 Bricks 問題，即可隱式觸發 Skill" style="width:620px"/></figure>



<p class="has-text-align-center has-small-font-size wp-block-paragraph">​​圖：提出明確的 Bricks 問題，即可隱式觸發 Skill</p>



<figure class="wp-block-image aligncenter size-large is-resized img-border"><img decoding="async" src="https://images.kenming.idv.tw/software/bricks-builder/chat-hook-query.webp" alt="需要查詢精確 hook、schema 或實作細節時，可用 $bricks-builder 明確觸發" style="width:620px"/></figure>



<p class="has-text-align-center has-small-font-size wp-block-paragraph">​​圖：需要查詢精確 hook、schema 或實作細節時，可用&nbsp;<code>$bricks-builder</code>&nbsp;明確觸發</p>
<p>〈<a rel="nofollow" href="https://kenming.idv.tw/bricks-builder-development-skill/">為 Bricks Builder 打造一個開發輔助 Skill</a>〉這篇文章最早發佈於《<a rel="nofollow" href="https://kenming.idv.tw">Kenmingの鮮思維</a>》。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://kenming.idv.tw/bricks-builder-development-skill/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<media:content url="https://images.kenming.idv.tw/software/bricks-builder/bricks-builder-skill-cover.webp" medium="image"></media:content>
	</item>
		<item>
		<title>從使用者操作到可驗證腳本：Playwright Test 核心能力</title>
		<link>https://kenming.idv.tw/playwright-test-core-capabilities/</link>
					<comments>https://kenming.idv.tw/playwright-test-core-capabilities/#respond</comments>
		
		<dc:creator><![CDATA[Wang Kenming]]></dc:creator>
		<pubDate>Fri, 31 Jul 2026 08:14:51 +0000</pubDate>
				<category><![CDATA[AI應用與開發]]></category>
		<category><![CDATA[軟體實作與編程技術]]></category>
		<category><![CDATA[Playwright]]></category>
		<category><![CDATA[UI 自動化測試]]></category>
		<category><![CDATA[AI Agent]]></category>
		<category><![CDATA[測試案例]]></category>
		<category><![CDATA[軟體測試]]></category>
		<guid isPermaLink="false">https://kenming.idv.tw/?p=23988</guid>

					<description><![CDATA[<p>UI 自動化測試 - 使用 Playwright x AI Agent 系列 #03 UI 自動化測試的重點， [&#8230;]</p>
<p>〈<a rel="nofollow" href="https://kenming.idv.tw/playwright-test-core-capabilities/">從使用者操作到可驗證腳本：Playwright Test 核心能力</a>〉這篇文章最早發佈於《<a rel="nofollow" href="https://kenming.idv.tw">Kenmingの鮮思維</a>》。</p>
]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image aligncenter size-large is-resized img-border"><img decoding="async" src="https://images.kenming.idv.tw/software/ui-automation-test/pw-test-core-script-ability.webp" alt="封面圖：Playwright Test 核心能力" style="width:620px"/></figure>



<div style="height:24px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="has-text-color has-link-color wp-elements-65b14f6a42fd0420a566b994d89525f0 wp-block-paragraph" style="color:#4f6b8a;font-size:16px"><strong><strong>UI 自動化測試 - 使用 Playwright x AI Agent</strong></strong> <strong>系列 #03</strong></p>



<div style="height:16px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">UI 自動化測試的重點，不是讓程式模擬滑鼠點擊，而是把一段使用者操作流程，轉換為可重複執行、可判定通過或失敗，並能持續維護的測試腳本。</p>



<p class="wp-block-paragraph">Playwright Test 提供的核心能力可整理為一條固定路徑：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">開啟頁面
→ 定位元素
→ 執行操作
→ 驗證結果
→ 處理等待
→ 整理測試結構</code></pre>



<p class="wp-block-paragraph">本文使用一個寵物購物系統作為案例，示範登入、商品搜尋、商品詳情、購物車與折扣金額驗證。QA 開發人員可將系統視為黑箱（Black-box），不需理解內部程式實作，而是從使用者操作界面、可觀察行為與預期結果出發，分析並撰寫 Playwright 測試腳本。</p>



<p class="wp-block-paragraph">重點不在於特定購物系統本身，而是理解如何將使用者操作流程轉換為具備明確驗證條件的 Playwright Test。</p>



<h2 class="wp-block-heading">從人工操作轉換為測試腳本</h2>



<p class="wp-block-paragraph">以登入流程為例，人工操作通常會描述為：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">開啟登入頁
→ 輸入帳號
→ 輸入密碼
→ 點擊登入
→ 確認進入商品列表頁</code></pre>



<p class="wp-block-paragraph">這段操作轉為 Playwright Test 後，可以寫成：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">import { test, expect } from &#039;@playwright/test&#039;;
​
test(&#039;有效帳號可以登入並進入商品列表&#039;, async ({ page }) =&gt; {
  await page.goto(&#039;/login&#039;);
​
  await page.getByTestId(&#039;username-input&#039;).fill(&#039;user1&#039;);
  await page.getByTestId(&#039;password-input&#039;).fill(&#039;user1&#039;);
  await page.getByTestId(&#039;login-submit&#039;).click();
​
  await expect(
    page.getByRole(&#039;heading&#039;, { name: &#039;寵物用品&#039; })
  ).toBeVisible();
​
  await expect(
    page.getByTestId(&#039;current-user&#039;)
  ).toHaveText(&#039;user1&#039;);
});</code></pre>



<p class="wp-block-paragraph">這段測試包含四個基本區段：</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>區段</th><th>Playwright 語法</th><th>用途</th></tr></thead><tbody><tr><td>定義測試</td><td><code>test(...)</code></td><td>定義一個可執行的測試案例</td></tr><tr><td>開啟頁面</td><td><code>page.goto()</code></td><td>進入測試起點</td></tr><tr><td>操作畫面</td><td><code>fill()</code>、<code>click()</code></td><td>模擬使用者操作</td></tr><tr><td>驗證結果</td><td><code>expect(...)</code></td><td>判定測試是否通過</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">​</p>



<figure class="wp-block-image aligncenter size-large is-resized img-border"><img decoding="async" src="https://images.kenming.idv.tw/software/ui-automation-test/pw-script-operation-mapping.webp" alt="流程對照圖：人工操作流程對應到腳本步驟" style="width:620px"/></figure>



<p class="has-text-align-center has-small-font-size wp-block-paragraph">​​圖：人工流程對應到腳本步驟</p>



<p class="wp-block-paragraph">這裡最重要的差異是：人工操作通常只描述「做了什麼」，測試腳本還必須定義「什麼結果才算正確」。</p>



<h2 class="wp-block-heading">使用 3A Pattern 組織測試結構與驗證意圖</h2>



<p class="wp-block-paragraph">Playwright 測試可使用 3A Pattern 整理：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">Arrange：準備測試狀態
Act：執行使用者操作
Assert：驗證使用者可見結果</code></pre>



<p class="wp-block-paragraph">例如商品搜尋測試：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">test(&#039;使用者可以搜尋犬糧商品&#039;, async ({ page }) =&gt; {
  // Arrange：登入並進入商品列表
  await page.goto(&#039;/login&#039;);
  await page.getByTestId(&#039;username-input&#039;).fill(&#039;user1&#039;);
  await page.getByTestId(&#039;password-input&#039;).fill(&#039;user1&#039;);
  await page.getByTestId(&#039;login-submit&#039;).click();
​
  // Act：執行搜尋
  await page.getByLabel(&#039;搜尋商品&#039;).fill(&#039;犬糧&#039;);
  await page.getByRole(&#039;button&#039;, { name: &#039;搜尋&#039; }).click();
​
  // Assert：驗證搜尋結果
  await expect(
    page.getByText(&#039;活力雞肉犬糧&#039;)
  ).toBeVisible();
});</code></pre>



<p class="wp-block-paragraph">​</p>



<figure class="wp-block-image aligncenter size-large is-resized img-border"><img decoding="async" src="https://images.kenming.idv.tw/software/ui-automation-test/pw-script-aaa-structure.webp" alt="圖：使用 3A（Arrange/Act/Assert）Pattern 撰寫測試案例" style="width:620px"/></figure>



<p class="has-text-align-center has-small-font-size wp-block-paragraph">​圖：使用 3A Pattern 撰寫測試案例</p>



<p class="wp-block-paragraph">AAA 並不是 Playwright 強制語法，而是一種讓測試意圖更清楚的組織方式。當測試失敗時，也較容易判斷問題發生在前置狀態、操作步驟或驗證結果。</p>



<h2 class="wp-block-heading">TypeScript 在 Playwright 測試中的角色</h2>



<p class="wp-block-paragraph">Playwright Test 可使用 JavaScript 或 TypeScript。TypeScript 在 UI 測試中的主要價值，是提供型別檢查、編輯器提示與較佳的維護性，而不是用來建立前端應用程式。</p>



<p class="wp-block-paragraph">常見結構如下：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">import { test, expect, type Page } from &#039;@playwright/test&#039;;
test 用於定義測試案例，expect 用於結果驗證，Page 則可用於 helper function 的型別標註。
async function login(page: Page) {
  // 共用登入流程
}</code></pre>



<p class="wp-block-paragraph">Playwright Test 會在測試執行時提供 <code>page</code> fixture，代表目前測試使用的瀏覽器頁面，因此不需要在每支測試中自行建立 browser、context 與 page。</p>



<h2 class="wp-block-heading">Navigation：確認測試是否進入正確頁面</h2>



<p class="wp-block-paragraph">Navigation 不只是呼叫 <code>page.goto()</code>，還包含確認頁面是否到達可操作狀態。</p>



<p class="wp-block-paragraph">若 <code>playwright.config.ts</code> 已設定 <code>baseURL</code>：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">use: {
  baseURL: &#039;http://127.0.0.1:8765&#039;,
}</code></pre>



<p class="wp-block-paragraph">測試中即可使用相對路徑：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">await page.goto(&#039;/login&#039;);</code></pre>



<p class="wp-block-paragraph">頁面開啟後，應驗證一個可觀察的狀態：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">await expect(
  page.getByRole(&#039;button&#039;, { name: &#039;登入&#039; })
).toBeVisible();</code></pre>



<p class="wp-block-paragraph">登入成功後，也可驗證 URL：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">await expect(page).toHaveURL(/.*products/);</code></pre>



<p class="wp-block-paragraph">但 URL 不一定是最重要的判準。若系統使用相同 URL 呈現不同狀態，應優先驗證使用者可見內容，例如頁面標題、目前登入者、商品名稱或完成訊息。</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">await expect(
  page.getByRole(&#039;heading&#039;, { name: &#039;寵物用品&#039; })
).toBeVisible();</code></pre>



<h2 class="wp-block-heading">Locator：找到元素不代表定位方式合理</h2>



<p class="wp-block-paragraph">Locator 決定測試如何找到欄位、按鈕、連結、商品卡片與提示訊息。</p>



<p class="wp-block-paragraph">Playwright 測試不應只追求「目前找得到元素」，而應選擇在 UI 小幅調整後仍能維持穩定的定位方式。</p>



<p class="wp-block-paragraph">常見 Locator 包括：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">page.getByRole(&#039;button&#039;, { name: &#039;登入&#039; });
page.getByLabel(&#039;搜尋商品&#039;);
page.getByPlaceholder(&#039;請輸入商品名稱&#039;);
page.getByText(&#039;活力雞肉犬糧&#039;);
page.getByTestId(&#039;username-input&#039;);
page.locator(&#039;input[name=&quot;username&quot;]&#039;);</code></pre>



<p class="wp-block-paragraph">建議優先順序如下：</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>優先順序</th><th>Locator</th><th>適用情境</th></tr></thead><tbody><tr><td>1</td><td><code>getByRole()</code></td><td>按鈕、連結、標題、輸入框等具語意元素</td></tr><tr><td>2</td><td><code>getByLabel()</code></td><td>表單欄位</td></tr><tr><td>3</td><td><code>getByPlaceholder()</code></td><td>缺少 label 的輸入欄位</td></tr><tr><td>4</td><td><code>getByText()</code></td><td>商品名稱、訊息與可見文字</td></tr><tr><td>5</td><td><code>getByTestId()</code></td><td>需要穩定且明確的測試識別</td></tr><tr><td>6</td><td><code>locator()</code></td><td>使用穩定的 <code>name</code>、<code>type</code> 等屬性</td></tr><tr><td>7</td><td>結構型 CSS 或 XPath</td><td>無其他可靠線索時的最後手段</td></tr></tbody></table></figure>



<h3 class="wp-block-heading">先縮小範圍，再操作目標元素</h3>



<p class="wp-block-paragraph">商品列表中可能存在多個「查看」連結。下列寫法可能點擊第一個匹配項目，而不是指定商品：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">await page.getByRole(&#039;link&#039;, { name: &#039;查看&#039; }).click();</code></pre>



<p class="wp-block-paragraph">較合理的作法，是先定位商品卡片，再操作卡片內的連結：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">const productCard = page
  .getByTestId(&#039;product-card&#039;)
  .filter({ hasText: &#039;活力雞肉犬糧&#039; });
​
await productCard
  .getByRole(&#039;link&#039;, { name: &#039;查看&#039; })
  .click();</code></pre>



<p class="wp-block-paragraph">這種寫法保留了明確的操作意圖：不是點擊任意「查看」，而是開啟指定商品的詳情頁。</p>



<h3 class="wp-block-heading">避免容易失效的 Selector</h3>



<p class="wp-block-paragraph">下列 Selector 過度依賴 DOM 位置、CSS class 或完整結構：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">page.locator(&#039;div:nth-child(3) &gt; button&#039;);
page.locator(&#039;.btn.btn-primary.mt-2&#039;);
page.locator(&#039;#app &gt; main &gt; section &gt; div &gt; div:nth-child(2)&#039;);</code></pre>



<p class="wp-block-paragraph">只要畫面重新排版、CSS class 調整或外層增加容器，測試就可能失效。</p>



<p class="wp-block-paragraph">較好的寫法應描述使用者操作意圖：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">await page
  .getByRole(&#039;button&#039;, { name: &#039;加入購物車&#039; })
  .click();</code></pre>



<p class="wp-block-paragraph">Locator 品質會直接影響測試穩定性。測試腳本應保護使用者行為，而不是綁定當下的 HTML 排版方式。</p>



<h2 class="wp-block-heading">User Actions：模擬使用者實際操作</h2>



<p class="wp-block-paragraph">常見 User Actions 包括：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">await page.getByRole(&#039;button&#039;, { name: &#039;登入&#039; }).click();
await page.getByLabel(&#039;搜尋商品&#039;).fill(&#039;犬糧&#039;);
await page.getByLabel(&#039;商品分類&#039;).selectOption(&#039;dog&#039;);
await page.getByRole(&#039;checkbox&#039;, { name: &#039;只顯示有庫存商品&#039; }).check();</code></pre>



<p class="wp-block-paragraph">搜尋框若支援 Enter，也可以使用：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">const searchBox = page.getByLabel(&#039;搜尋商品&#039;);
​
await searchBox.fill(&#039;犬糧&#039;);
await searchBox.press(&#039;Enter&#039;);</code></pre>



<p class="wp-block-paragraph">測試操作應盡量符合真實使用方式。若使用者會點擊搜尋按鈕，就測試 <code>click()</code>；若正式流程支援 Enter，就測試 <code>press('Enter')</code>。</p>



<p class="wp-block-paragraph">測試不應為了方便而改走使用者不會使用的捷徑，否則即使測試通過，也未必能證明實際 UI 流程可用。</p>



<h2 class="wp-block-heading">Assertions 設計原則</h2>



<p class="wp-block-paragraph">Action 只代表操作已執行，不代表功能正確。</p>



<p class="wp-block-paragraph">例如：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">await page
  .getByRole(&#039;button&#039;, { name: &#039;加入購物車&#039; })
  .click();</code></pre>



<p class="wp-block-paragraph">這行只能證明 Playwright 成功點擊按鈕，不能證明商品真的加入購物車，更不能證明數量、折扣與總金額正確。</p>



<p class="wp-block-paragraph">購物車流程應進一步驗證：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">await expect(
  page.getByText(&#039;活力雞肉犬糧&#039;)
).toBeVisible();
​
await expect(
  page.getByTestId(&#039;cart-subtotal&#039;)
).toHaveText(&#039;NT$ 1360&#039;);
​
await expect(
  page.getByTestId(&#039;cart-discount&#039;)
).toHaveText(&#039;NT$ 136&#039;);
​
await expect(
  page.getByTestId(&#039;cart-total&#039;)
).toHaveText(&#039;NT$ 1224&#039;);</code></pre>



<p class="wp-block-paragraph">不同操作可對應不同的業務結果：</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>不足的驗證</th><th>較完整的驗證</th></tr></thead><tbody><tr><td>登入按鈕可點擊</td><td>登入後顯示商品列表與目前使用者</td></tr><tr><td>搜尋欄可以輸入文字</td><td>搜尋結果出現指定商品</td></tr><tr><td>加入購物車按鈕可點擊</td><td>購物車顯示商品、數量與金額</td></tr><tr><td>前往結帳按鈕可點擊</td><td>結帳頁顯示訂單摘要與正確總額</td></tr><tr><td>送出訂單按鈕可點擊</td><td>顯示訂單成立訊息與訂單金額</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">Assertion 的目的，是證明使用者真正關心的結果仍然成立。</p>



<h2 class="wp-block-heading">Auto-waiting：等待機制與 timeout 使用原則</h2>



<p class="wp-block-paragraph">Playwright 在執行操作前，會自動檢查元素是否可見、穩定、可接收事件，並確認沒有被其他元素遮住。這些機制稱為 auto-waiting 與 actionability checks。</p>



<p class="wp-block-paragraph">因此，多數測試不需要自行加入固定等待：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">await page.waitForTimeout(3000);</code></pre>



<p class="wp-block-paragraph">固定等待有兩個問題：</p>



<ul class="wp-block-list">
<li>頁面若 1 秒完成，測試仍會浪費 3 秒。</li>



<li>頁面若超過 3 秒才完成，測試仍會失敗。</li>
</ul>



<p class="wp-block-paragraph">較好的方式是等待明確狀態：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">await expect(
  page.getByText(&#039;活力雞肉犬糧&#039;)
).toBeVisible();</code></pre>



<p class="wp-block-paragraph">或等待頁面切換與標題出現：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">await expect(page).toHaveURL(/.*cart/);
​
await expect(
  page.getByRole(&#039;heading&#039;, { name: &#039;購物車&#039; })
).toBeVisible();</code></pre>



<p class="wp-block-paragraph">Timeout 也不應用來掩蓋不穩定測試。測試經常 timeout 時，應先檢查：</p>



<ul class="wp-block-list">
<li>服務是否已啟動。</li>



<li>URL 或 port 是否正確。</li>



<li>Locator 是否符合目前 UI。</li>



<li>前一步操作是否成功。</li>



<li>Assertion 是否符合實際畫面。</li>



<li>測試資料是否受到前一次執行影響。</li>
</ul>



<p class="wp-block-paragraph">只有在明確知道某個操作確實需要較長時間時，才調整 timeout。</p>



<h2 class="wp-block-heading">測試案例的命名原則</h2>



<p class="wp-block-paragraph">測試案例不應只是描述操作：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">test(&#039;click checkout button&#039;, async ({ page }) =&gt; {
  // ...
});</code></pre>



<p class="wp-block-paragraph">較好的名稱應直接表達驗證目標：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">test(&#039;購物車達滿額門檻時可以看到 9 折後總額&#039;, async ({ page }) =&gt; {
  // ...
});</code></pre>



<p class="wp-block-paragraph">測試名稱應回答：</p>



<pre class="wp-block-preformatted">這個測試要證明什麼行為仍然成立？</pre>



<p class="wp-block-paragraph">當測試報告顯示失敗時，具體名稱也能讓開發者直接理解受影響的功能，而不必先閱讀整支腳本。</p>



<h2 class="wp-block-heading">集中管理測試資料</h2>



<p class="wp-block-paragraph">測試資料若散落在操作與 Assertion 中，後續很難判斷各數值的來源。</p>



<p class="wp-block-paragraph">可先集中整理：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">const testUser = {
  username: &#039;user1&#039;,
  password: &#039;user1&#039;,
};
​
const targetProduct = {
  keyword: &#039;犬糧&#039;,
  name: &#039;活力雞肉犬糧&#039;,
  unitPriceText: &#039;NT$ 680&#039;,
  quantityForDiscount: &#039;2&#039;,
  subtotalWithDiscountText: &#039;NT$ 1360&#039;,
  discountText: &#039;NT$ 136&#039;,
  totalWithDiscountText: &#039;NT$ 1224&#039;,
};</code></pre>



<p class="wp-block-paragraph">當商品價格、測試帳號或折扣規則調整時，就能清楚知道哪些預期值需要同步修改。</p>



<p class="wp-block-paragraph">測試資料的整理方式可依規模選擇：</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>資料規模</th><th>建議方式</th><th>適用情境</th></tr></thead><tbody><tr><td>少量固定資料</td><td>測試檔或 helper 檔案</td><td>單一 Smoke Test、少量帳號與商品</td></tr><tr><td>中量測試資料</td><td>JSON 或 CSV</td><td>多組登入、搜尋與預期結果</td></tr><tr><td>大量或需重設狀態</td><td>DB seed data</td><td>商品、使用者、訂單與初始狀態</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">DB seed data 可用來建立固定前置條件，但不能取代 UI 驗證。Playwright 測試仍應從使用者可見畫面判斷商品、金額、錯誤訊息與流程結果是否正確。</p>



<h2 class="wp-block-heading">使用 Helper Function 整理重複操作</h2>



<p class="wp-block-paragraph">當多支測試都需要登入、搜尋商品或進入商品詳情頁，可將重複操作抽成 helper function。</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">async function login(page: Page) {
  await page.goto(&#039;/login&#039;);
​
  await page
    .getByTestId(&#039;username-input&#039;)
    .fill(testUser.username);
​
  await page
    .getByTestId(&#039;password-input&#039;)
    .fill(testUser.password);
​
  await page
    .getByTestId(&#039;login-submit&#039;)
    .click();
​
  await expect(
    page.getByRole(&#039;heading&#039;, { name: &#039;寵物用品&#039; })
  ).toBeVisible();
}</code></pre>



<p class="wp-block-paragraph">搜尋商品也可整理為：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">async function searchProduct(
  page: Page,
  keyword: string,
  productName: string,
) {
  await page
    .getByLabel(&#039;搜尋商品&#039;)
    .fill(keyword);
​
  await page
    .getByRole(&#039;button&#039;, { name: &#039;搜尋&#039; })
    .click();
​
  await expect(
    page.getByRole(&#039;heading&#039;, { name: productName })
  ).toBeVisible();
}</code></pre>



<p class="wp-block-paragraph">Helper function 應保持短小，並封裝具備明確語意的重複操作。若過度包裝，測試案例本身反而會失去可讀性。</p>



<p class="wp-block-paragraph">另外，不應讓測試案例彼此呼叫。多個案例若共用前置流程，應抽成 helper function 或放入 <code>beforeEach()</code>，讓每個 <code>test(...)</code> 仍可獨立執行。</p>



<h2 class="wp-block-heading">建立最小購物流程測試</h2>



<p class="wp-block-paragraph">將登入、清空購物車、搜尋商品、開啟商品詳情與金額驗證整合後，可形成下列測試：</p>


<pre class="language-typescript no-line-numbers"><code class="language-typescript no-line-numbers">import { test, expect, type Page } from &#039;@playwright/test&#039;;
​
const testUser = {
  username: &#039;user2&#039;,
  password: &#039;user2&#039;,
};
​
const product = {
  keyword: &#039;犬糧&#039;,
  name: &#039;活力雞肉犬糧&#039;,
  quantity: &#039;2&#039;,
  subtotalText: &#039;NT$ 1360&#039;,
  discountText: &#039;NT$ 136&#039;,
  totalText: &#039;NT$ 1224&#039;,
};
​
async function login(page: Page) {
  await page.goto(&#039;/login&#039;);
​
  await page
    .getByTestId(&#039;username-input&#039;)
    .fill(testUser.username);
​
  await page
    .getByTestId(&#039;password-input&#039;)
    .fill(testUser.password);
​
  await page
    .getByTestId(&#039;login-submit&#039;)
    .click();
​
  await expect(
    page.getByRole(&#039;heading&#039;, { name: &#039;寵物用品&#039; })
  ).toBeVisible();
}
​
async function clearCart(page: Page) {
  await page.goto(&#039;/cart&#039;);
​
  while (
    await page.getByRole(&#039;button&#039;, { name: /移除/ }).count()
  ) {
    await page
      .getByRole(&#039;button&#039;, { name: /移除/ })
      .first()
      .click();
  }
}
​
test(
  &#039;使用者可以搜尋商品並在加入購物車後看到滿額折扣&#039;,
  async ({ page }) =&gt; {
    await login(page);
    await clearCart(page);
    await page.goto(&#039;/products&#039;);
​
    await page
      .getByLabel(&#039;搜尋商品&#039;)
      .fill(product.keyword);
​
    await page
      .getByRole(&#039;button&#039;, { name: &#039;搜尋&#039; })
      .click();
​
    const productCard = page
      .getByTestId(&#039;product-card&#039;)
      .filter({ hasText: product.name });
​
    await productCard
      .getByRole(&#039;link&#039;, { name: &#039;查看&#039; })
      .click();
​
    await page
      .getByLabel(&#039;數量&#039;)
      .fill(product.quantity);
​
    await page
      .getByRole(&#039;button&#039;, { name: &#039;加入購物車&#039; })
      .click();
​
    await expect(
      page.getByRole(&#039;heading&#039;, { name: &#039;購物車&#039; })
    ).toBeVisible();
​
    await expect(
      page.getByTestId(&#039;cart-subtotal&#039;)
    ).toHaveText(product.subtotalText);
​
    await expect(
      page.getByTestId(&#039;cart-discount&#039;)
    ).toHaveText(product.discountText);
​
    await expect(
      page.getByTestId(&#039;cart-total&#039;)
    ).toHaveText(product.totalText);
  },
);</code></pre>



<p class="wp-block-paragraph">這支測試同時示範：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">Navigation
→ Locator
→ User Actions
→ Assertions
→ Auto-waiting
→ Test Data
→ Helper Function</code></pre>



<p class="wp-block-paragraph">測試執行前先清空購物車，是為了避免前一次執行殘留資料影響金額。這也說明 UI 測試的穩定性不只取決於腳本語法，還取決於可控制的測試前置狀態。</p>



<h2 class="wp-block-heading">觀察測試實際操作瀏覽器</h2>



<p class="wp-block-paragraph">Playwright Test 預設使用 headless 模式。若需要觀察實際操作，可加入 <code>--headed</code>：</p>


<pre class="language-powershell no-line-numbers"><code class="language-powershell no-line-numbers">npx playwright test tests/core-scripts/petshop_product_flow.spec.ts --project=chromium --headed</code></pre>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Playwright Test 預設使用 headless（無頭）模式，也就是測試執行時不顯示瀏覽器視窗，適合快速執行與自動化驗證。Headed（有頭）模式則會開啟可見的瀏覽器視窗，方便觀察實際操作流程、檢查 Locator 與分析測試失敗原因。</p>
</blockquote>



<p class="wp-block-paragraph">若要逐步檢查 Locator 與操作流程，可使用 debug 模式：</p>


<pre class="language-powershell no-line-numbers"><code class="language-powershell no-line-numbers">npx playwright test tests/core-scripts/petshop_product_flow.spec.ts --project=chromium --debug</code></pre>



<p class="wp-block-paragraph">Debug 模式會開啟 Playwright Inspector，可逐步查看操作、Locator 與測試狀態。</p>



<p class="wp-block-paragraph">使用 VS Code 的 Playwright Test 擴充套件時，也可在 Testing 面板啟用 <strong>Show Browser</strong>，直接觀察測試執行過程。</p>



<p class="wp-block-paragraph">​</p>



<figure class="wp-block-image aligncenter size-large is-resized img-border"><img decoding="async" src="https://images.kenming.idv.tw/software/ui-automation-test/pw-vscode-show-browser-run.webp" alt="圖：Playwright 測試總管啟用 Show Browser 後執行測試" style="width:320px"/></figure>



<p class="has-text-align-center has-small-font-size wp-block-paragraph">​圖：Playwright 測試總管啟用 Show Browser 後執行測試</p>



<h2 class="wp-block-heading">不要急著把所有流程塞進一支測試</h2>



<p class="wp-block-paragraph">完整購物主流程可能包含：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">登入
→ 商品列表
→ 分類與分頁
→ 搜尋商品
→ 商品詳情
→ 加入購物車
→ 折扣驗證
→ 修改數量
→ 結帳
→ 送出訂單
→ 訂單成立</code></pre>



<p class="wp-block-paragraph">若一開始就把所有步驟塞進單一測試，腳本會過長，失敗時也難以定位問題。</p>



<p class="wp-block-paragraph">較合理的作法是先拆成：</p>



<ol class="wp-block-list">
<li>登入後可以進入商品列表。</li>



<li>可以搜尋指定商品並進入商品詳情。</li>



<li>加入商品後可以看到正確小計與折扣。</li>



<li>修改數量後，折扣金額會依規則變化。</li>



<li>達到折扣門檻後可以完成結帳。</li>
</ol>



<p class="wp-block-paragraph">待各段流程穩定後，再依測試目的決定是否組合為一條核心 UI Smoke Test。</p>



<p class="wp-block-paragraph">Smoke Test 的價值不在於步驟最多，而在於能以合理成本快速回答：</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">系統修改後，最重要的使用者流程是否仍然可以完成？</p>
</blockquote>



<h2 class="wp-block-heading">結語</h2>



<p class="wp-block-paragraph">Playwright 核心腳本能力可以整理為：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">Navigation
→ Locator
→ User Actions
→ Assertions
→ Waiting / Auto-waiting
→ Test Structure</code></pre>



<p class="wp-block-paragraph">撰寫 UI 測試時，應先理解使用者操作流程，再決定如何定位、操作與驗證。</p>



<p class="wp-block-paragraph">Locator 應盡量使用接近使用者視角的語意；Action 應對應真實操作；Assertion 應驗證使用者可見的業務結果，而不是只確認按鈕可以點擊。</p>



<p class="wp-block-paragraph">當測試資料、前置狀態與重複流程也被適當整理後，Playwright 腳本才會從一次性的瀏覽器操作，轉變為可重複執行、可診斷、可維護的驗收規格。</p>
<p>〈<a rel="nofollow" href="https://kenming.idv.tw/playwright-test-core-capabilities/">從使用者操作到可驗證腳本：Playwright Test 核心能力</a>〉這篇文章最早發佈於《<a rel="nofollow" href="https://kenming.idv.tw">Kenmingの鮮思維</a>》。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://kenming.idv.tw/playwright-test-core-capabilities/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<media:content url="https://images.kenming.idv.tw/software/ui-automation-test/pw-test-core-script-ability.webp" medium="image"></media:content>
	</item>
		<item>
		<title>從 GitHub Issue 至 PR：遠端協同開發的寬鬆耦合的合作模式</title>
		<link>https://kenming.idv.tw/github-issue-to-pr-loosely-coupled-collaboration/</link>
					<comments>https://kenming.idv.tw/github-issue-to-pr-loosely-coupled-collaboration/#respond</comments>
		
		<dc:creator><![CDATA[Wang Kenming]]></dc:creator>
		<pubDate>Tue, 28 Jul 2026 07:56:28 +0000</pubDate>
				<category><![CDATA[軟體開發方法論]]></category>
		<category><![CDATA[AI應用與開發]]></category>
		<category><![CDATA[GitHub協作]]></category>
		<category><![CDATA[遠端協同開發]]></category>
		<category><![CDATA[LooseCoupling]]></category>
		<category><![CDATA[軟體設計]]></category>
		<guid isPermaLink="false">https://kenming.idv.tw/?p=23981</guid>

					<description><![CDATA[<p>前兩個星期，我完成了 ZenGTPX。它可以將 Zen7（天頂圍棋）包裝成標準 GTP 引擎，除了可供人機對局 [&#8230;]</p>
<p>〈<a rel="nofollow" href="https://kenming.idv.tw/github-issue-to-pr-loosely-coupled-collaboration/">從 GitHub Issue 至 PR：遠端協同開發的寬鬆耦合的合作模式</a>〉這篇文章最早發佈於《<a rel="nofollow" href="https://kenming.idv.tw">Kenmingの鮮思維</a>》。</p>
]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image aligncenter size-large is-resized img-border"><img decoding="async" src="https://images.kenming.idv.tw/software/github-issue-to-pr-loosely-coupled-collaboration.webp" alt="封面圖：從 GitHub Issue 至 PR：遠端協同開發的寬鬆耦合的合作模式" style="width:620px"/></figure>



<p class="wp-block-paragraph">前兩個星期，我完成了 ZenGTPX。它可以將 Zen7（天頂圍棋）包裝成標準 GTP 引擎，除了可供人機對局使用，也能與其他圍棋 AI 引擎進行自動對局。</p>



<p class="wp-block-paragraph">🔗 <a href="https://github.com/kenming/ZenGTPX" target="_blank" rel="noopener">https://github.com/kenming/ZenGTPX</a></p>



<p class="wp-block-paragraph">我把 ZenGTPX 加入 LizzieYzy Next，作為其中一個 GTP 引擎。如此不僅可以在該介面內進行人機對局，也能安排 Zen7 與 KataGo 等不同引擎進行自動對局。</p>



<p class="wp-block-paragraph">🔗 <a href="https://github.com/wimi321/lizzieyzy-next" target="_blank" rel="noopener">https://github.com/wimi321/lizzieyzy-next</a></p>



<p class="wp-block-paragraph">不過，LizzieYzy Next 原本的引擎設定介面主要提供通用的啟動與對局參數，無法直接呈現 ZenGTPX 專屬的棋力設定。</p>



<p class="wp-block-paragraph">ZenGTPX 提供三種棋力設定模式：<code>rank</code>、<code>fixed-time</code> 與 <code>advanced</code>。</p>



<p class="wp-block-paragraph"><code>rank</code> 模式最為方便，只要直接設定棋力，範圍從 6 級到 9 段即可；<code>fixed-time</code> 模式則固定每手棋的思考時間，實際能完成多少搜尋計算，仍會受到 CPU 運算效能影響；<code>advanced</code> 模式提供更細部的參數設定，可透過 Config 設定檔調整多組 Zen7 搜尋參數，包括部分原 Zen7 GUI 未直接提供的底層設定，用以進一步微調棋力與搜尋行為。</p>



<p class="wp-block-paragraph">因此，我在 LizzieYzy Next 的 GitHub 提出了一篇 Issue，希望它可以支援 ZenGTPX 的棋力參數設定。</p>



<p class="wp-block-paragraph">作者好有心，很快就回覆，並且提出一個更具擴展性的方向：由 GTP 引擎透過穩定的擴充協定，自行提供設定 Schema、目前生效值、參數驗證與 Profile；LizzieYzy Next 則負責偵測引擎是否具備這項能力，並依據 Schema 自動產生設定介面。</p>



<p class="wp-block-paragraph">如此一來，這套機制不會只適用於 ZenGTPX，未來其他支援標準 GTP 協定的 AI 引擎，也可以使用相同方式擴充自己的專屬設定。</p>



<p class="wp-block-paragraph">從設計角度來看，這是以擴充協定、能力偵測與設定 Schema 為核心的 Plugin 設計。LizzieYzy Next 不需要預先知道各種引擎有哪些參數，而是由引擎自行描述能力與設定內容。</p>



<p class="wp-block-paragraph">🔗 <a href="https://github.com/wimi321/lizzieyzy-next/issues/101" target="_blank" rel="noopener">https://github.com/wimi321/lizzieyzy-next/issues/101</a></p>



<p class="wp-block-paragraph">我依照這個方向，在 ZenGTPX 端完成了 Configuration Protocol v1；LizzieYzy Next 作者則在 Next 端完成通用協定的偵測、設定介面、Profile 保存、執行中套用、多引擎設定隔離與錯誤處理。</p>



<p class="wp-block-paragraph">作者先使用模擬的 fake ZenGTPX Process 跑過完整流程，驗證能力偵測、設定、保存與重新啟動後的 Profile 還原。自動化測試全部通過後，他提交了 PR #126，將這套通用的 GTP 引擎棋力設定機制整合進 LizzieYzy Next。</p>



<p class="wp-block-paragraph">🔗 <a href="https://github.com/wimi321/lizzieyzy-next/pull/126" target="_blank" rel="noopener">https://github.com/wimi321/lizzieyzy-next/pull/126</a></p>



<p class="wp-block-paragraph">PR 合併後，作者希望我協助進行 Windows 與真實 Zen7／Zen.dll 的整合驗證。</p>



<p class="wp-block-paragraph">我先使用 VS Code 開啟原來的 ZenGTPX 專案，再將 PR 連結交給 Codex 分析。Codex 隨即取得相關分支內容，並整理出兩組可供 LizzieYzy Next 使用的 ZenGTPX 設定檔；接著我再請 Codex 啟動 LizzieYzy Next，讓我直接進行實機驗證。</p>



<p class="wp-block-paragraph">實際測試結果完全正常。<code>rank</code>、<code>fixed-time</code> 與 <code>advanced</code> 三種模式皆可正常顯示與套用，設定保存、執行中更新、重新啟動後還原，以及相同執行檔的多引擎 Profile 隔離也都沒有問題。</p>



<p class="wp-block-paragraph">完成測試後，我將結果交給 Codex 整理成 PR 回覆。作者之後又在最新的主分支上重新執行相同測試，確認全部通過，最後正式關閉原來的 Issue。</p>



<p class="wp-block-paragraph">整個遠端協同開發的過程，不到一個星期便完成了兩個應用程式的擴展整合。真正讓我感到有意思的，並不只是功能最終得以實現，而是整個合作過程幾乎完全建立在 GitHub 的標準協作機制上：先透過 Issue 整理需求與交換設計想法，再由開發者建立獨立分支完成實作，透過 PR（Pull Request）公開變更內容，交由另一方測試、驗證與回報，最後才合併至正式版本。</p>



<p class="wp-block-paragraph">雙方不需要共享相同的開發環境，也不需要頻繁召開會議或同步各自的工作進度，只要將需求、協定、程式變更與驗證結果清楚地留在 Issue 與 PR 中，便能各自在相對獨立的情況下完成工作。這正是一種 Loose Coupling 的協同開發模式：彼此保持低度依賴，卻能透過明確的介面、流程與可追蹤的紀錄，快速完成跨專案的合作與整合。</p>
<p>〈<a rel="nofollow" href="https://kenming.idv.tw/github-issue-to-pr-loosely-coupled-collaboration/">從 GitHub Issue 至 PR：遠端協同開發的寬鬆耦合的合作模式</a>〉這篇文章最早發佈於《<a rel="nofollow" href="https://kenming.idv.tw">Kenmingの鮮思維</a>》。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://kenming.idv.tw/github-issue-to-pr-loosely-coupled-collaboration/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<media:content url="https://images.kenming.idv.tw/software/github-issue-to-pr-loosely-coupled-collaboration.webp" medium="image"></media:content>
	</item>
		<item>
		<title>從業務流程到測試案例：Playwright 基礎操作與實務應用</title>
		<link>https://kenming.idv.tw/business-process-to-playwright-ui-automation-testing/</link>
					<comments>https://kenming.idv.tw/business-process-to-playwright-ui-automation-testing/#respond</comments>
		
		<dc:creator><![CDATA[Wang Kenming]]></dc:creator>
		<pubDate>Thu, 23 Jul 2026 12:47:48 +0000</pubDate>
				<category><![CDATA[AI應用與開發]]></category>
		<category><![CDATA[軟體實作與編程技術]]></category>
		<category><![CDATA[Playwright]]></category>
		<category><![CDATA[UI 自動化測試]]></category>
		<category><![CDATA[AI Agent]]></category>
		<category><![CDATA[測試案例]]></category>
		<category><![CDATA[u]]></category>
		<category><![CDATA[Business Process]]></category>
		<guid isPermaLink="false">https://kenming.idv.tw/?p=23965</guid>

					<description><![CDATA[<p>UI 自動化測試 - 使用 Playwright x AI Agent 系列 #02 開始使用 Playwri [&#8230;]</p>
<p>〈<a rel="nofollow" href="https://kenming.idv.tw/business-process-to-playwright-ui-automation-testing/">從業務流程到測試案例：Playwright 基礎操作與實務應用</a>〉這篇文章最早發佈於《<a rel="nofollow" href="https://kenming.idv.tw">Kenmingの鮮思維</a>》。</p>
]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image aligncenter size-large is-resized img-border"><img decoding="async" src="https://images.kenming.idv.tw/software/ui-automation-test/business-to-playwright-automation-test.webp" alt="封面圖：AI Agent 協作開發中的 UI 自動化驗證流程" style="width:620px"/></figure>



<div style="height:24px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="has-text-color has-link-color wp-elements-132d68c1d37b685e9774bd76dd554e5b wp-block-paragraph" style="color:#4f6b8a;font-size:16px"><strong><strong>UI 自動化測試 - 使用 Playwright x AI Agent</strong></strong> <strong>系列 #02</strong></p>



<div style="height:16px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">開始使用 Playwright 撰寫 UI 自動化測試時，往往會先將注意力放在 click()、fill()、Locator 與 Assertion 等基礎腳本語法。然而，真正影響測試能否穩定執行、重複驗證與持續維護的關鍵，通常不在語法本身，而在於是否已先明確界定測試範圍。</p>



<p class="wp-block-paragraph">現實中的業務流程（Business Process）通常橫跨多個角色、作業活動與系統狀態，涵蓋範圍往往過大；Playwright 測試腳本則需要明確界定前置條件、操作步驟、測試資料與預期結果。若未先將業務流程轉換為具體的測試情境與測試案例，開發者或 AI Agent 便只能自行推測測試應從何處開始、驗證範圍到哪裡為止，以及符合哪些條件才算通過。</p>



<p class="wp-block-paragraph">因此，進入 Playwright 腳本實作前，需要先處理兩個問題：</p>



<ol class="wp-block-list">
<li>如何從業務流程界定 Test Scenario、Test Suite 與 Test Case。</li>



<li>Playwright 測測試體系的三個核心組成（CLI、Skill 與 Test），以及他們在瀏覽器操作、Agent 協作與自動化測試中的分工。</li>
</ol>



<p class="wp-block-paragraph">這兩個問題釐清後，瀏覽器操作才可能從一次性的 UI 探索，轉換為可重複執行的自動化測試。</p>



<h2 class="wp-block-heading">業務流程不等於測試案例</h2>



<p class="wp-block-paragraph">業務流程（Business Process）通常描述跨角色、跨活動與跨系統狀態的完整作業脈絡。例如：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">建立申請
→ 主管審核
→ 退回補件
→ 再次送審
→ 核准
→ 查詢處理結果</code></pre>



<p class="wp-block-paragraph">這份流程適合用來理解系統如何運作，但不適合直接轉成一支 Playwright Test。流程中可能包含不同角色登入、人工判斷、等待事件、外部通知，以及無法在同一次瀏覽器操作中穩定完成的狀態轉換。</p>



<p class="wp-block-paragraph">若直接把完整流程塞進一支測試，常見結果包括：</p>



<ul class="wp-block-list">
<li>測試執行時間過長。</li>



<li>前置狀態與測試資料難以重建。</li>



<li>任一中間步驟失敗，後續驗證全部中斷。</li>



<li>失敗結果只能指出流程未完成，卻難以定位問題。</li>
</ul>



<p class="wp-block-paragraph">UI 自動化需要先從完整業務流程中找出可由單一角色連續操作、而且可以從畫面判定結果的範圍。</p>



<h2 class="wp-block-heading">企業流程、活動與操作程序的區分</h2>



<p class="wp-block-paragraph">將業務描述轉換為 UI 測試之前，需先區分業務流程（Business Process）、業務活動（Activity）與操作程序（Operation Procedure）三個層級。</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>名稱</th><th>定位</th><th>範例</th></tr></thead><tbody><tr><td>業務流程</td><td>跨角色與活動的完整作業脈絡</td><td>購買商品、付款、出貨與配送</td></tr><tr><td>業務活動</td><td>某個角色在特定節點完成的工作</td><td>顧客購買商品</td></tr><tr><td>操作程序</td><td>操作者在一次操作階段內完成的一連串 UI 步驟</td><td>登入、搜尋商品、加入購物車與完成結帳</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">以電子商務購物流程為例，可以拆分為：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">Business Process：商品購物與訂單處理流程
　
Activity：訂購（購買）商品
Operation Procedure：
登入網站 → 搜尋商品 → 查看商品詳情 → 加入購物車
→ 填寫收件與付款資料 → 送出訂單
　
Activity：處理商品出貨
Operation Procedure：
登入後台 → 查詢待出貨訂單 → 檢視訂單內容
→ 建立出貨資料 → 更新訂單狀態</code></pre>



<p class="wp-block-paragraph">使用 Playwright 撰寫測試案例時，通常不會直接驗證完整的業務流程，而是選定其中某項業務活動所包含的操作程序，進一步界定為可執行、可驗證的測試範圍。</p>



<p class="wp-block-paragraph">例如：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">客戶登入電子商務網站
→ 搜尋並查看商品詳情
→ 將商品加入購物車
→ 填寫訂購資料並完成結帳
→ 確認畫面顯示訂單成立結果</code></pre>



<p class="wp-block-paragraph">這段操作程序具有明確的起點與終點，測試資料及前置狀態也可加以控制，最終結果則能透過畫面內容判定，因此較適合進一步轉換為自動化測試案例。</p>



<h2 class="wp-block-heading">從業務情境轉換為測試情境、測試套件與測試案例</h2>



<p class="wp-block-paragraph">業務流程與使用者操作程序仍不屬於正式的測試規格。若要進一步轉換為可執行的使用者介面（UI）測試，需先區分測試情境（Test Scenario）、測試套件（Test Suite）與測試案例（Test Case）。</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>名稱</th><th>回答的問題</th><th>主要用途</th></tr></thead><tbody><tr><td>測試情境</td><td>要驗證什麼情境？</td><td>描述使用者目標或主要業務情境</td></tr><tr><td>測試套件</td><td>如何組織相關案例？</td><td>依功能、流程或驗證目的管理一組測試案例</td></tr><tr><td>測試案例</td><td>如何進行具體驗證？</td><td>定義前置條件、測試資料、操作步驟與預期結果</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">三者的關係可簡化為：</p>



<ul class="wp-block-list">
<li>測試情境：界定測試意圖</li>



<li>測試套件：組織相關測試案例</li>



<li>測試案例：界定可執行條件與預期結果</li>
</ul>



<p class="wp-block-paragraph">以一般電子商務網站為例：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">測試情境：
已登入的客戶可以搜尋商品、加入購物車並完成結帳。
　
測試套件：
購物主流程 UI 驗證
　
測試案例：
1. 使用有效帳號登入後，可以進入商品列表。
2. 輸入指定關鍵字後，可以看到目標商品。
3. 將商品加入購物車後，商品數量與小計顯示正確。
4. 進入結帳頁面後，訂單總額與購物車金額一致。
5. 送出訂單後，畫面顯示訂單成立結果。</code></pre>



<p class="wp-block-paragraph">測試情境用於描述需要驗證的使用者目標，不必包含所有操作細節；測試案例則必須提供足夠資訊，使人工執行者、Playwright 測試腳本與 AI Agent 對驗證範圍及成功條件產生一致理解。</p>



<p class="wp-block-paragraph">一個可執行的測試案例，至少需要回答下列問題：</p>



<ul class="wp-block-list">
<li>要驗證哪一項使用者目標？</li>



<li>測試從什麼系統狀態開始？</li>



<li>使用哪些帳號與測試資料？</li>



<li>需要執行哪些 UI 操作？</li>



<li>操作程序在哪個階段結束？</li>



<li>應依據哪些畫面、文字、網址或資料判定測試通過？</li>



<li>測試失敗時，是否能定位至特定操作階段？</li>
</ul>



<p class="wp-block-paragraph">若缺少這些條件，內容仍只是業務流程或操作程序的描述，尚不能直接視為可供 Playwright 執行的測試規格。</p>



<h2 class="wp-block-heading">測試資料與前置條件是測試案例的必要組成</h2>



<p class="wp-block-paragraph">測試需求若只描述為：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">加入商品並確認折扣。</code></pre>



<p class="wp-block-paragraph">這段描述沒有明確指定商品單價、購買數量、折扣門檻與預期金額。執行者只能自行補充缺少的條件，最後可能只確認畫面出現「折扣」文字，卻未實際驗證折扣金額與訂單總額是否計算正確。</p>



<p class="wp-block-paragraph">較完整的測試資料（Test Data）可以定義為：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">商品單價：NT$ 500
購買數量：2
折扣規則：消費滿 NT$ 1,000 享 9 折
　
預期小計：NT$ 1,000
預期折扣金額：NT$ 100
預期訂單總額：NT$ 900</code></pre>



<p class="wp-block-paragraph">除了測試資料，測試案例也必須明確定義前置條件（Precondition）。以前述折扣案例為例，執行測試前應確認：</p>



<ul class="wp-block-list">
<li>測試帳號可以正常使用。</li>



<li>使用者已完成登入，或測試案例包含登入步驟。</li>



<li>購物車處於空白狀態。</li>



<li>指定商品存在、庫存充足且可正常購買。</li>



<li>對應的折扣規則已啟用。</li>



<li>前一次測試未留下訂單、購物車或暫存資料。</li>
</ul>



<p class="wp-block-paragraph">測試資料決定「使用哪些數值進行驗證」，前置條件則決定「測試從什麼系統狀態開始」。兩者若未明確界定，即使 Playwright 腳本語法正確，測試結果仍可能因資料或環境狀態不同而不穩定。</p>



<p class="wp-block-paragraph">這類問題不應只透過固定等待、重試（Retry）或延長逾時時間（Timeout）加以掩蓋，而應先確認測試資料是否隔離，以及每次執行前後的系統狀態是否已正確建立與清理。</p>



<h2 class="wp-block-heading">從業務流程中裁適 UI Smoke Test 範圍</h2>



<p class="wp-block-paragraph">冒煙測試（Smoke Test）不是業務流程的同義詞，而是從既有測試情境與測試案例中，選出少量具代表性且可快速執行的基準測試，用於確認系統最重要的功能與使用者操作路徑仍可正常運作。</p>



<p class="wp-block-paragraph">以電子商務網站為例，購物主流程可整理為：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">登入
→ 搜尋商品
→ 查看商品詳情
→ 加入購物車
→ 進入結帳頁面
→ 送出訂單
→ 確認訂單成立</code></pre>



<p class="wp-block-paragraph">規劃第一組 UI Smoke Test 時，不需要同時涵蓋：</p>



<ul class="wp-block-list">
<li>登入密碼錯誤。</li>



<li>商品搜尋無結果。</li>



<li>輸入無效購買數量。</li>



<li>多種折扣規則組合。</li>



<li>商品庫存不足。</li>



<li>付款處理失敗。</li>



<li>訂單取消或退款。</li>
</ul>



<p class="wp-block-paragraph">這些情境可能同樣重要，但應依不同驗證目的，分別整理為其他測試案例。若將所有正常流程、例外條件與業務規則都納入同一支測試案例，測試範圍會逐漸擴張成龐大的端到端回歸流程，不僅執行時間增加，失敗原因也更難定位，因而失去 Smoke Test 快速檢查核心功能的作用。</p>



<p class="wp-block-paragraph">從業務流程中裁適 UI Smoke Test 範圍時，可依下列條件評估：</p>



<ol class="wp-block-list">
<li><strong>代表性</strong>：是否涵蓋系統最重要的使用者目標與核心操作路徑。</li>



<li><strong>可重複性</strong>：測試資料與前置狀態是否能在每次執行前穩定建立。</li>



<li><strong>可判定性</strong>：是否具備明確且可由程式自動驗證的預期結果。</li>



<li><strong>可定位性</strong>：測試失敗時，是否能快速判斷問題發生在哪一個操作階段。</li>



<li><strong>執行效率</strong>：是否能在合理時間內完成，並適合頻繁執行。</li>
</ol>



<p class="wp-block-paragraph">因此，UI Smoke Test 應視為測試套件（Test Suite）中一組高優先級的基準測試案例，而不是將完整業務流程直接縮寫成單一測試案例。</p>



<h2 class="wp-block-heading">明確界定測試意圖，避免 AI Agent 自行推測</h2>



<p class="wp-block-paragraph">AI Agent 可以協助操作瀏覽器、產生測試腳本與分析失敗原因，但不應自行決定測試範圍與業務規則。</p>



<p class="wp-block-paragraph">模糊要求：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">請測試購物流程是否正常。</code></pre>



<p class="wp-block-paragraph">Agent 可能自行選擇商品、忽略購物車前置狀態，或只確認頁面可以切換，卻沒有驗證金額與訂單結果。</p>



<p class="wp-block-paragraph">較合理的輸入應明確界定：</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">驗證目標：已登入使用者可以完成一次商品結帳。
　
前置條件：
- 使用固定測試帳號。
- 測試開始前購物車必須為空。
　
操作範圍：
- 搜尋指定關鍵字。
- 開啟第一筆符合條件的商品。
- 加入 2 件商品。
- 進入結帳頁並送出訂單。
　
驗證項目：
- 購物車數量為 2。
- 小計與預期計算一致。
- 結帳頁總額與購物車一致。
- 送出後顯示訂單成立訊息或訂單編號。
　
限制：
- 不修改產品功能。
- 不降低 Assertion 條件。
- 若實際畫面與描述不一致，先回報差異。</code></pre>



<p class="wp-block-paragraph">這種輸入讓 Agent 的責任集中在依據實際 UI 完成驗證，而不是替需求補寫規則。</p>



<h2 class="wp-block-heading">Playwright CLI、Skill 與 Playwright Test 的基本分工</h2>



<p class="wp-block-paragraph">Playwright CLI、Playwright Skill 與 Playwright Test 位於不同層次。三者不是互相取代，而是形成一個由觀察、規範到正式測試的流程。</p>


<pre class="language-text no-line-numbers"><code class="language-text no-line-numbers">Playwright CLI
→ 讓 AI Agent 操作瀏覽器與觀察 UI 狀態
　
Playwright Skill
→ 規範 AI Agent 如何正確使用 Playwright CLI
　
Playwright Test
→ 將使用者操作流程整理為可重複執行的測試案例</code></pre>



<p class="wp-block-paragraph">Playwright CLI 適合用來開啟頁面、取得 snapshot、執行操作與保留 screenshot。基本操作流程可以表示為：</p>


<pre class="language-powershell no-line-numbers"><code class="language-powershell no-line-numbers">playwright-cli open &lt;target-url&gt;
playwright-cli snapshot
playwright-cli screenshot
playwright-cli close</code></pre>



<p class="wp-block-paragraph">CLI 可協助確認：</p>



<ul class="wp-block-list">
<li>實際頁面有哪些可互動元素。</li>



<li>預期按鈕或欄位是否存在。</li>



<li>操作後畫面如何變化。</li>



<li>Locator 應根據哪些實際資訊建立。</li>
</ul>



<figure class="wp-block-image aligncenter size-large is-resized img-border"><img decoding="async" src="https://images.kenming.idv.tw/software/ui-automation-test/playwright-cli-open-url.webp" alt="" style="width:620px"/></figure>



<p class="has-text-align-center has-small-font-size wp-block-paragraph">圖：Playwright CLI 操作並取得頁面狀態</p>



<p class="wp-block-paragraph">需注意一次 CLI 操作不等於正式測試。它通常沒有完整 Test Case 定義，也不會自然形成可版本控管、可重複執行的回歸驗證。</p>



<h3 class="wp-block-heading">Playwright Skill：約束 Agent 的操作與回報</h3>



<p class="wp-block-paragraph">Playwright Skill 不會增加新的瀏覽器 API，也不會取代 Playwright Test。它主要用來規範 Agent：</p>



<ul class="wp-block-list">
<li>必須實際開啟頁面。</li>



<li>先取得頁面狀態，再決定操作方式。</li>



<li>回報實際執行步驟與觀察結果。</li>



<li>必要時保留 snapshot 或 screenshot。</li>



<li>畫面與需求不一致時，停止猜測並回報差異。</li>
</ul>



<figure class="wp-block-image aligncenter size-large is-resized img-border"><img decoding="async" src="https://images.kenming.idv.tw/software/ui-automation-test/agent-playwright-skill-verification.webp" alt="" style="width:620px"/></figure>



<p class="has-text-align-center has-small-font-size wp-block-paragraph">圖：AI Agent 依 Playwright Skill 執行頁面驗證並回報結果</p>



<p class="wp-block-paragraph">Skill 的價值，是降低 Agent 未實際操作瀏覽器就直接推測 UI 狀態的風險。</p>



<h3 class="wp-block-heading">Playwright Test：UI 自動化測試框架</h3>



<p class="wp-block-paragraph">Playwright Test 是 Playwright 提供的測試框架與測試執行器（Test Runner），用於撰寫、執行及管理可重複的 UI 自動化測試案例。</p>



<p class="wp-block-paragraph">它主要負責：</p>



<ul class="wp-block-list">
<li>組織與執行測試案例。</li>



<li>管理瀏覽器生命週期與測試隔離。</li>



<li>執行操作步驟與斷言。</li>



<li>產出測試結果、HTML Report、Trace 與 Screenshot。</li>
</ul>



<p class="wp-block-paragraph">相較於 Playwright CLI 主要用於操作與觀察瀏覽器，Playwright Test 則負責承載正式的測試腳本，並提供一致、可重複的測試執行與結果判定機制。</p>



<h2 class="wp-block-heading">頁面快照、畫面截圖、HTML 測試報告與追蹤記錄</h2>



<p class="wp-block-paragraph">UI 測試不只需要執行操作，也需要留下可供核對的測試證據（Evidence）。不同類型的測試證據，適合用於判斷不同問題：</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>測試證據</th><th>主要用途</th></tr></thead><tbody><tr><td>頁面快照（Snapshot）</td><td>確認頁面文字、結構與可互動元素</td></tr><tr><td>畫面截圖（Screenshot）</td><td>確認實際視覺狀態、版面配置與錯誤訊息</td></tr><tr><td>HTML 測試報告（HTML Report）</td><td>彙整測試通過、失敗、錯誤訊息與相關附件</td></tr><tr><td>追蹤記錄（Trace）</td><td>還原失敗前後的操作、文件物件模型（DOM）、網路請求與頁面狀態</td></tr></tbody></table></figure>



<figure class="wp-block-image aligncenter size-large is-resized img-border"><img decoding="async" src="https://images.kenming.idv.tw/software/ui-automation-test/playwright-html-report.webp" alt="" style="width:620px"/></figure>



<p class="has-text-align-center has-small-font-size wp-block-paragraph">圖：Playwright HTML 測試報告集中呈現測試結果</p>



<p class="wp-block-paragraph">例如：</p>



<ul class="wp-block-list">
<li>確認按鈕是否存在，可先檢查頁面快照。</li>



<li>判斷按鈕是否被其他元素遮蔽，需要查看畫面截圖。</li>



<li>確認哪一支測試案例執行失敗，可以從 HTML 測試報告中檢查。</li>



<li>分析點擊後未跳轉的原因，通常需要透過追蹤記錄還原操作過程。</li>
</ul>



<p class="wp-block-paragraph">測試證據的目的不是增加附件數量，而是讓測試結果能夠被判讀、重現與分類。</p>



<h2 class="wp-block-heading">從一次性 UI 操作轉為可重複執行的測試</h2>



<p class="wp-block-paragraph">將業務流程轉換為 Playwright 測試，可以依下列順序進行：</p>



<ol class="wp-block-list">
<li>確認 Business Process 與主要業務活動。</li>



<li>選定單一角色的 Operation Procedure。</li>



<li>定義 Test Scenario、Test Suite 與 Test Case。</li>



<li>固定測試資料、帳號與前置狀態。</li>



<li>使用 Playwright CLI 或 Agent 操作實際頁面。</li>



<li>根據 snapshot 與 screenshot 核對 UI 狀態。</li>



<li>修正錯誤的流程假設與 Locator 判斷。</li>



<li>將穩定操作整理為 Playwright Test。</li>



<li>執行測試並檢查 HTML Report 或 Trace。</li>



<li>保留為可重複執行的基準驗證。</li>
</ol>



<p class="wp-block-paragraph">這個順序比直接要求 Agent 生成完整測試更可靠。Agent 先接觸實際頁面，再根據已界定的測試意圖撰寫腳本，可以降低錯誤 Locator、模糊 Assertion 與錯誤流程假設。</p>



<h2 class="wp-block-heading">常見問題</h2>



<h3 class="wp-block-heading">將完整業務流程寫成單一測試案例</h3>



<p class="wp-block-paragraph">跨角色、跨系統或跨狀態的業務流程應分段驗證。若將完整流程寫成單一測試案例，任何中間步驟失敗，都可能使後續驗證失去意義，也會增加問題定位的難度。</p>



<h3 class="wp-block-heading">將測試情境視為測試案例</h3>



<p class="wp-block-paragraph">「使用者可以完成結帳」只描述測試情境（Test Scenario），尚未明確定義前置條件、測試資料、操作步驟與預期結果，因此不能直接作為可執行的測試案例（Test Case）。</p>



<h3 class="wp-block-heading">為了讓測試通過而降低斷言要求</h3>



<p class="wp-block-paragraph">例如，原本應驗證精確的折扣金額與訂單總額，最後卻只確認頁面出現「結帳成功」。測試雖然通過，卻未實際保護重要的業務規則。</p>



<p class="wp-block-paragraph">調整斷言（Assertion）時，應確認驗證條件是否仍符合原始測試意圖，而不是單純降低通過門檻。</p>



<h3 class="wp-block-heading">依靠固定等待掩蓋狀態問題</h3>



<p class="wp-block-paragraph">大量使用固定秒數等待，通常表示測試沒有依據實際 UI 狀態進行同步，或測試資料與前置狀態不穩定。</p>



<p class="wp-block-paragraph">應優先依據實際 UI 狀態進行非同步等待，例如等待具體元素、文字、網址或網路狀態符合預期，而不是使用固定秒數等待。</p>



<h3 class="wp-block-heading">將一次性的 AI Agent 操作視為回歸測試</h3>



<p class="wp-block-paragraph">AI Agent 成功完成一次操作，只能證明該流程在當時的環境與資料狀態下可能可用，不能直接視為可重複執行的回歸測試（Regression Testing）。</p>



<p class="wp-block-paragraph">若要形成正式驗證，仍需將操作流程整理為明確的測試案例，並使用 Playwright Test 撰寫成具備固定資料、操作步驟、斷言與執行結果的自動化測試腳本。</p>



<h3 class="wp-block-heading">在初期一次納入過多測試情境</h3>



<p class="wp-block-paragraph">初期應先建立一組穩定且具代表性的主要流程測試，再逐步增加錯誤情境、邊界值與替代操作路徑。</p>



<p class="wp-block-paragraph">相較於一次建構所有分支，這種漸進式作法更容易驗證測試設計、控制執行範圍及維持後續可維護性。</p>



<h2 class="wp-block-heading">結語</h2>



<p class="wp-block-paragraph">Playwright UI 自動化測試的起點，不是直接使用瀏覽器操作介面，而是先將業務流程轉換為明確、可執行且可驗證的測試案例。</p>



<p class="wp-block-paragraph">業務流程（Business Process）用於理解完整的作業脈絡；業務活動（Activity）與操作程序（Operation Procedure）則用於找出可由單一角色執行的操作範圍。測試情境（Test Scenario）描述需要驗證的使用者目標，測試套件（Test Suite）負責組織相關測試案例，測試案例（Test Case）則明確定義前置條件、測試資料、操作步驟與預期結果。UI 冒煙測試（UI Smoke Test）是從這些測試案例中選出的高優先級基準測試，而不是完整業務流程的替代名稱。</p>



<p class="wp-block-paragraph">進入實際操作後，Playwright CLI 可用於操作瀏覽器並觀察頁面狀態；Playwright Skill 用於規範人工智慧代理（AI Agent）的操作方式與結果回報；Playwright Test 則是 Playwright 提供的測試框架與測試執行器，用於撰寫、執行及管理可重複的 UI 自動化測試。頁面快照（Snapshot）、畫面截圖（Screenshot）、HTML 測試報告（HTML Report）與追蹤記錄（Trace），則提供結果判定與失敗診斷所需的測試證據（Evidence）。</p>



<p class="wp-block-paragraph">只有先界定流程層級、測試範圍與各項工具的責任，定位器（Locator）、使用者操作（User Actions）與斷言（Assertions），才能共同形成可執行、可觀察且可維護的 UI 自動化測試。</p>



<h2 class="wp-block-heading">延伸閱讀</h2>



<ul class="wp-block-list">
<li>Playwright CLI + Skills：<a href="https://github.com/microsoft/playwright-cli" target="_blank" rel="noopener">https://github.com/microsoft/playwright-cli</a></li>



<li>Playwright Test CLI：<a href="https://playwright.dev/docs/test-cli" target="_blank" rel="noopener">https://playwright.dev/docs/test-cli</a></li>



<li>Playwright for VS Code：<a href="https://playwright.dev/docs/getting-started-vscode" target="_blank" rel="noopener">https://playwright.dev/docs/getting-started-vscode</a></li>
</ul>
<p>〈<a rel="nofollow" href="https://kenming.idv.tw/business-process-to-playwright-ui-automation-testing/">從業務流程到測試案例：Playwright 基礎操作與實務應用</a>〉這篇文章最早發佈於《<a rel="nofollow" href="https://kenming.idv.tw">Kenmingの鮮思維</a>》。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://kenming.idv.tw/business-process-to-playwright-ui-automation-testing/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<media:content url="https://images.kenming.idv.tw/software/ui-automation-test/business-to-playwright-automation-test.webp" medium="image"></media:content>
	</item>
	</channel>
</rss>
