Members
(constant) BOX_PAD
- Description:
把紅框以sharp疊到截圖buffer上(截圖後合成,不注入DOM),標注本張標準圖之觀看或操作區
幾何:目標區外擴pad(預設6),四邊夾在bounds內並留margin(預設3);SVG之stroke置中於路徑, 故外緣落在夾邊後之矩形上,等效DOM版border-box之5px內縮框線。框過小(寬或高不足stroke)時預設不畫、回傳原buffer(guardSmall=false則照畫,重現無此守門之專案)。 why不注入DOM:插入後又移除之暫時DOM偶發使整頁光柵化偏1px,量測工具不得改動被測頁
- Source:
把紅框以sharp疊到截圖buffer上(截圖後合成,不注入DOM),標注本張標準圖之觀看或操作區
幾何:目標區外擴pad(預設6),四邊夾在bounds內並留margin(預設3);SVG之stroke置中於路徑, 故外緣落在夾邊後之矩形上,等效DOM版border-box之5px內縮框線。框過小(寬或高不足stroke)時預設不畫、回傳原buffer(guardSmall=false則照畫,重現無此守門之專案)。 why不注入DOM:插入後又移除之暫時DOM偶發使整頁光柵化偏1px,量測工具不得改動被測頁
Example
import composeBox from 'w-package-tools-e2e/src/composeBox.mjs'
buf = await composeBox(buf, { left: 100, top: 50, right: 300, bottom: 120 })
(constant) INK_PAD
- Description:
文字墨跡之紅框留白:紅框幾何為目標 ±6 且 5px 描邊內縮,目標矩形緊貼文字墨跡時框內僅剩約 1px(紅框壓字)。 墨跡外擴 INK_PAD(4px)後框內留白約 5px,與自帶內距之按鈕相當(2026-09-28)。 itemsUnionBox 之 fit 以同一常數外擴 DOM 文字墨跡;本函數供「無 DOM 之緊貼墨跡矩形」(canvas 圖例列、翻頁箭頭、圖例項等, 由圖表程式庫回報之視窗座標矩形)使用,交給 captureStableWithBox 作為矩形目標。 neighbors(2026-09-28):同一列之其他項目矩形(例:其他圖例項);外擴後框線不越過與鄰項間隙之正中,不壓到鄰項(業主:亂框亂壓遮蔽有效資訊即缺陷)。 矩形須為「實際墨跡」:圖表程式庫回報之文字/項目矩形常比畫出之字形寬數 px,以之取中點框線會偏向鄰項——先經 canvasInkRects 收斂; 鄰項須含畫面上有墨跡之全部項目(被捲動窗裁掉一部分者亦算,canvasInkRects 只回其可見部分),不可只取完整顯示者 (w-web-perm 統計圖例項殷鑑:兩者皆未做時,框線距自身文字 7px、貼下一項圖示 0px)。
- Source:
文字墨跡之紅框留白:紅框幾何為目標 ±6 且 5px 描邊內縮,目標矩形緊貼文字墨跡時框內僅剩約 1px(紅框壓字)。 墨跡外擴 INK_PAD(4px)後框內留白約 5px,與自帶內距之按鈕相當(2026-09-28)。 itemsUnionBox 之 fit 以同一常數外擴 DOM 文字墨跡;本函數供「無 DOM 之緊貼墨跡矩形」(canvas 圖例列、翻頁箭頭、圖例項等, 由圖表程式庫回報之視窗座標矩形)使用,交給 captureStableWithBox 作為矩形目標。 neighbors(2026-09-28):同一列之其他項目矩形(例:其他圖例項);外擴後框線不越過與鄰項間隙之正中,不壓到鄰項(業主:亂框亂壓遮蔽有效資訊即缺陷)。 矩形須為「實際墨跡」:圖表程式庫回報之文字/項目矩形常比畫出之字形寬數 px,以之取中點框線會偏向鄰項——先經 canvasInkRects 收斂; 鄰項須含畫面上有墨跡之全部項目(被捲動窗裁掉一部分者亦算,canvasInkRects 只回其可見部分),不可只取完整顯示者 (w-web-perm 統計圖例項殷鑑:兩者皆未做時,框線距自身文字 7px、貼下一項圖示 0px)。
Example
import inkRect from 'w-package-tools-e2e/src/inkRect.mjs'
let buf = await captureStableWithBox(page, inkRect(legend.rect))
let buf2 = await captureStableWithBox(page, inkRect(item.rect, { neighbors: others.map((o) => o.rect) }))
WPackageToolsE2e
- Description:
e2e 測試與標準圖(pixel baseline)共用設施
- Source:
e2e 測試與標準圖(pixel baseline)共用設施
Example
import wpte from 'w-package-tools-e2e/src/WPackageToolsE2e.mjs'
let browser = await wpte.launchBrowser()
let page = await wpte.openCasePage(browser)
await page.goto('http://127.0.0.1:8080')
let buf = await wpte.captureStableWithBox(page, '#btn')
wpte.assertBaselineMatch(buf, './test/pics/demo/demo-eng-E2E-001-click-btn.png', 'demo-eng-E2E-001-click-btn')
await browser.close()
其餘詳見各模組範例
chromiumLaunchArgs
- Description:
確定性渲染之Chromium啟動旗標(六旗標),供像素比對之截圖使用
headless Chromium預設GPU光柵化與次像素字形反鋸齒,於像素比對下非決定性(拉丁字偶發散落差異,CJK灰階反鋸齒較穩故常只英文語系出錯)。 本組關閉GPU(改走CPU Skia)、固定sRGB色彩、關閉LCD與次像素字形定位、關閉Skia執行期最佳化與部分光柵化, 四個web專案實測截圖自我一致(同態連拍)由裸啟動之2/4提升為5/5。旗標組改動即全部標準圖須重產
陣列已凍結,需增加旗標時由launchBrowser之opt.args附加
- Source:
確定性渲染之Chromium啟動旗標(六旗標),供像素比對之截圖使用
headless Chromium預設GPU光柵化與次像素字形反鋸齒,於像素比對下非決定性(拉丁字偶發散落差異,CJK灰階反鋸齒較穩故常只英文語系出錯)。 本組關閉GPU(改走CPU Skia)、固定sRGB色彩、關閉LCD與次像素字形定位、關閉Skia執行期最佳化與部分光柵化, 四個web專案實測截圖自我一致(同態連拍)由裸啟動之2/4提升為5/5。旗標組改動即全部標準圖須重產
陣列已凍結,需增加旗標時由launchBrowser之opt.args附加
Example
import chromiumLaunchArgs from 'w-package-tools-e2e/src/chromiumLaunchArgs.mjs'
console.log(chromiumLaunchArgs)
// => ['--disable-gpu', '--force-color-profile=srgb', '--disable-lcd-text', '--disable-font-subpixel-positioning', '--disable-skia-runtime-opts', '--disable-partial-raster']
Methods
assertBaselineMatch(buf, baselinePath, labelopt, optopt) → {undefined}
- Description:
截圖與標準圖比對(反鋸齒感知之像素比對加容差),不一致時保留三聯組證據後拋錯
比對:pixelmatch之includeAA:false自動忽略反鋸齒邊緣像素,threshold預設0.1,真不同像素數超過maxDiffPixels(預設100)即失敗; 尺寸不同必為真差異,直接失敗;標準圖不存在直接拋錯。像素層為補強,呼叫前每案仍須先有語意斷言。 證據:失敗時把當次截圖、標準圖與差異圖存到pendingDir(預設'./testPending'),檔名為
<label>__<毫秒時間戳>__{capture,baseline,diff}.png,撞名加-N,永不覆蓋、不自動刪除。 餘裕告警:通過但真不同像素數超過maxDiffPixels之headroomRatio(預設0.5)時印告警——計數貼近上限之標準圖為零餘裕之flake源(2026-09-28 實測兩張恰為100),應重產或查因。 本函數只比對不寫檔:標準圖之寫檔一律經runBaselineCase(全部斷言通過後才寫);原opt.regen寫檔旁路(第二條寫檔路徑、先寫圖後斷言)已於2026-09-28移除。本函數為同步函數
- Source:
Example
import assertBaselineMatch from 'w-package-tools-e2e/src/assertBaselineMatch.mjs'
assertBaselineMatch(buf, './test/pics/tokens/tokens-eng-E2E-001-list-loaded.png', 'tokens-eng-E2E-001-list-loaded')
Parameters:
| Name | Type | Attributes | Default | Description | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
buf |
Buffer | 輸入當次截圖PNG之Buffer |
||||||||||||||||||||||||||||||||
baselinePath |
String | 輸入標準圖路徑字串 |
||||||||||||||||||||||||||||||||
label |
String |
<optional> |
''
|
輸入證據檔名用之可讀標籤字串,預設''代表使用標準圖檔名 |
||||||||||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
通過時無回傳,不一致時拋出錯誤
- Type
- undefined
(async) assertTextSpec(page, spec, optopt) → {Promise}
- Description:
以spec衍生之期望文字斷言頁面:mode為'text'時頁面須含value,'absentText'時頁面不得含value;不符即拋出斷言錯誤並附頁面文字供診斷
期望文字應自spec與語系檔衍生(可觀察之訊息原文),不可取自現狀;像素比對只是補強層,每案仍須先有此類語意斷言
- Source:
Example
import assertTextSpec from 'w-package-tools-e2e/src/assertTextSpec.mjs'
await assertTextSpec(page, { mode: 'text', value: 'Save tokens successfully' }, { label: 'E2E-002' })
Parameters:
| Name | Type | Attributes | Default | Description | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
page |
Page | 輸入Playwright之Page |
||||||||||||
spec |
Object | 輸入期望物件{mode,value},mode為'text'或'absentText' |
||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,符合時resolve,不符時reject斷言錯誤
- Type
- Promise
(async) canvasInkRects(page, rects, optopt) → {Promise.<Array>}
- Description:
畫布(canvas)目標之實際墨跡範圍:逐矩形讀其所在 canvas 之像素,回傳矩形內實際有墨跡之最小外接矩形(視窗座標)
圖表程式庫(echarts/zrender 等)回報之文字、圖例項矩形常比畫出之字形寬數 px(字寬量測與實際繪字之差、項目群組含互動熱區); 以之交 inkRect 外擴留白或取與鄰項間隙之中點,框線會偏向一側而貼到鄰項(2026-09-28 w-web-perm 統計圖例項殷鑑: 框線距自身文字 7px、貼下一項圖示 0px)。先以本函數收斂到實際墨跡再交 inkRect。 被捲動窗裁掉一部分之項目只回其可見墨跡、完全未繪製者回 null——同一列之鄰項應取「畫面上有墨跡者」, 不可只取完整顯示者(否則緊鄰之半顯示項不算鄰項,框線照常外擴而貼上去;同一殷鑑)。 canvas 預設以各矩形中心點命中測試取得(該點最上層之 canvas),亦可由 opt.canvas 指定(選擇器字串或 Locator)。 墨跡判準:未繪製處須為透明(圖表未設不透明背景色),以 alpha 大於門檻判定;背景不透明者以 opt.bg 給背景色,改以色差判定。
- Source:
Example
import canvasInkRects from 'w-package-tools-e2e/src/canvasInkRects.mjs'
let inks = await canvasInkRects(page, legend.items.map((o) => o.whole))
let target = inks[0]
let buf = await captureStableWithBox(page, inkRect(target, { neighbors: inks.slice(1).filter(Boolean) }))
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
page |
Object | 輸入Playwright Page |
|||||||||||||||||||||||||||
rects |
Array | 輸入視窗座標矩形陣列,每個為{x,y,width,height} |
|||||||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳與rects同序之矩形陣列,矩形內無墨跡、找不到canvas或canvas非2d者為null
- Type
- Promise.<Array>
(async) captureStable(page, optopt) → {Promise}
- Description:
標準圖截圖之唯一入口:先排除「還沒穩定」與「穩定到錯的狀態」兩類成因,再連拍至相鄰兩張完全相同才回傳
順序(不可調換):滑鼠移到(0,0)消除hover → 初始等待(涵蓋setTimeout延遲顯示與hover離開動畫,animations:'disabled'不快轉setTimeout) → settle掛鉤(元件狀態機優先,例如WDrawer之waitColResizeOverlay、waitDrawerReady) → 凍結inline SVG之SMIL動畫 → 等字型就緒 → beforeShots掛鉤(例如ag-grid水平捲動歸零) → 偵測
內含
之SVG區域(截圖後貼上該SVG去掉動畫元素之靜態影格,兩端同區同圖) → 連拍:每張先遮罩再與前一張比對位元組,相同即回傳;重試用盡仍不同時,strict拋錯(產製端拒絕寫入未穩定畫面),否則回傳最後一張交由比對揭露真實flake。 strict之決定:opt.strict為布林值時依之;否則呼叫strictDefault()(於每次呼叫時求值,預設讀環境變數E2E_STRICT_CAPTURE為'1')。 動畫區座標須與截圖之座標系一致:全頁截圖為頁面座標(視窗座標加捲動量)、視窗截圖為視窗座標,給clip時再減去clip原點 (smilRectBasis預設'auto'即依shotOpts自動決定)。2026-09-28前四個姊妹專案之全頁截圖皆用視窗座標,頁面捲動時遮罩落錯位置、動畫露出, 且Chrome暫停視窗外之
動畫而使連拍「穩定」於不固定影格。smilRectBasis設'viewport'可重現舊行為(僅供等價對照,不建議)
動畫區之處理(imgSmilFill,2026-09-28):'static'(預設)先以區外左側像素色填底,再貼上以sharp算繪之「去掉動畫元素之SVG」靜態影格—— 手冊用圖看得到真實圖示(技能 §8.2「該讓讀者看到的畫面不填黑」,原填黑使連線中畫面之轉圈成黑方塊),且輸入固定故輸出決定; 區域超出截圖邊界者退回填黑。'black'為2026-09-28前之行為(僅供等價對照)。
- Source:
Example
import captureStable from 'w-package-tools-e2e/src/captureStable.mjs'
import waitColResizeOverlay from 'w-package-tools-e2e/src/waitColResizeOverlay.mjs'
import waitDrawerReady from 'w-package-tools-e2e/src/waitDrawerReady.mjs'
let buf = await captureStable(page, { settle: [waitColResizeOverlay, waitDrawerReady] })
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
page |
Page | 輸入Playwright之Page |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,resolve回傳PNG之Buffer
- Type
- Promise
(async) captureStableWithBox(page, target, optopt) → {Promise}
- Description:
全頁穩定截圖並以紅框標注本張之觀看或操作區(截圖後以sharp合成,不注入DOM),可另遮黑非決定性區域
target:CSS選擇器字串、Playwright Locator、視窗座標矩形{x,y,width,height}、量測型目標{scroll,measure,label},或以上混合之陣列(多個取聯集畫「一個」框)。 量測型目標:measure(page)回傳視窗矩形、矩形陣列或null,於「捲入與等待之後」呼叫(故量到的是截圖當下之版面);scroll為捲入用之選擇器或Locator(可省略); probe為被蓋住檢查用之選擇器或Locator(可省略,省略者不做被蓋住檢查)。 框對象須由頁面內容決定(例如表格之標頭與可見資料列、清單之可見項目)時用之,見gridContentBox、itemsUnionBox。 流程:第一個非矩形目標先捲入視窗(逾時不中斷) → 等待preWaitMs並把滑鼠移到(0,0) → 量各目標與遮罩之視窗矩形及捲動量(皆在截圖「之前」量) → capture(預設captureStable,其餘opt原樣透傳,例如strict、settle) → 遮罩(先) → 紅框(後,框永遠可見)。 mask:選擇器字串,或{sel,fixedWidth}(自元素右緣往左取固定寬,數字位數變動時黑塊邊界不浮動)。 clampTo:'buffer'(框夾在整張截圖內)或'viewport'(框夾在截圖當下之視窗內,全頁截圖時加捲動量) 靜默無框防護(2026-09-28):目標全數找不到或量得尺寸為 0 時於截圖前拋錯;框夾邊後過小(composeBox 將不畫)時於合成後拋錯—— 標準圖每張皆須有框(技能 §7.1),原「找不到即回無框圖」會產出永遠比對通過之無框標準圖(SSO stainfor E2E-002 殷鑑)。 確屬設計如此者(極少)須顯式傳 allowNoBox:true。 被蓋住檢查(2026-09-28):選擇器/Locator 目標(與量測型目標之 probe)之中心點(夾在視窗內)以 elementFromPoint 取最上層元素, 若非目標本身、其子孫或祖先(例如彈窗遮罩、蓋在上面之面板)即視為「框了被蓋住的東西」——技能 §7.3-4 規定改框蓋在上面者。 coverCheck:'throw'(預設,可由環境變數 E2E_COVER_CHECK 覆寫)、'warn'(印 [box-covered] 告警,供全量盤查)、'off'(確屬設計者)。
- Source:
Example
import captureStableWithBox from 'w-package-tools-e2e/src/captureStableWithBox.mjs'
let buf = await captureStableWithBox(page, '.ag-root-wrapper')
let buf2 = await captureStableWithBox(page, [page.locator('.ag-row').filter({ hasText: '帳號' })], { mask: [{ sel: '.clock', fixedWidth: 80 }] })
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
page |
Page | 輸入Playwright之Page |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||
target |
String | Object | Array | 輸入框選目標 |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{},除下列鍵外皆透傳給capture Properties
|
Returns:
回傳Promise,resolve回傳PNG之Buffer
- Type
- Promise
(async) collectDomText(page, optopt) → {Promise}
- Description:
收集頁面DOM之文字節點內容(略過SCRIPT、STYLE、NOSCRIPT),以分隔字串串接,供語意斷言失敗時印出頁面內容
注意:只走訪DOM文字節點,不判斷元素是否可見(隱藏元素之文字亦會收入);每個文字節點先trim,空白者略過
- Source:
Example
import collectDomText from 'w-package-tools-e2e/src/collectDomText.mjs'
console.log(await collectDomText(page))
// => 'Tokens | 金鑰清單 | test-token-1 | ...'
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
page |
Page | 輸入Playwright之Page |
|||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,resolve回傳串接後之字串
- Type
- Promise
compareImageDirs(dirA, dirB, optopt) → {Object}
- Description:
逐檔比對兩個目錄內同名之PNG:位元組是否相同、解碼後RGBA是否相同、反鋸齒感知之像素差異數
用途:行為等價證明——把新管線之產製輸出導到暫存目錄(dirA),與現行標準圖目錄(dirB)逐檔比對,不動標準圖本身。 判準分層:bytesEqual(檔案位元組相同,受PNG編碼器與版本影響) ⊂ rgbaEqual(像素完全相同) ⊂ withinTolerance(pixelmatch差異數≤maxDiffPixels)。 等價層(2026-09-28):equivalent=rgbaEqual,或RGBA相異之像素全數落在opt.drift登錄之漂移點(座標+各通道差上限)內—— 不以「pixelmatch計數0」為等價:threshold 0.1時整片均勻亮度差在約26級以內之像素一律不計,會掩蓋肉眼可見之全域變化。 各檔另報rgbaDiff(RGBA相異像素數)與rgbaDiffBox(其外接框),供歸因。 以dirA之檔案為準逐一比對;只存在於dirB者列於onlyInB(部分產製時屬正常,由呼叫端判斷)
- Source:
Example
import compareImageDirs from 'w-package-tools-e2e/src/compareImageDirs.mjs'
let r = compareImageDirs('./test/_tmp/regen-out', './test/pics/tokens')
console.log(r.summary)
// => { total: 50, bytesEqual: 50, rgbaEqual: 50, equivalent: 50, withinTolerance: 50, exceeded: 0, missingInB: 0, sizeMismatch: 0, onlyInB: 0 }
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
dirA |
String | 輸入受檢目錄字串(例如產製輸出之暫存目錄) |
|||||||||||||||||||||||||||
dirB |
String | 輸入對照目錄字串(例如現行標準圖目錄) |
|||||||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳物件{files,onlyInB,summary},files為各檔結果陣列[{file,status,bytesEqual,rgbaEqual,rgbaDiff,rgbaDiffBox,equivalent,numDiff,withinTolerance}],status為'ok'、'missingInB'或'sizeMismatch'
- Type
- Object
createBaselineGate(opt) → {Object}
- Description:
建立標準圖產製之篩選器:解析--names、--langs、--write-mode與E2E_BASELINE_OUT_DIR,於「截圖之前」決定要跑哪些案例、寫哪些階段圖
案例宣告:cases為[{name,stages,compareOnly,langs}],name為案例鍵;stages為該案產出之階段圖鍵陣列(可省略,省略時單張案例之圖鍵即name, 多階段案例則於執行後以編號前綴比對);compareOnly為true代表只比對不產圖(共用他案標準圖);langs限定語系(預設全部)。
--names 每項之解析順序(每項可帶
<語系>-前綴限定語系,不帶則套用全部已選語系): ①完全等於某案例宣告之階段圖鍵 → 只寫該張(案例鍵與首張階段圖鍵同名時亦只寫該張); ②完全等於案例鍵,或為案例鍵之邊界前綴(E2E-005命中E2E-005-xxx,不命中E2E-0051-xxx) → 寫該案全部階段; ③為已宣告階段圖鍵之邊界前綴 → 寫命中之各張; ④案例未宣告stages而編號相符 → 執行該案,只寫圖鍵等於該項或以「該項-」開頭者,執行後未產出即拋錯(不靜默略過); 命中compareOnly案例、或不符任何案例與圖鍵者一律拋錯並列出可用鍵。--langs須完全等於已宣告語系。 未篩選(全部案例)時casesFor含只比對案例:產製端照跑其流程與語意斷言、不寫圖(runBaselineCase於regen遇compareOnly即不寫), 與mocha REGEN型專案一致(2026-09-28前直跑型專案之產製端從不執行只比對案例)。 孤兒檢查:給pathOf(或由runBaselineCase經usePathOf告知)時,finalize()以findOrphanBaselines靜態比對宣告圖鍵與標準圖目錄,有孤兒即拋錯並列出。 --write-mode:all(預設,全寫)、missing(只寫標準圖不存在者,追加案例用)、changed(只寫與現行標準圖差異超過容差或尺寸不同者,避免重寫已審過之圖)。 E2E_BASELINE_OUT_DIR:有值時寫檔改導至該目錄(等價驗證用),比對端讀取之標準圖路徑不受影響
- Source:
Example
import createBaselineGate from 'w-package-tools-e2e/src/createBaselineGate.mjs'
let gate = createBaselineGate({
langs: ['eng', 'cht'],
cases: [
{ name: 'E2E-001-list-loaded' },
{ name: 'E2E-005-grant-perms-save-success', stages: ['E2E-005-1-click-perms', 'E2E-005-2-perms-list'] },
],
})
console.log(gate.describe())
for (let lang of gate.langs) {
for (let c of gate.casesFor(lang)) {
//...執行案例, 每張圖以 gate.shouldWrite(lang, c.name, key) 決定是否寫檔...
}
}
gate.finalize()
Parameters:
| Name | Type | Description | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opt |
Object | 輸入設定物件 Properties
|
Returns:
回傳篩選器物件{filtered,langs,writeMode,outDir,casesFor(lang),shouldWrite(lang,caseName,stageKey),noteProduced(lang,caseName,stageKey),decideWrite(buf,baselinePath),outPath(baselinePath),usePathOf(fn),orphans(),finalize(),describe()}
- Type
- Object
createKnownDefect(message, optopt) → {Error}
- Description:
建立「已知缺陷」錯誤:案例走到已登錄於spec〈已知落差〉之產品缺陷徵狀時拋出, 產製端跳過該案例不寫圖(壞掉的畫面不得凍結為標準圖),比對端交由測試框架標為pending
錯誤物件帶knownDefect:true與ref(〈已知落差〉之條目識別);runBaselineCase據此分流
- Source:
Example
import createKnownDefect from 'w-package-tools-e2e/src/createKnownDefect.mjs'
if (await page.locator('.webpack-dev-server-client-overlay').count() > 0) {
throw createKnownDefect('儲存後出現未捕捉例外覆蓋層', { ref: 'spec/流程_xxx.md 已知落差 2026-09-27' })
}
Parameters:
| Name | Type | Attributes | Default | Description | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
message |
String | 輸入徵狀描述字串 |
||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳帶knownDefect標記之錯誤物件
- Type
- Error
createServiceManager(opt) → {Object}
- Description:
建立e2e服務生命週期管理器(沿用政策reuse):port已有服務即沿用(不spawn也不負責關),沒有才spawn並等就緒;只殺自己spawn的
startServersOnce:依services順序逐一處理;每個服務各自一個一次性狀態(進行中之呼叫共用同一個promise)。 失敗語意failureMode:'once'(預設,首次呼叫拋錯,之後之呼叫不再重試也不再拋錯;沿用w-web-sso現行語意)或'sticky'(之後之呼叫持續拋出同一錯誤,直到cleanup)。 restart:有自建行程者先同步殺整棵樹並等port釋放(有上限);等不到釋放、或無自建行程而port被佔用者,killForeignOnRestart為true才以killPortListeners殺監聽者 (只可用於該port專屬本專案且已於專案映射表明文登錄之情形),否則拋錯(自建殺後仍被佔者只記錄並繼續,沿用前版語意);之後以args與env(與process.env淺合併,只作用於本次)spawn並等就緒。restart不改變一次性狀態。 服務可給beforeSpawn掛鉤(async({phase,args,env,hookArg})):每次spawn之前呼叫並等待,phase為'start'(startServersOnce確定要spawn時)或'restart', hookArg為restart之opt.hookArg(例如{reseed:true});用於須在服務啟動前完成之前置(例如資料庫須在後端開啟前重建)。 cleanup:同步(exit與訊號處理器內非同步不會被等待);依services反序殺自建行程、重置所有一次性狀態(使之後之startServersOnce重新偵測)、呼叫onCleanup。 另有政策own(只用自建實例、port被佔即報錯、跨行程互斥)未於本版提供,參考w-web-task之test/tools/harnessLifecycle.mjs
- Source:
Example
import { spawn } from 'child_process'
import createServiceManager from 'w-package-tools-e2e/src/createServiceManager.mjs'
let sm = createServiceManager({
services: [
{ name: 'backend', port: 11007, readyTimeoutMs: 30000, spawn: ({ args, env }) => spawn('node', ['srv.mjs', ...args], { stdio: 'ignore', env }) },
{ name: 'frontend', port: 8080, readyTimeoutMs: 90000, spawn: () => spawn('npm', ['run', 'serve'], { stdio: 'ignore', shell: true }) },
],
killForeignOnRestart: true,
})
await sm.startServersOnce()
await sm.restart('backend', { args: ['./test/_tmp/settings-e2e.json'] })
sm.cleanup()
Parameters:
| Name | Type | Description | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opt |
Object | 輸入設定物件 Properties
|
Returns:
回傳物件{startServersOnce({only}),restart(name,{args,env,hookArg}),cleanup(),state(name)}
- Type
- Object
createTempSettings(opt) → {Object}
- Description:
建立臨時設定檔之產生器:以基底設定(JSON5)淺合併overrides後寫出純JSON,供e2e以不同設定重啟後端
落點須為測試中介資料目錄(例如test/_tmp,gitignore),絕不可為AI暫存區./tmp(隨時被整個清除,後端讀不到設定即假失敗)。 檔名為
settings-e2e-<pid>-<序號>.json;cleanupTempSettings逐一刪除本產生器建立之檔案,目錄空了再刪目錄。 forbiddenKeys內之鍵不可覆寫(例如測試實例固定之port與log目錄),覆寫即拋錯
- Source:
Example
import createTempSettings from 'w-package-tools-e2e/src/createTempSettings.mjs'
let { genTempSettings, cleanupTempSettings } = createTempSettings({ basePath: './settings.json', tmpDir: './test/_tmp' })
let p = genTempSettings({ allowUserRegistration: false })
//...以p重啟後端...
cleanupTempSettings()
Parameters:
| Name | Type | Description | |||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opt |
Object | 輸入設定物件 Properties
|
Returns:
回傳物件{genTempSettings(overrides),cleanupTempSettings(),files()}
- Type
- Object
(async) cropRegion(buf, rect) → {Promise}
- Description:
自截圖buffer裁切指定矩形(夾在buffer內),供把動態區域存成參考小圖
- Source:
Example
import cropRegion from 'w-package-tools-e2e/src/cropRegion.mjs'
let ref = await cropRegion(buf, { x: 240, y: 330, w: 1000, h: 380 })
Parameters:
| Name | Type | Description |
|---|---|---|
buf |
Buffer | 輸入PNG之Buffer |
rect |
Object | 輸入矩形物件{x,y,w,h}(buffer座標) |
Returns:
回傳Promise,resolve回傳裁切後之PNG Buffer,矩形完全在圖外時reject
- Type
- Promise
diffBaselineSnapshots(before, after) → {Object}
- Description:
比對兩次snapshotBaselines之結果,列出新增、刪除、內容改變、內容相同但被重寫(mtime改變)之檔案
touched(內容相同但mtime改變)亦視為「被重寫」:已審過之圖被重產等於換掉真理,即使內容相同, 故「零標準圖變動」須added、removed、changed、touched皆為空(unchanged為true)
- Source:
Example
import snapshotBaselines from 'w-package-tools-e2e/src/snapshotBaselines.mjs'
import diffBaselineSnapshots from 'w-package-tools-e2e/src/diffBaselineSnapshots.mjs'
let before = snapshotBaselines('./test/pics')
//...
let r = diffBaselineSnapshots(before, snapshotBaselines('./test/pics'))
if (!r.unchanged) throw new Error(`標準圖被改動: ${JSON.stringify(r)}`)
Parameters:
| Name | Type | Description |
|---|---|---|
before |
Object | 輸入先前之快照物件 |
after |
Object | 輸入之後之快照物件 |
Returns:
回傳物件{added,removed,changed,touched,unchanged},前四者為相對路徑陣列(已排序),unchanged為四者皆空之布林值
- Type
- Object
findOrphanBaselines(opt) → {Array}
- Description:
依案例宣告找出標準圖目錄中之孤兒圖(靜態比對宣告,與實際執行了哪些案例無關)
目錄與檔名前後綴由pathOf(lang,key)推得(圖鍵須在檔名內);目錄內符合「前綴…後綴」之檔案,若其圖鍵不是任何案例(含只比對案例)於該語系所宣告之圖鍵即為孤兒。 預期圖鍵:案例之語系(langs,預設全部)含該語系者之stages;未宣告stages者以案例鍵為單張圖鍵,並視以其編號(E2E-NNN)為前綴之圖鍵為該案所有(執行後才決定之多階段圖)。 以
_開頭之貼圖參考片段不符前綴而自然排除。因不依賴執行紀錄,--grep/--names局部執行、案例失敗、knownDefect皆不致誤報。 why:改名後重產只寫新名、從不刪舊名,舊圖成為無人產出亦無人比對之孤兒(PERM 2026-09-17 改版留下12張,至09-28才被發現)。
- Source:
Example
import findOrphanBaselines from 'w-package-tools-e2e/src/findOrphanBaselines.mjs'
let orphans = findOrphanBaselines({ cases, langs: ['eng', 'cht'], pathOf: (lang, key) => `./test/pics/login/login-${lang}-${key}.png` })
console.log(orphans)
// => [] 或 ['test\\pics\\login\\login-eng-E2E-099-old.png', ...]
Parameters:
| Name | Type | Description | |||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opt |
Object | 輸入設定物件 Properties
|
Returns:
回傳孤兒圖路徑陣列(依語系、檔名排序)
- Type
- Array
getE2eMode(optopt) → {Object}
- Description:
判定e2e執行模式:是否為標準圖產製(regen)、是否處於診斷環境、截圖是否採strict
regen:命令列含
--baseline或環境變數E2E_REGEN為'1';diag:環境變數E2E_BARE或E2E_DIAG有值;strictCapture:環境變數E2E_STRICT_CAPTURE為'1'。 診斷閘門:regen且diag時拋錯(診斷環境下絕不可寫入正式標準圖,否則把診斷態凍結為真理),可以opt.guard=false關閉(僅供測試)
- Source:
Example
import getE2eMode from 'w-package-tools-e2e/src/getE2eMode.mjs'
let { regen } = getE2eMode()
Parameters:
| Name | Type | Attributes | Default | Description | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳模式物件{regen,diag,strictCapture}
- Type
- Object
gridContentBox(gridSel, optopt) → {Object}
- Description:
【ag-grid】紅框「表格內容」之量測型目標:標頭(含浮動篩選列) ∪ 可見資料列,夾在表格框內;無資料列時改取標頭 ∪「無資料」訊息(外擴 INK_PAD)
交給captureStableWithBox作為target(或其陣列之一員)。技能 §7.2「表格出現/重算 → 標頭+有資料的列,夾在表格框內」、 §7.3-2「不框整區空白」:直接框表格外框(.ag-root-wrapper)時,列數少的表格會把下方大片空白一起框進去(業主問「這到底是圈什麼」)。 量測於捲入與等待之後、截圖之前進行;可見資料列=與資料捲動區(.ag-body-viewport)有交集且高度大於0之.ag-row(含左側固定欄之列)。 欄位未撐滿表格寬時(標頭容器撐滿、欄位只佔左側,例:兩欄之 IP 清單),右側空白 ≥ 200px 且 ≥ 框寬 30% 即把右緣收在最後一欄(2026-09-28)。 表格框找不到、或標頭與資料列、訊息皆量不到時回傳null(captureStableWithBox因此拋錯,不產出無框圖)。 「無資料」訊息為無可見邊界之文字,其矩形即文字行框:直接取聯集時框之內緣只距行框 1px,字形墨跡至框內緣僅 3–4px (2026-09-30 量測 w-web-sso/w-web-perm 之空表標準圖),低於技能 §7.3-8 要求之約 5px;比照 itemsUnionBox 之 fit 與 inkRect (無可見邊界之文字一律外擴 INK_PAD),訊息矩形四周外擴 noRowsPad 後再取聯集。noRowsPad:0 可重現舊行為(供等價對照)。
- Source:
Example
import gridContentBox from 'w-package-tools-e2e/src/gridContentBox.mjs'
let buf = await captureStableWithBox(page, gridContentBox('.ag-root-wrapper'))
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
gridSel |
String | 輸入表格外框之選擇器字串,例如'.ag-root-wrapper'或'.dlg .ag-root-wrapper',取第一個命中者 |
|||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳量測型目標物件{label,scroll,measure}
- Type
- Object
isChildAlive(child) → {Boolean}
- Description:
判斷子行程是否仍存活('exit'事件尚未發生)
- Source:
Example
import isChildAlive from 'w-package-tools-e2e/src/isChildAlive.mjs'
console.log(isChildAlive(child))
// => true
Parameters:
| Name | Type | Description |
|---|---|---|
child |
ChildProcess | 輸入子行程 |
Returns:
回傳是否存活
- Type
- Boolean
itemsUnionBox(itemSel, optopt) → {Object}
- Description:
紅框「可見項目之聯集」之量測型目標:清單、樹、選單、訊息時間軸、工具列上之項目、一行訊息文字等,框實際有內容之項目而非整欄/整區
交給captureStableWithBox作為target(或其陣列之一員)。技能 §7.2「清單/樹重新列出、展開縮合、進入頁面 → 當時可見之項目列的聯集」、 §7.3-2「不框整欄/整區的空白」。量測於捲入與等待之後、截圖之前進行。 項目:選擇器字串、其陣列(以逗號併為一個選擇器),或Playwright Locator(例:getByText 取得之訊息文字);選擇器與Locator皆經同一個頁內量測函式。 可見項目=命中項目、寬高大於0、非display:none/visibility:hidden/opacity:0,且與範圍容器(within;未給則為視窗)有交集者; 聯集再夾在範圍容器內(容器可捲動時,捲出可視範圍之項目不計入)。一個都量不到時回傳null(captureStableWithBox因此拋錯)。 fit:依「元素自身有無可見邊界」決定量法——有(底色異於其背後底色、至少三邊之邊框、陰影、背景圖,或圖片/表單等替換元素;晶片、按鈕、 灰頁上之白卡、底色列)量元素本身;無(透明容器、白頁上之白底區塊、整行文字、開關與其標籤、勾選列、無內距之欄位與表單欄) 改量其可見內容(有可見邊界之子元素框與文字行框之聯集;透明子元素之內距不計),且一律外擴 inkPad。 用於「一行字卻框整列寬」(技能 §7.2 提示訊息/狀態文字→訊息本體)、「一列晶片卻框整列寬」與「無邊界之元件緊貼文字」。 inkPad(2026-09-28):紅框幾何為目標 ±6 且 5px 描邊內縮,無可見邊界之目標緊貼墨跡時框內僅剩約 1px、紅框壓字 (SSO login E2E-006、stainfor E2E-002、tokens 權限勾選列與編輯模式開關、連線狀態文字殷鑑);外擴 4px 後框內留白約 5px,與自帶內距之按鈕相當。 子元素(勾選框、開關軌道、圖示、輸入框)與文字同外擴(同日改:原只外擴文字,子元素框貼框線 0–1px、四邊留白不一致)。 fit 下框線不壓框外內容(2026-09-28,業主:「亂框亂壓導致遮蔽有效資訊或使用者誤解就是問題」): ①相鄰項目(自元素往上至多 4 層之緊鄰兄弟,上下左右四向各取最近一層):框線置於兩者可見範圍(有可見邊界者為元素框,否則為子元素與文字之聯集) 間隙正中,不壓到鄰項之字(例:權限清單緊鄰列、模式段與名稱段),也不因鄰項元素框之透明內距而貼回目標自身之字(例:開關右側之圖示鈕); 無可見內容之兄弟(空白間隔)不算鄰項; ②浮層(自身或祖先為 position:fixed,例:下拉清單、浮出面板;全畫面者除外):浮層外之頁面內容不是 DOM 兄弟,改以命中測試找目標四周、 浮層範圍外最近之可見內容(文字、晶片/按鈕/觸發區級之有邊界元件、圖示與表單元件;大容器之底色與整張圖表不算;遮罩後之背景不算), 框線置於與其間隙正中;浮層本身為目標時以其內容之可見範圍計,框線可退入浮層內距;四周無內容者照常外擴 (同日首版一律畫在浮層內側,內距小之清單框線反而貼字:PERM 時間清單下緣 2px、關聯模式清單左緣 1px)。
- Source:
Example
import itemsUnionBox from 'w-package-tools-e2e/src/itemsUnionBox.mjs'
let buf = await captureStableWithBox(page, itemsUnionBox('.tree-item', { within: '[data-fmid="channel-tree"]' }))
let buf2 = await captureStableWithBox(page, ['img.spinner', itemsUnionBox(page.getByText('Connecting...').first(), { fit: true })])
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
itemSel |
String | Array | Object | 輸入項目之選擇器字串、其陣列,或Playwright Locator |
|||||||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳量測型目標物件{label,scroll,measure}
- Type
- Object
killOwnTree(child, optopt) → {Boolean}
- Description:
同步殺自建子行程之整棵行程樹;已結束或無PID者不下殺(其PID可能已被系統回收給無關行程)
Windows以同步
taskkill /F /T /PID <pid>;其他平台先殺行程群組,失敗再退回child.kill('SIGKILL')。 同步:exit/SIGINT/SIGTERM處理器內非同步spawn不會被等待。是否真的結束由呼叫端回驗(waitChildExit或pidExists)。 移植自w-web-task test/tools/harnessProc.mjs
- Source:
Example
import killOwnTree from 'w-package-tools-e2e/src/killOwnTree.mjs'
killOwnTree(backendChild)
Parameters:
| Name | Type | Attributes | Default | Description | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
child |
ChildProcess | 輸入自建子行程 |
||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳是否有下殺
- Type
- Boolean
killPortListeners(port, optopt) → {Object}
- Description:
殺監聽指定port之全部行程(含其行程樹;同步)
【政策】本函數會殺非本行程所建立之行程,只可用於「該port專屬本專案」且已於專案映射表明文登錄之情形 (例如重啟本專案後端而port被手動啟動之同專案後端佔用、逐檔隔離runner每檔前換新後端);絕不可用於他專案或共用port。 Windows以
taskkill /F /T /PID,其他平台以kill -9;exclude內之PID(預設本行程)不殺
- Source:
Example
import killPortListeners from 'w-package-tools-e2e/src/killPortListeners.mjs'
let r = killPortListeners(11007)
console.log(r)
// => { pids: ['18168'], killed: ['18168'] }
Parameters:
| Name | Type | Attributes | Default | Description | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
port |
Number | 輸入port |
||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳物件{pids,killed},pids為查得之監聽PID陣列(查詢工具不可用時為null),killed為已下殺之PID陣列
- Type
- Object
(async) launchBrowser(optopt) → {Promise}
- Description:
以確定性渲染旗標啟動無頭Chromium,為e2e測試端、標準圖產製端與探測腳本之唯一啟動出口
預設為無頭並帶chromiumLaunchArgs六旗標;opt.args另附加於六旗標之後,其餘鍵原樣傳給launchChromium(即chromium.launch)。 所需版本之瀏覽器不存在時由launchChromium先行下載
- Source:
Example
import launchBrowser from 'w-package-tools-e2e/src/launchBrowser.mjs'
let browser = await launchBrowser()
let page = await browser.newPage()
await page.goto('http://127.0.0.1:8080')
await browser.close()
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opt |
Object |
<optional> |
{}
|
輸入chromium.launch之設定物件,預設{} Properties
|
Returns:
回傳Promise,resolve回傳Browser,reject回傳錯誤
- Type
- Promise
(async) launchChromium(optopt) → {Promise}
- Description:
啟動Playwright之Chromium,所需版本之瀏覽器不存在時先自動下載再啟動
不使用套件安裝腳本(npm 12起預設略過相依套件之install/postinstall),改於第一次啟動時以Playwright自身之CLI下載, 下載之瀏覽器為本套件所用Playwright版本指定之版本,與本機已安裝者相同,像素比對得以一致
Linux缺少系統相依套件時先執行install-deps(非root時Playwright會自行使用sudo)再啟動; 指定系統瀏覽器(channel如'chrome')或自訂executablePath時不自動安裝
多個程序同時首次啟動時,Playwright安裝過程有目錄鎖,只會下載一次,其餘等待後直接使用
- Source:
Example
import launchChromium from 'w-package-tools-e2e/src/launchChromium.mjs'
let browser = await launchChromium({ headless: true, args: ['--disable-gpu'] })
let page = await browser.newPage()
await page.goto('http://127.0.0.1:8080')
await browser.close()
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
opt |
Object |
<optional> |
{}
|
輸入chromium.launch之設定物件,原樣傳入,預設{} |
Returns:
回傳Promise,resolve回傳Browser,reject回傳錯誤
- Type
- Promise
(async) launchChromiumCore(optopt, deps) → {Promise}
- Description:
啟動瀏覽器之核心流程,缺少Playwright所需版本之瀏覽器時先安裝再啟動,Linux缺少系統相依套件時先安裝相依再啟動
依賴以參數注入,供單元測試模擬各種失敗情形,實際使用請呼叫launchChromium
僅在使用Playwright管理之瀏覽器時自動安裝(未指定executablePath,且channel為空或'chromium'), 指定系統瀏覽器(channel如'chrome')或自訂執行檔時不安裝,錯誤直接拋出
各補救措施(安裝瀏覽器、安裝系統相依)至多各執行一次,補救後仍失敗則拋出錯誤
- Source:
Parameters:
| Name | Type | Attributes | Default | Description | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opt |
Object |
<optional> |
{}
|
輸入chromium.launch之設定物件,預設{} |
||||||||||||
deps |
Object | 輸入依賴物件 Properties
|
Returns:
回傳Promise,resolve回傳deps.launch之結果,reject回傳錯誤
- Type
- Promise
listenerPids(port, optopt) → {Array|null}
- Description:
查詢監聽指定port之PID(同步)
Windows以
netstat -ano(不可加-p TCP,該參數只列IPv4,會漏掉以::雙堆疊監聽者)取得後以parseListenerPids解析; 其他平台以lsof -nP -iTCP:<port> -sTCP:LISTEN -t。無人監聽回傳[],查詢工具不可用回傳null(呼叫端不得把null當成無人)。 依賴可注入,供單元測試。移植自w-web-task test/tools/harnessProc.mjs
- Source:
Example
import listenerPids from 'w-package-tools-e2e/src/listenerPids.mjs'
console.log(listenerPids(11007))
// => ['18168']
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
port |
Number | 輸入port |
|||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳PID字串陣列,無人回傳[],工具不可用回傳null
- Type
- Array | null
(async) maskRegions(buf, rects, coloropt) → {Promise}
- Description:
對截圖buffer之指定矩形填色(截圖後以sharp合成,不改動被測頁DOM)
用途限於DOM層凍不到之動態內容(例如img內SVG SMIL動畫、無法固定之即時數值);絕不用來遮應被偵測之靜態UI。 預設黑色,一眼可辨為刻意遮蔽。矩形座標為buffer座標(全頁截圖時為視窗座標加捲動量),超出buffer之部分自動裁切,寬或高為0者略過
- Source:
Example
import maskRegions from 'w-package-tools-e2e/src/maskRegions.mjs'
let buf = await page.screenshot({ fullPage: true })
buf = await maskRegions(buf, [{ x: 10, y: 20, w: 100, h: 30 }])
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
buf |
Buffer | 輸入PNG之Buffer |
||
rects |
Array | 輸入矩形陣列,每個元素為{x,y,w,h} |
||
color |
Object |
<optional> |
{r:0,g:0,b:0}
|
輸入填色物件,預設黑色 |
Returns:
回傳Promise,resolve回傳填色後之PNG Buffer,無有效矩形時回傳原buffer
- Type
- Promise
normalizeShots(result, name) → {Array}
- Description:
把案例執行函數之各種回傳形狀正規化為截圖陣列[{key,buf}]
支援:①Buffer(單張,圖鍵為name);②物件{圖鍵:Buffer}(多階段);③陣列[{name,buf}]或[{key,buf}]; ④包裝物件{buf,page}或{shots,page}(執行中換頁者,page另由呼叫端取用);⑤null或undefined(無截圖)。 圖鍵重複、元素不是Buffer一律拋錯
- Source:
Example
import normalizeShots from 'w-package-tools-e2e/src/normalizeShots.mjs'
normalizeShots(buf, 'E2E-001-list-loaded')
// => [{ key: 'E2E-001-list-loaded', buf }]
normalizeShots({ 'E2E-002-1-a': b1, 'E2E-002-2-b': b2 }, 'E2E-002-x')
// => [{ key: 'E2E-002-1-a', buf: b1 }, { key: 'E2E-002-2-b', buf: b2 }]
Parameters:
| Name | Type | Description |
|---|---|---|
result |
* | 輸入案例執行函數之回傳值 |
name |
String | 輸入案例鍵字串(單張時作為圖鍵) |
Returns:
回傳截圖陣列[{key,buf}]
- Type
- Array
(async) openCasePage(browser, optopt) → {Promise}
- Description:
為單一案例開新的瀏覽器context與頁面,並掛上對話框(alert/confirm/prompt)處理器
每案例fresh context,避免cookie、localStorage與快取跨案例殘留;contextOptions透傳給browser.newContext (viewport、locale、timezoneId等確定性參數依專案給定,不給即沿用Playwright預設)
- Source:
Example
import openCasePage from 'w-package-tools-e2e/src/openCasePage.mjs'
let page = await openCasePage(browser)
let page2 = await openCasePage(browser, { contextOptions: { viewport: { width: 1440, height: 900 } } })
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
browser |
Browser | 輸入Playwright之Browser |
|||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,resolve回傳Playwright之Page
- Type
- Promise
(async) overlayImageAt(buf, overlayBuf, left, top) → {Promise}
- Description:
把一張已裁切成區域尺寸之小圖overlayBuf貼到截圖buffer之(left,top),超出buffer邊界之部分自動裁切
用於貼圖覆蓋時只存「動態區域那一塊」之參考小圖(而非整頁),一看即知只覆蓋該區,其餘區域仍逐像素比對
- Source:
Example
import overlayImageAt from 'w-package-tools-e2e/src/overlayImageAt.mjs'
buf = await overlayImageAt(buf, refChartBuf, 240, 330)
Parameters:
| Name | Type | Description |
|---|---|---|
buf |
Buffer | 輸入PNG之Buffer |
overlayBuf |
Buffer | 輸入要貼上之小圖PNG Buffer |
left |
Number | 輸入貼上位置左緣像素 |
top |
Number | 輸入貼上位置上緣像素 |
Returns:
回傳Promise,resolve回傳貼上後之PNG Buffer,貼上位置完全在圖外時回傳原buffer
- Type
- Promise
(async) overlayRegions(buf, rects, refBuf) → {Promise}
- Description:
以參考圖refBuf同座標之內容覆蓋截圖buffer之指定矩形(貼圖覆蓋,取代填黑)
用途:動態圖表(canvas)跨行程渲染無法像素穩定、又需保留真實畫面者;產製端與比對端貼同一張參考圖,該區永遠一致,其餘區域仍逐像素比對。 refBuf須與buf同版面同尺寸(同座標取用),矩形夾在兩者邊界內
- Source:
Example
import overlayRegions from 'w-package-tools-e2e/src/overlayRegions.mjs'
buf = await overlayRegions(buf, [{ x: 240, y: 330, w: 1000, h: 380 }], fs.readFileSync('./test/pics/stainfor/_staref-eng-chart.png'))
Parameters:
| Name | Type | Description |
|---|---|---|
buf |
Buffer | 輸入PNG之Buffer |
rects |
Array | 輸入矩形陣列,每個元素為{x,y,w,h}(buffer座標) |
refBuf |
Buffer | 輸入參考圖PNG之Buffer |
Returns:
回傳Promise,resolve回傳覆蓋後之PNG Buffer,無有效矩形時回傳原buffer
- Type
- Promise
(async) pageHasText(page, text) → {Promise}
- Description:
判斷頁面DOM是否有某個文字節點含指定字串(略過SCRIPT、STYLE、NOSCRIPT)
注意:逐一比對單一文字節點,跨多個節點(例如被切開)之字串不會命中;不判斷元素是否可見
- Source:
Example
import pageHasText from 'w-package-tools-e2e/src/pageHasText.mjs'
console.log(await pageHasText(page, '儲存金鑰數據成功'))
// => true
Parameters:
| Name | Type | Description |
|---|---|---|
page |
Page | 輸入Playwright之Page |
text |
String | 輸入要尋找之字串 |
Returns:
回傳Promise,resolve回傳是否找到之布林值
- Type
- Promise
parseListenerPids(text, port) → {Array}
- Description:
解析
netstat -ano之輸出,取出本地位址為指定port之TCP監聽列之PID(字串、去重)監聽列之判定:外部位址為0.0.0.0:0、[::]:0或*:*(監聽中之socket沒有對端),或狀態欄為LISTENING;前者與顯示語言無關(部分語系之Windows會在地化狀態字)。 以「本地位址結尾為:port」比對,避開外部位址含:port之連線列;IPv6位址含zone id亦適用;UDP列與表頭(在地化文字)皆略過。 移植自w-web-task test/tools/harnessProc.mjs(含其單元測試)
- Source:
Example
import parseListenerPids from 'w-package-tools-e2e/src/parseListenerPids.mjs'
parseListenerPids(' TCP 0.0.0.0:11007 0.0.0.0:0 LISTENING 1234', 11007)
// => ['1234']
Parameters:
| Name | Type | Description |
|---|---|---|
text |
String | 輸入netstat -ano之輸出字串 |
port |
Number | 輸入port |
Returns:
回傳PID字串陣列,無監聽者回傳[]
- Type
- Array
pidExists(pid, optopt) → {Boolean}
- Description:
同步判斷PID是否存在(exit/signal處理器內使用:事件迴圈不再推進,isChildAlive不會更新)
以kill(pid,0)探測:成功為存在、ESRCH為不存在、EPERM為存在(無權限)。PID回收只會造成誤報存在,不會誤殺
- Source:
Example
import pidExists from 'w-package-tools-e2e/src/pidExists.mjs'
console.log(pidExists(process.pid))
// => true
Parameters:
| Name | Type | Attributes | Default | Description | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
pid |
Number | String | 輸入PID |
||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳是否存在
- Type
- Boolean
(async) pollUntil(label, fn, optopt) → {Promise}
- Description:
測試行程端之偵測驅動等待:反覆執行判斷函數直到回傳truthy,逾時即拋錯(代表真實異常,而非固定等待不夠)
用於等待瀏覽器外之非同步結果——後端週期計時器寫入資料庫(封鎖、補登記)、背景程序產檔、日誌出現某行—— 取代「固定等N秒再讀」:計時器於負載高時延遲,固定秒數只在一台機器、一種負載下成立(2026-09-28 w-web-sso autoblock 以固定3秒等2秒週期計時器殷鑑)。與waitUntilExist分工:waitUntilExist之fn於瀏覽器頁面內執行(DOM、localStorage), 本函數之fn於測試行程執行(資料庫、檔案系統、HTTP),可使用閉包。 fn拋錯視為「尚未成立」並繼續輪詢(例如查詢之紀錄尚不存在而取值失敗),逾時訊息附最後一次錯誤。
- Source:
Example
import pollUntil from 'w-package-tools-e2e/src/pollUntil.mjs'
//等後端計時器把新IP補登記進ips表, 回傳查得之紀錄
let ips = await pollUntil('ips 補登記', async () => {
let rs = await woItems.ips.select({ ip })
return rs.length > 0 ? rs : null
}, { timeout: 60000 })
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
label |
String | 輸入錯誤訊息用之對象描述字串 |
|||||||||||||||||
fn |
function | 輸入判斷函數,可為async函數,回傳truthy代表成立 |
|||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,resolve為fn最後一次之truthy回傳值,逾時reject
- Type
- Promise
(async) probeHttp(url, optopt) → {Promise}
- Description:
探測HTTP服務是否有回應(用於「port已有服務則沿用、否則啟動」之判斷)
預設只要有任何HTTP回應即為true(連線失敗或逾時為false);opt.accept可限制狀態碼(例如status<500); opt.identify可讀回應內文判斷是否為本專案之服務(契約C2:health回專案識別才沿用,避免沿用到他專案之服務)
- Source:
Example
import probeHttp from 'w-package-tools-e2e/src/probeHttp.mjs'
let up = await probeHttp('http://127.0.0.1:11007/')
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
url |
String | 輸入網址字串 |
|||||||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,resolve回傳是否有(符合條件之)回應
- Type
- Promise
(async) probeStuckTooltip(page, optopt) → {Promise}
- Description:
【w-component-vue】截圖前偵測提示框殘留(captureStable之beforeShots掛鉤):captureStable已將游標移至(0,0)並初始等待後,仍顯示之hover型提示框(w-component-vue WTooltip mode='tooltip')必為殘留, 即mouseleave未送達其觸發區,拋錯使該案失敗(殘留畫面不得凍結為標準圖)。點開型浮層(mode='popup':WPopup、下拉清單)為刻意開啟,不在此列; display:none與visibility:hidden(popper之data-popper-reference-hidden)皆非畫面可見。
背景:w-component-vue ≤2.5.23之WButtonCircle於點擊時以v-if換掉游標下之圖示(promiseUnlock或loading之載入圖示、停用遮罩),其後出現全頁遮罩或游標隨即移開時, 未採「命中節點被移除後以最近仍在DOM之祖先為目標」之瀏覽器(Chrome 144以前;Playwright ≤1.62之預設啟動參數停用BoundaryEventDispatchTracksNodeRemoval)不派發mouseleave, 提示框殘留;2.5.24起圖示層與停用遮罩pointer-events:none已修正。本函數為回歸守門,亦涵蓋他處同型成因(自寫元件之內層被移除替換)。 以提示文字判斷殘留會漏(對話框標題列Save鈕等其他文字之提示框),且.WPopperFix亦用於下拉浮層,故依元件設定(mode)辨識,文字不限。
WTooltip未宣告name,以結構辨識:$refs含divTrigger與divContent、props.mode、data.valueTrans(w-component-vue 2.5.24之WTooltip.vue)。 本函數依賴上述內部欄位:元件改名或改寫時會找不到任何提示框而一律通過(靜默失效),升級w-component-vue時須以真元件頁複驗(hover中偵測得到、移開後為空、已開之WPopup不誤判)。 根實例:opt.rootSel有給時取該元素之__vue__;否則取window.$vo(應用掛上者),再否則取body直屬元素之__vue__;皆無則不檢查(非Vue頁面)。
- Source:
Example
import captureStable from 'w-package-tools-e2e/src/captureStable.mjs'
import probeStuckTooltip from 'w-package-tools-e2e/src/probeStuckTooltip.mjs'
let buf = await captureStable(page, { beforeShots: [probeStuckTooltip] })
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
page |
Object | 輸入Playwright頁面物件 |
|||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,無殘留時resolve回傳空陣列;有殘留時reject回傳錯誤物件
- Type
- Promise
registerCleanupHooks(cleanup, optopt) → {Object}
- Description:
註冊e2e收尾之兩條觸發來源與中斷備援:mocha root after(框架環境)、process之exit與中斷訊號
背景:spawn之子行程會拖住事件迴圈,process.on('exit')要等迴圈清空才觸發而形成互鎖,故框架環境須由root after主動收尾; 直跑(非mocha,如--baseline產製)時globalThis.after不存在,由直跑主函式末尾顯式呼叫cleanup,exit與訊號處理器為備援。 處理器內只能同步收尾(非同步spawn不會被等待),故cleanup須為同步函數;teardown(可非同步,例如殺後回驗)只用於root after
- Source:
Example
import registerCleanupHooks from 'w-package-tools-e2e/src/registerCleanupHooks.mjs'
registerCleanupHooks(cleanup, { afterTimeoutMs: 20000 })
Parameters:
| Name | Type | Attributes | Default | Description | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
cleanup |
function | 輸入同步收尾函數 |
||||||||||||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳物件{mochaAfter},mochaAfter代表是否已註冊root after
- Type
- Object
(async) resetAgGridScroll(page, optopt) → {Promise}
- Description:
【ag-grid】把表格水平捲動歸零並稍候,使欄位重排(例如切換編輯模式)後之視圖確定
- Source:
Example
import resetAgGridScroll from 'w-package-tools-e2e/src/resetAgGridScroll.mjs'
await resetAgGridScroll(page)
Parameters:
| Name | Type | Attributes | Default | Description | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
page |
Page | 輸入Playwright之Page |
||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,頁面無ag-grid時亦正常resolve
- Type
- Promise
rowBoxSel(rowIndex, optopt) → {Array}
- Description:
【ag-grid】框「整列」用之選擇器陣列:一列跨pinned-left與center兩容器(勾選框欄在pinned-left), 交給captureStableWithBox取聯集即框出涵蓋整列之紅框;單一.ag-row選擇器只會命中其中一個容器
順序不是中性的:captureStableWithBox只把「第一個」目標捲入視窗,故順序決定捲動對象,改順序可能改變截圖。 預設'pinned-left','center';w-web-perm、w-web-task之現行實作為['center','pinned-left'],沿用時須以order指定
- Source:
Example
import rowBoxSel from 'w-package-tools-e2e/src/rowBoxSel.mjs'
let buf = await captureStableWithBox(page, rowBoxSel(3))
let buf2 = await captureStableWithBox(page, rowBoxSel(3, { order: ['center', 'pinned-left'] }))
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
rowIndex |
Number | String | 輸入列之row-index |
|||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳選擇器字串陣列
- Type
- Array
(async) runBaselineCase(opt) → {Promise}
- Description:
單一案例之標準圖管線:產製端(regen)與比對端(compare)呼叫同一函數,使兩端之瀏覽器取得、每案fresh、前置、執行、語意斷言與順序逐項對稱
順序:prepare(開瀏覽器前,例如資料庫重置) → launch → openPage → beforeRun → run → 正規化截圖 → semantic → verify → 產製端:依gate逐張決定是否寫檔(全部斷言通過後才寫,任何斷言失敗則該案一張都不寫);比對端:逐張assertBaselineMatch → finally關閉瀏覽器(若run中換了瀏覽器,以ctx.browserRef.current為準)並呼叫afterCase。 各掛鉤皆收到ctx物件{mode,lang,name,browser,browserRef,page,shots,result},可依ctx.mode分流(例如只能在測試框架內執行之檢查)。 已知缺陷(createKnownDefect):產製端略過該案不寫圖並回傳status為'knownDefect';比對端呼叫onKnownDefect(例如mocha之this.skip),未給則原樣拋出
- Source:
Example
import runBaselineCase from 'w-package-tools-e2e/src/runBaselineCase.mjs'
//比對端(mocha)
it(name, async function() {
await runBaselineCase({ mode: 'compare', lang, name, launch: launchBrowser, run: fn, semantic, pathOf, onKnownDefect: () => this.skip() })
})
//產製端(直跑 --baseline)
await runBaselineCase({ mode: 'regen', lang, name, launch: launchBrowser, run: fn, semantic, pathOf, gate })
Parameters:
| Name | Type | Description | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opt |
Object | 輸入設定物件 Properties
|
Returns:
回傳Promise,resolve回傳物件{status,shots,written,kept,skipped},status為'regen'、'compare'、'compareOnly'或'knownDefect'
- Type
- Promise
(async) runIsolatedE2e(opt) → {Promise}
- Description:
逐檔隔離執行e2e:每個e2e檔以獨立之mocha行程執行,檔與檔之間由beforeEachFile換新後端等狀態
why:多個e2e檔塞在單一mocha行程時,會共用被前面測試改過狀態之後端(記憶體內計數、快取、換過設定之實例、RPC正規化過之欄位順序), 與各檔單獨產製標準圖之環境不同;逐檔各給全新後端即回到單獨執行之狀態。前端通常無狀態且啟動慢,由各檔之startServersOnce沿用。 測試檔以pattern於testDir動態列舉(新增之檔自動納入,不因寫死清單而靜默漏跑),依檔名排序執行。 只重跑受改動影響之案例時(依差異範圍驗證),以targets逐檔指定檔案與--grep:每檔仍換新後端,並自動加--fail-zero(grep對不到任何案例時失敗,不假綠)。 殺port等屬專案政策,一律以beforeEachFile/afterAll回呼注入,本函數不殺任何行程
啟動方式:有mocha執行檔(mochaBin,預設projRoot下之node_modules/mocha/bin/mocha.js)時以目前之node直接執行之(不經shell),否則沿用npx mocha(Windows下經shell)。 why:Windows下npx須經cmd.exe,參數被拼接成一行後由cmd.exe重新解析(2026-09-30實測):| & 切出另一指令(如'E2E-0(0[2-9]|1[0-5])-'被切成管線而exit 255), < > 為轉向(> 把輸出寫成cwd下之檔案且exit 0),^ 與 " 被吃掉、空白拆成多個參數、%名稱% 展開為環境變數(皆exit 0,靜默改變篩選); 括號、[ ]、! , ; = * ? \ 與中文原樣傳遞。Node 24另警告DEP0190。故經npx且於Windows時,參數含空白或 " & | < > ^ % 即於執行前拋錯,不跑被改寫之指令。 mocha執行檔之路徑一律以projRoot解析為絕對路徑(spawn之cwd為projRoot,相對路徑會被重複疊加);明確給定之mochaBin不存在即拋錯,不默默改用npx。
- Source:
Example
import runIsolatedE2e from 'w-package-tools-e2e/src/runIsolatedE2e.mjs'
import killPortListeners from 'w-package-tools-e2e/src/killPortListeners.mjs'
let { failed } = await runIsolatedE2e({
projRoot, testDir,
beforeEachFile: async () => { killPortListeners(11007); await new Promise((r) => setTimeout(r, 2000)) },
afterAll: () => { killPortListeners(11007) },
})
process.exit(failed === 0 ? 0 : 1)
//只重跑受影響之案例(每檔仍換新後端)
await runIsolatedE2e({
projRoot, testDir,
targets: [{ file: 'e2e-adduser.test.mjs', grep: 'E2E-0(0[2-9]|1[0-5])-' }, { file: 'e2e-ips.test.mjs', grep: 'E2E-004-' }],
beforeEachFile, afterAll,
})
Parameters:
| Name | Type | Description | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opt |
Object | 輸入設定物件 Properties
|
Returns:
回傳Promise,resolve回傳物件{results,failed},results為{file,code},failed為失敗檔數
- Type
- Promise
sleepSync(ms) → {undefined}
- Description:
同步睡眠(exit/signal處理器內回驗用;該處不能使用setTimeout)
- Source:
Example
import sleepSync from 'w-package-tools-e2e/src/sleepSync.mjs'
sleepSync(100)
Parameters:
| Name | Type | Description |
|---|---|---|
ms |
Number | 輸入毫秒數 |
Returns:
無回傳
- Type
- undefined
snapshotBaselines(dir, optopt) → {Object}
- Description:
盤點標準圖目錄之快照:每個圖檔之相對路徑、sha256、位元組數與mtime
用途:重產或改動共用設施之前後各取一次,交diffBaselineSnapshots比對,機械化「產前記下既有圖mtime,產後回驗未變」 (已審過之圖被重產等於換掉真理,即使內容看起來一樣)。只讀檔,不改動任何檔案。 相對路徑一律以/分隔(跨平台可比);目錄不存在時回傳空物件
- Source:
Example
import snapshotBaselines from 'w-package-tools-e2e/src/snapshotBaselines.mjs'
let before = snapshotBaselines('./test/pics')
//...重產或改動...
let after = snapshotBaselines('./test/pics')
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
dir |
String | 輸入標準圖根目錄字串 |
|||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳物件,鍵為相對路徑,值為{sha256,size,mtimeMs}
- Type
- Object
(async) stepShots(page, opt) → {Promise}
- Description:
「每步兩張」之單步:操作前框住將被操作之元素整顆 → 執行操作 → 等反應就緒 → 框住有反應之內容
依全域技能 role-coder-for-test-e2e §7.1(每個操作步驟兩張、兩張皆須有框)與 §7.2(先決定反應是什麼再決定框什麼)。 相鄰兼任(§7.3-1 相鄰兩物取聯集為一框):前一步之反應與本步之目標相鄰時,前一步之後圖即本步之前圖—— 此時呼叫端傳 before:null 表示不另拍;before 不可省略(undefined 即拋錯),強制每一步明確決定是否兼任。 泛化自 w-web-perm 之 setDialogModeWithShots/openChipsAllWithShots/setChipsAllModeWithShots 與 w-web-sso tokens 之 capturePermsChangeSave。
- Source:
Example
import stepShots from 'w-package-tools-e2e/src/stepShots.mjs'
let s = await stepShots(page, {
capture: captureStableWithBox,
before: saveBtnLoc(page),
act: () => saveBtnLoc(page).click(),
ready: () => waitCheckYes(page),
after: '.w-checkyes-panel',
})
shots['E2E-003-5-click-save'] = s.before
shots['E2E-003-6-save-success-modal'] = s.after
Parameters:
| Name | Type | Description | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
page |
Page | 輸入Playwright之Page |
||||||||||||||||||||||||||||||||||||||||
opt |
Object | 輸入設定物件 Properties
|
Returns:
回傳Promise,resolve回傳{before,after},before為Buffer或null(兼任),after為Buffer
- Type
- Promise
(async) typeIntoInput(page, locator, value) → {Promise}
- Description:
以真人鍵盤輸入文字至輸入框(Pattern D),供Vue v-model等受控輸入框
流程:等可見 → 等1000ms(元件掛載與綁定穩定) → 點擊 → 驗證焦點確實落在該元素(防父層mousedown.prevent攔截造成靜默漏字) → 以End加Backspace逐字清空(不用Ctrl+A或剪貼簿,避免與系統快捷鍵衝突) → keyboard.insertText一次注入 → 驗證值,不符重試至多3次。 why insertText:逐字type於受控輸入框每字觸發重繪,焦點可能中途離開而漏字
- Source:
Example
import typeIntoInput from 'w-package-tools-e2e/src/typeIntoInput.mjs'
await typeIntoInput(page, page.locator('input[type="password"]').first(), 'Pw@login1')
Parameters:
| Name | Type | Description |
|---|---|---|
page |
Page | 輸入Playwright之Page |
locator |
Locator | 輸入輸入框之Locator |
value |
String | 輸入要填入之文字字串 |
Returns:
回傳Promise,resolve代表值已正確填入,3次仍不符則reject
- Type
- Promise
(async) typeIntoNthInput(page, idx, value) → {Promise}
- Description:
以真人鍵盤輸入文字至頁面第idx個input(Pattern D,與typeIntoInput同機制),供無穩定選擇器之表單
- Source:
Example
import typeIntoNthInput from 'w-package-tools-e2e/src/typeIntoNthInput.mjs'
await typeIntoNthInput(page, 2, '10.0.0.9')
Parameters:
| Name | Type | Description |
|---|---|---|
page |
Page | 輸入Playwright之Page |
idx |
Number | 輸入input之索引(document.querySelectorAll('input')之順序) |
value |
String | 輸入要填入之文字字串 |
Returns:
回傳Promise,resolve代表值已正確填入,3次仍不符則reject
- Type
- Promise
(async) waitAlertGone(page, optopt) → {Promise}
- Description:
等提示浮窗(wsemi domAlert,即 w-component-vue WAlert/vo.$alert;元素 id 以 alt- 開頭)全部消失
截圖之反應目標不是提示浮窗本身時(例:行內紅字),浮窗在不在畫面上取決於時序(domAlert 預設數秒後自動移除),截圖因而不確定。 以偵測取代「固定等 N 秒」:先等反應目標就緒,再以本函數等浮窗消失後截圖 (w-web-sso login E2E-016 殷鑑:固定等待改偵測式後,文字一出現即截圖,截到尚未消失之錯誤浮窗而與標準圖不符)。 反應目標即為提示浮窗者不可用本函數(應等其滑入定位後截圖,技能 §8.1)。
- Source:
Example
import waitAlertGone from 'w-package-tools-e2e/src/waitAlertGone.mjs'
await waitUntilExist(page, '行內錯誤', (s) => document.body.innerText.includes(s), { arg: msg })
await waitAlertGone(page)
let buf = await captureStableWithBox(page, page.getByText(msg).last())
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
page |
Object | 輸入Playwright Page |
|||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,浮窗全部消失時resolve;逾時reject(含仍在之浮窗數)
- Type
- Promise
waitChildExit(child, timeoutMs) → {Promise}
- Description:
等子行程真正結束('exit'事件),逾時回傳false;以事件判定,不受PID回收影響,逾時亦不留監聽器
- Source:
Example
import waitChildExit from 'w-package-tools-e2e/src/waitChildExit.mjs'
let ok = await waitChildExit(child, 5000)
Parameters:
| Name | Type | Description |
|---|---|---|
child |
ChildProcess | 輸入子行程 |
timeoutMs |
Number | 輸入逾時毫秒數 |
Returns:
回傳Promise,resolve回傳是否已結束
- Type
- Promise
(async) waitColResizeOverlay(page, optopt) → {Promise}
- Description:
【w-component-vue】等WDrawer拖曳分隔條(cursor:col-resize)之覆蓋層opacity變為1
該覆蓋層之opacity由setTimeout(300ms)控制0到1,CPU忙時可能被延後超過初始等待,故於頁面內輪詢(每50ms)至全部為1; 頁面無此元素時立即放行,逾時(預設5000ms)不拋錯
- Source:
Example
import waitColResizeOverlay from 'w-package-tools-e2e/src/waitColResizeOverlay.mjs'
await waitColResizeOverlay(page)
Parameters:
| Name | Type | Attributes | Default | Description | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
page |
Page | 輸入Playwright之Page |
||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,resolve代表已就緒或已逾時
- Type
- Promise
(async) waitDrawerReady(page, optopt) → {Promise}
- Description:
【w-component-vue】等WDrawer抽屜到達穩定態(opened或hidden)才放行
讀WDrawer根節點之[state]屬性(平移transitionend決定性標記hidden/opening/opened/hiding),所有抽屜皆為opened或hidden即放行; 事件驅動,不受主執行緒負載影響(取代易被減速尾段騙而截到半途之座標取樣)。頁面無WDrawer時立即放行;逾時(預設10000ms)不拋錯,交由呼叫端之連拍穩定兜底
- Source:
Example
import waitDrawerReady from 'w-package-tools-e2e/src/waitDrawerReady.mjs'
await waitDrawerReady(page)
Parameters:
| Name | Type | Attributes | Default | Description | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
page |
Page | 輸入Playwright之Page |
||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,resolve代表已穩定或已逾時
- Type
- Promise
(async) waitGridIdle(page, optopt) → {Promise}
- Description:
【ag-grid】等表格靜止:以「內容簽章+版面幾何」每intervalMs取樣一次,連續stableMs毫秒完全相同才放行
簽章:.ag-cell總數、指定列(rowIndex)各儲存格之欄位id與前30字、中央欄容器/標頭/首列之矩形、水平與垂直捲動量。 why:只看文字之簽章抓不到1px位移,兩次雙重requestAnimationFrame(約4個frame)之觀察窗太短,技能列為已驗證無效之手法; 本函數取代各測試檔手寫之雙重rAF判斷。頁面無表格(.ag-center-cols-viewport)時立即放行,但minCells大於0時視為未就緒(等表格出現); requireSelector未出現、儲存格數少於minCells或(requireScrollLeftZero時)水平捲動量不為0之取樣視為未就緒並重新計時;逾時拋錯
- Source:
Example
import waitGridIdle from 'w-package-tools-e2e/src/waitGridIdle.mjs'
await waitGridIdle(page, { requireSelector: '.ag-header-cell[col-id="token"]', requireScrollLeftZero: true })
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
page |
Page | 輸入Playwright之Page |
|||||||||||||||||||||||||||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,resolve代表已靜止,逾時reject
- Type
- Promise
(async) waitUntilExist(page, label, fn, optopt) → {Promise}
- Description:
偵測驅動之等待:每步驟先偵測對象存在或就緒再進下一步,逾時即拋錯(代表真實異常,而非固定等待不夠)
fn於瀏覽器端執行,跨行程序列化故不可使用閉包變數,傳值一律經opt.arg。 fn為async函數(例如頁面內fetch後端再判斷)時改為逐次page.evaluate並await其結果輪詢:Playwright之waitForFunction同步呼叫predicate, async函數回傳之Promise為truthy而第一次即放行(不重試、逾時無效,條件不成立也照樣通過;2026-09-28查得共20處: w-web-sso之ag-grid等待以async判斷直呼waitForFunction 14處、w-web-task以async判斷呼叫waitUntilExist 6處)
- Source:
Example
import waitUntilExist from 'w-package-tools-e2e/src/waitUntilExist.mjs'
await waitUntilExist(page, '表格列', () => document.querySelectorAll('.ag-row').length > 0)
await waitUntilExist(page, '訊息', (t) => document.body.innerText.includes(t), { arg: '儲存成功' })
Parameters:
| Name | Type | Attributes | Default | Description | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
page |
Page | 輸入Playwright之Page |
||||||||||||||||||||||
label |
String | 輸入錯誤訊息用之對象描述字串 |
||||||||||||||||||||||
fn |
function | 輸入於頁面執行之判斷函數,回傳truthy代表就緒;可為async函數 |
||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,resolve代表就緒,逾時reject
- Type
- Promise