UNPKG

w-puppeteer-uitest

Version:
369 lines (330 loc) 14.5 kB
import get from 'lodash-es/get.js' import isint from 'wsemi/src/isint.mjs' import isobj from 'wsemi/src/isobj.mjs' import isarr from 'wsemi/src/isarr.mjs' import isbol from 'wsemi/src/isbol.mjs' import iser from 'wsemi/src/iser.mjs' import cint from 'wsemi/src/cint.mjs' import pmSeries from 'wsemi/src/pmSeries.mjs' import puppeteer from 'puppeteer' /** * 瀏覽網址並用screenshot取得圖片base64資料 * * @memberOf w-puppeteer-uitest * @param {String} url 輸入瀏覽網址,可為本機檔案絕對或相對位置 * @param {Object} [opt={}] 輸入設定檔,預設為{} * @param {Boolean} [opt.headless=true] 輸入是否以無頭方式開啟網頁,預設為true * @param {Object} [opt.viewport={ width: 800, height: 600 }] 輸入網頁開啟後之viewport,預設為{ width: 800, height: 600 } * @param {Array} [opt.actions=[]] 輸入網頁開啟後之操作動作,預設為[] * @param {Object} opt.action 輸入action動作物件 * @param {Object} opt.action.mode 動作模式字串,可選'wait','move','elemove','elehover',drag','eledrag','click','eleclick','dbclick','eledbclick','type','eletype' * @param {Object} opt.action 若action.mode使用'wait',需再輸入{time},單位為毫秒 * @param {Object} opt.action 若action.mode使用'resize',需再輸入{width,height},為網頁可視區域(viewport)的長寬,單位為整數 * @param {Object} opt.action 若action.mode使用'move',需再輸入{x1,y1},為相對網頁內容左上角位置,單位為px * @param {Object} opt.action 若action.mode使用'elemove',需再輸入{selector,nth(可選)},selector為css選擇器,nth為陣列結果取第nth個dom元素,預設為0,負值代表由最末往前選(-nth)個dom元素 * @param {Object} opt.action 若action.mode使用'elehover',需再輸入{selector},selector為css選擇器,若有超過1個結果則取第1個dom元素 * @param {Object} opt.action 若action.mode使用'drag',需再輸入{x1,y1,x2,y2},由(x1,y1)拖曳至(x2,y2),為相對網頁內容左上角位置,單位為px * @param {Object} opt.action 若action.mode使用'eledrag',需再輸入{selector,nth(可選),shiftx,shifty},由元素中心拖曳平移(shiftx,shifty),selector為css選擇器,nth為陣列結果取第nth個dom元素,預設為0,負值代表由最末往前選(-nth)個dom元素,shiftx,shifty單位為px * @param {Object} opt.action 若action.mode使用'click',需再輸入{x1,y1},為相對網頁內容左上角位置,單位為px * @param {Object} opt.action 若action.mode使用'eleclick',需再輸入{selector,nth(可選)},selector為css選擇器,nth為陣列結果取第nth個dom元素,預設為0,負值代表由最末往前選(-nth)個dom元素 * @param {Object} opt.action 若action.mode使用'dbclick',需再輸入{x1,y1},為相對網頁內容左上角位置,單位為px * @param {Object} opt.action 若action.mode使用'eledbclick',需再輸入{selector,nth(可選)},selector為css選擇器,nth為陣列結果取第nth個dom元素,預設為0,負值代表由最末往前選(-nth)個dom元素 * @param {Object} opt.action 若action.mode使用'type',需再輸入{str,noEnter(可選)},為由當前焦點輸入文字str,noEnter為輸入文字結尾不再輸入enter,預設為true * @param {Object} opt.action 若action.mode使用'eletype',需再輸入{selector,nth(可選),str,noEnter(可選)},selector為css選擇器,nth為陣列結果取第nth個dom元素,預設為0,負值代表由最末往前選(-nth)個dom元素,通過click該dom元素作為焦點輸入文字str,noEnter為輸入文字結尾不再輸入enter,預設為true * @param {Object} opt.action 若action.mode使用'keypress',需再輸入{key,count(可選)},為由當前焦點輸入鍵盤key值,key可用'Backspace', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'或其他keycode,count為觸發次數,預設為1次,keypress可用key詳見[https://github.com/GoogleChrome/puppeteer/blob/v1.19.0/lib/USKeyboardLayout.js] * @param {Object} opt.action 若action.mode使用'elefocus',需再輸入{selector,nth(可選),str},selector為css選擇器,nth為陣列結果取第nth個dom元素,預設為0,負值代表由最末往前選(-nth)個dom元素,設定dom元素為當前焦點 * keypress * @param {Integer} [opt.waitsec=5] 輸入開啟網頁後之等待時間,單位為秒,預設為5 * @returns {String} 回傳screenshot圖片轉base64資料 */ async function getB64(url, opt = {}) { //headless let headless = get(opt, 'headless') if (!isbol(headless)) { headless = true } //viewport let viewport = get(opt, 'viewport') if (!isobj(viewport)) { viewport = { x: 0, y: 0, width: 800, height: 600, } } //action let actions = get(opt, 'actions') if (!isarr(actions)) { actions = [] } //waitsec let waitsec = get(opt, 'waitsec') if (!isint(waitsec)) { waitsec = 5 } //new page let olaunch = { //timeout: 0, headless: headless, slowMo: 20, // arg: [ // //`--window-size=${viewport.width},${viewport.height}`, //無效 // //`--disable-resize-lock`, //無效 // ], // defaultViewport: { //無效 // x: 0, // y: 0, // width: 800, // height: 600, // } } let browser = await puppeteer.launch(olaunch) let page = await browser.newPage() //show page await page.goto(url) await page.setViewport(viewport) // await page.emulate({ //無效 // viewport: { // width: viewport.width, // height: viewport.height, // }, // userAgent: '' // }) //getxy async function getxy(selector, n = 0) { //n n = cint(n) //ele let ele if (n === 0) { ele = await page.$(selector) if (iser(ele)) { console.log('page.$ get no result: ' + selector) return null } } else { let eles = await page.$$(selector) if (eles.length === 0) { console.log('page.$$ get no result: ' + selector) return null } if (n < 0) { ele = eles[eles.length + n] } else { ele = eles[n] } } //check if (iser(ele)) { console.log('invalid ele: ' + selector) return null } //check if (iser(ele.boundingBox)) { console.log('invalid ele.boundingBox: ' + selector) return null } //r let r = await ele.boundingBox() r['cx'] = r.x + r.width / 2 r['cy'] = r.y + r.height / 2 r['ele'] = ele // let r = await page.evaluate((e) => { // let { top, left, bottom, right } = e.getBoundingClientRect() // return { // top: top, // left: left, // bottom: bottom, // right: right, // cx: (left + right) / 2, // cy: (top + bottom) / 2, // } // }, ele) return r } //wait await page.waitFor(waitsec * 1000) //pmSeries await pmSeries(actions, async function(v) { if (v.mode === 'wait') { await page.waitFor(v.time) } else if (v.mode === 'resize') { await page.waitFor(300) await page.setViewport({ x: 0, y: 0, width: v.width, height: v.height, }) await page.waitFor(300) } else if (v.mode === 'move') { await page.waitFor(300) await page.mouse.move(v.x1, v.y1) await page.waitFor(300) } else if (v.mode === 'elemove') { await page.waitFor(300) let r = await getxy(v.selector, v.nth) if (r) { await page.mouse.move(r.cx, r.cy) await page.waitFor(300) } } else if (v.mode === 'elehover') { await page.waitFor(300) await page.hover(v.selector) await page.waitFor(300) } else if (v.mode === 'drag') { await page.waitFor(300) await page.mouse.move(v.x1, v.y1) await page.mouse.down() await page.waitFor(300) await page.mouse.move(v.x2, v.y2, { steps: 50 }) await page.waitFor(300) await page.mouse.up() await page.waitFor(300) } else if (v.mode === 'eledrag') { await page.waitFor(300) let r = await getxy(v.selector, v.nth) if (r) { await page.mouse.move(r.cx, r.cy) await page.mouse.down() await page.waitFor(300) await page.mouse.move(r.cx + v.shiftx, r.cy + v.shifty, { steps: 50 }) await page.waitFor(300) await page.mouse.up() await page.waitFor(300) } } else if (v.mode === 'click') { await page.waitFor(300) await page.mouse.move(v.x1, v.y1) await page.mouse.down() await page.mouse.up() await page.waitFor(300) } else if (v.mode === 'eleclick') { await page.waitFor(300) let r = await getxy(v.selector, v.nth) if (r) { await r.ele.click() // await page.mouse.move(r.cx, r.cy) // await page.waitFor(50) // await page.mouse.down() // await page.mouse.up() await page.waitFor(300) } } else if (v.mode === 'dbclick') { await page.waitFor(300) await page.mouse.click(v.x1, v.y1, { clickCount: 2 }) await page.waitFor(300) } else if (v.mode === 'eledbclick') { await page.waitFor(300) let r = await getxy(v.selector, v.nth) if (r) { await r.ele.click({ clickCount: 2 }) //await page.mouse.move(r.cx, r.cy) // //await page.waitFor(50) // await page.mouse.down() // await page.mouse.move(r.cx, r.cy) // await page.mouse.up() // //await page.waitFor(50) // await page.mouse.move(r.cx, r.cy) // await page.mouse.down() // await page.mouse.move(r.cx, r.cy) // await page.mouse.up() await page.waitFor(300) } } else if (v.mode === 'type') { await page.waitFor(300) await page.keyboard.type(v.str, { delay: 50 }) if (!v.noEnter) { //預設為true await page.keyboard.type(String.fromCharCode(13)) } await page.waitFor(300) } else if (v.mode === 'eletype') { await page.waitFor(300) let r = await getxy(v.selector, v.nth) if (r) { await r.ele.click() await page.waitFor(50) await page.keyboard.type(v.str, { delay: 50 }) if (!v.noEnter) { //預設為true await page.keyboard.type(String.fromCharCode(13)) } await page.waitFor(300) } } else if (v.mode === 'keypress') { await page.waitFor(300) let count = cint(v.count) if (count <= 0) { count = 1 } for (let i = 0; i < count; i++) { await page.keyboard.press(v.key) //Backspace, ArrowLeft, ArrowRight, ArrowUp, ArrowDown } await page.waitFor(300) } else if (v.mode === 'elefocus') { await page.waitFor(300) let r = await getxy(v.selector, v.nth) if (r) { await r.ele.focus() await page.waitFor(300) } } else { console.log('mode is unrecognized: ' + v.mode) } }) //wait await page.waitFor(2000) //預設等2s才視為action全部結束 // //get base64 from screenshot, 此法雖能sceenshot到tooltip, 但內容物(例:ag-grid)會爆版 // //add html2canvas and html2pic64 // await page.addScriptTag({ url: 'https://html2canvas.hertzen.com/dist/html2canvas.min.js' }) // let cfun = ` // function html2pic64(ele) { // //類似print screen將html轉為base64圖片 // return new Promise((resolve, reject) => { // let opt={ // logging:false, //不要console.log // scale:1, //anti-aliasing // width:${viewport.width}, // height:${viewport.height}, // } // html2canvas(ele, opt) // .then(function (canvas) { // resolve(canvas.toDataURL('image/png')) // }) // .catch(function (msg) { // reject(msg) // }) // }) // } // ` // await page.addScriptTag({ content: cfun }) // let base64 = await page.evaluate(() => { // return html2pic64(document.body) // }) // base64 = base64.replace('data:image/png;base64,', '') //取代掉給img用之開頭符號 //get base64 from screenshot, 此法最不會爆版, 但screenshot前會先resize至原始dpi(例:96), 導致tooltip被自動隱藏無法截到圖 //已恢復正常 let base64 = await page.screenshot({ encoding: 'base64' }) //fullPage: true //close await page.close() await browser.close() return base64 } export default getB64