/**
* 【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之取樣視為未就緒並重新計時;逾時拋錯
*
* @param {Page} page 輸入Playwright之Page
* @param {Object} [opt={}] 輸入設定物件,預設{}
* @param {Number} [opt.stableMs=1000] 輸入須連續相同之毫秒數,預設1000
* @param {Number} [opt.intervalMs=100] 輸入取樣間隔毫秒數,預設100
* @param {Number} [opt.timeout=15000] 輸入逾時毫秒數,預設15000
* @param {String} [opt.scope=''] 輸入限定範圍之選擇器前綴字串(例如對話框內之表格),預設''
* @param {String} [opt.requireSelector=null] 輸入須已出現之選擇器字串(例如某欄之標頭),預設null
* @param {Number} [opt.rowIndex=0] 輸入納入簽章之列row-index,預設0
* @param {Number} [opt.minCells=0] 輸入至少須有之儲存格數,預設0;大於0時表格尚未出現亦視為未就緒
* @param {Boolean} [opt.requireScrollLeftZero=false] 輸入是否要求水平捲動量為0布林值,預設false
* @returns {Promise} 回傳Promise,resolve代表已靜止,逾時reject
* @example
*
* import waitGridIdle from 'w-package-tools-e2e/src/waitGridIdle.mjs'
*
* await waitGridIdle(page, { requireSelector: '.ag-header-cell[col-id="token"]', requireScrollLeftZero: true })
*
*/
async function waitGridIdle(page, opt = {}) {
let {
stableMs = 1000,
intervalMs = 100,
timeout = 15000,
scope = '',
requireSelector = null,
rowIndex = 0,
minCells = 0,
requireScrollLeftZero = false,
} = opt
let ok = await page.evaluate(async (o) => {
let sel = (s) => (o.scope ? `${o.scope} ${s}` : s)
let rect = (e) => {
if (!e) return ''
let r = e.getBoundingClientRect()
return `${r.x.toFixed(1)},${r.y.toFixed(1)},${r.width.toFixed(1)},${r.height.toFixed(1)}`
}
//回傳 { skip } 代表頁面無表格; null 代表未就緒; { sig } 為簽章
let sample = () => {
let body = document.querySelector(sel('.ag-center-cols-viewport'))
if (!body) return o.minCells > 0 ? null : { skip: true }
if (o.requireSelector && !document.querySelector(o.requireSelector)) return null
if (o.requireScrollLeftZero && body.scrollLeft !== 0) return null
let cells = document.querySelectorAll(sel('.ag-cell'))
if (cells.length < o.minCells) return null
let row = Array.from(document.querySelectorAll(sel(`.ag-row[row-index="${o.rowIndex}"] .ag-cell`)))
return {
sig: JSON.stringify([
cells.length,
row.map((c) => `${c.getAttribute('col-id') || ''}:${(c.innerText || '').slice(0, 30)}`),
rect(document.querySelector(sel('.ag-center-cols-container'))),
rect(document.querySelector(sel('.ag-header'))),
rect(document.querySelector(sel('.ag-row'))),
body.scrollLeft,
body.scrollTop,
]),
}
}
let deadline = Date.now() + o.timeout
let prev = null
let since = 0
while (Date.now() < deadline) {
let s = sample()
if (s && s.skip) return true
let now = Date.now()
if (!s) {
prev = null
}
else if (s.sig !== prev) {
prev = s.sig
since = now
}
else if (now - since >= o.stableMs) {
return true
}
await new Promise((resolve) => setTimeout(resolve, o.intervalMs))
}
return false
}, { stableMs, intervalMs, timeout, scope, requireSelector, rowIndex, minCells, requireScrollLeftZero })
if (!ok) {
throw new Error(`waitGridIdle 超過 ${timeout}ms 表格仍未靜止${requireSelector ? ` (或未出現 ${requireSelector})` : ''}`)
}
}
export default waitGridIdle