stepShots.mjs

/**
 * 「每步兩張」之單步:操作前框住將被操作之元素整顆 → 執行操作 → 等反應就緒 → 框住有反應之內容
 *
 * 依全域技能 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。
 *
 * @param {Page} page 輸入Playwright之Page
 * @param {Object} opt 輸入設定物件
 * @param {Function} opt.capture 輸入框圖截圖函數(page,target,opt)=>Promise<Buffer>,通常為專案之captureStableWithBox(已帶settle與strict組態)
 * @param {*} opt.before 輸入操作前之框選目標(選擇器、Locator、矩形或其陣列);null代表由前一步之後圖兼任、不另拍
 * @param {Function} opt.act 輸入執行操作之函數async()=>{}(真滑鼠/鍵盤,技能 §4)
 * @param {Function} [opt.ready=null] 輸入等待反應就緒之函數async()=>{}(偵測driven,技能 §4.4),預設null
 * @param {*} opt.after 輸入操作後之框選目標;為函數時於ready之後呼叫取得(目標於操作後才出現者)
 * @param {Object} [opt.beforeOpt={}] 輸入操作前截圖之設定物件(透傳capture),預設{}
 * @param {Object} [opt.afterOpt={}] 輸入操作後截圖之設定物件(透傳capture),預設{}
 * @returns {Promise} 回傳Promise,resolve回傳{before,after},before為Buffer或null(兼任),after為Buffer
 * @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
 *
 */
async function stepShots(page, opt = {}) {
    let { capture, before, act, ready = null, after, beforeOpt = {}, afterOpt = {} } = opt
    if (typeof capture !== 'function') {
        throw new Error('stepShots: capture 須為函數(通常為專案之 captureStableWithBox)')
    }
    if (typeof act !== 'function') {
        throw new Error('stepShots: act 須為函數')
    }
    if (before === undefined) {
        throw new Error('stepShots: before 不可省略——給操作前之框選目標, 或傳 null 表示由前一步之後圖兼任(技能 §7.3-1)')
    }
    if (after === undefined || after === null) {
        throw new Error('stepShots: after 為必填(操作後有反應之內容, 技能 §7.2)')
    }
    let bufBefore = before === null ? null : await capture(page, before, beforeOpt)
    await act()
    if (ready) {
        await ready()
    }
    let target = typeof after === 'function' ? await after() : after
    let bufAfter = await capture(page, target, afterOpt)
    return { before: bufBefore, after: bufAfter }
}


export default stepShots