<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                &emsp;&emsp;最近在研究[Web自動化測試](https://www.cnblogs.com/strick/p/16892143.html),之前做了些實踐,但效果并不理想。 &emsp;&emsp;對于 QA 來說,公司的網頁交互并不多,用手點點也能滿足。對于前端來說,如果要做成自動化,就得維護一堆的腳本。 &emsp;&emsp;當然,這些腳本也可以 QA 來維護,但前提是得讓他們覺得做這件事的 ROI 很高,依目前的情況看,好像不高。 &emsp;&emsp;所以在想,做一個平臺,在這個平臺中可以保存些數據,并且在旁邊提供個小窗口,呈現要測試的 H5 網頁,如下圖所示(畫圖工具是[excalidraw](https://excalidraw.com/))。 &emsp;&emsp;在修改相關數據后,可以直接看到網頁的變化。 :-: ![](https://img.kancloud.cn/8b/23/8b235013653afbe5ed7b39d4bfffb12c_1101x938.png =400x) &emsp;&emsp;QA 或前端可以不用再寫腳本代碼,就能實現自動化測試。 &emsp;&emsp;目前想到兩塊,第一塊是攔截請求,mock 響應;第二塊是記錄頁面行為,然后自動回放,最后截圖,和上一次的截圖做對比分析,看是否相同。 ## 一、攔截請求 &emsp;&emsp;攔截請求就是將響應 mock 成自己想要的數據,然后查看頁面的呈現。 &emsp;&emsp;這樣就能模擬各種場景,畢竟測試環境的業務數據肯定不能滿足所有場景,所以需要自己造。 &emsp;&emsp;有了平臺后,就能將造的數據保存在數據庫中,可隨時調取查看頁面呈現。 **1)攔截** &emsp;&emsp;現在就要實現攔截,我首先想到的就是注入腳本,然后在 XMLHttpRequest 或 fetch() 埋入攔截代碼。 &emsp;&emsp;以 XMLHttpRequest 為例,在 monitorXHR() 函數中就可以讓請求轉發到代理處。 ~~~ var _XMLHttpRequest = window.XMLHttpRequest; // 保存原生的XMLHttpRequest // 覆蓋XMLHttpRequest window.XMLHttpRequest = function (flags) { var req = new _XMLHttpRequest(flags); // 調用原生的XMLHttpRequest monitorXHR(req); // 埋入我們的“間諜” return req; }; ~~~ &emsp;&emsp;例如將所有的請求都 post 到 test/proxy 接口,這是一個 Node 接口,代碼如下。 &emsp;&emsp;代碼比較簡單,沒有考慮各種請求,例如自定義的 header、cookie 等。因為沒有經過實踐,只是展示下思路,所以肯定存在著 BUG。 &emsp;&emsp;思路就是將整理好的請求地址、參數等信息轉發過來后,先從數據庫中查看是否有指定的 mock 數據。 &emsp;&emsp;如果有就直接返回,若沒有,就再去請求原接口。 ~~~ router.post("/test/proxy", async (ctx) => { const { id, method, url, params } = ctx.request.body; // 通過ID查找存儲在 MongoDB 中的攔截記錄 const row = await services.app.getOne(id); if (row) { ctx.body = row.response; return; } // 沒有攔截就請求原接口 const { data } = await axios[method](url, params); ctx.body = data; }); ~~~ &emsp;&emsp;理論上,是完成了攔截,但是現在還有個很重要的問題,那就是 XMLHttpRequest 或 fetch() 那段間諜腳本該怎么注入。 **2)注入腳本** &emsp;&emsp;暫時想到了三個方法,第一個是通過控制 iframe 在頁面中注入腳本。 &emsp;&emsp;因為那張 H5 示例頁面,可以放到 iframe 中呈現,所以這種注入方式理論上可行。 &emsp;&emsp;只需要讀取 HTMLIFrameElement 中的[contentDocument](https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/contentDocument)屬性就能得到頁面中的 document。 ~~~ document.getElementById('inner').contentDocument.body.innerHTML ~~~ &emsp;&emsp;但是 iframe 有個同源限制,必須是同源的才能通過腳本讀取到 contentDocument。 &emsp;&emsp;況且注入的時機也比較講究,必須在發起請求之前,改寫 XMLHttpRequest 或 fetch(),若用 JavaScript 添加 script 元素,恐怕不夠及時。 &emsp;&emsp;那么第二個方法,就是在構建的時候將腳本注入,當然,在上線后,這些腳本都是要去除掉的,僅限測試的時候使用。 &emsp;&emsp;不過這種方法不夠自動化,需要研發配合,像我們這種小公司,就那么幾個項目,倒也問題不大。 &emsp;&emsp;第三個方法是用無頭瀏覽器(例如[puppeteer](https://pptr.dev/api/))將腳本注入(如下所示),然后再把新的頁面結構作為響應返回。 ~~~ await page.evaluate(async () => { const img = new Image(); img.src = "xxx.png"; document.body.appendChild(img); }); // 獲取 HTML 結構 const html = await page.content(); ~~~ &emsp;&emsp;但有個地方要注意,輸出頁面結構的域名要和之前相同(需要運維配合),否則那些腳本很有可能因為跨域而無法執行了。 ## 二、記錄頁面行為 &emsp;&emsp;網頁就是一棵 DOM 樹,要記錄頁面行為,其實就是記錄發生動作的 DOM 元素以及相關的動作參數。 &emsp;&emsp;腳本注入的方式可以參考上面的 3 種方法,平臺的布局也與上面的類似,只是表單中的參數可能略有不同。 **1)保存 DOM 元素** &emsp;&emsp;DOM 元素是不能直接 JSON 序列化的,所以需要將其映射成一個指定結構的對象,如下所示。 ~~~ { "type": "scrollTo", "rect": { "top": 470, "left": 8, "width": 359, "height": 400 }, "scroll": { "top": 189.5, "left": 0 }, "tag": "div" } ~~~ &emsp;&emsp;tag 是元素類型,例如 div、button、window 等;type 是事件類型,例如點擊、滾動等;rect 是坐標和尺寸,scroll 是滾動距離。 &emsp;&emsp;這種結構就可以順利存儲到數據庫中了。 **2)監控行為** &emsp;&emsp;目前實驗,就只監控了點擊和滾動兩種行為。 &emsp;&emsp;為 body 元素綁定 click 事件,采用捕獲的事件傳播方式。 ~~~ /** * 監控 body 內的點擊行為 */ document.body.addEventListener('click', (e) => { behaviors.push({ type: 'click', rect: offsetRect(e.target), tag: e.target.tagName.toLowerCase() }); }, true); ~~~ &emsp;&emsp;rect 的尺寸和坐標本來是通過[getBoundingClientRect()](https://developer.mozilla.org/zh-CN/docs/Web/API/Element/getBoundingClientRect)獲取的,但是該方法參照的是視口的左上角,也就是說會隨著滾動而改變坐標。 :-: ![](https://img.kancloud.cn/08/f7/08f7f10f7fcf5ad72fe28ff7ed1068b3_1466x1099.png =500x) &emsp;&emsp;所以就換了一種能更加精確獲取坐標的方法,如下所示,nodeMap 是一個 Map 數據結構,key 可以是一個元素對象,用于緩存計算過的元素坐標。 ~~~ // 元素緩存 const nodeMap = new Map(); /** * 讀取元素真實的坐標 */ function offsetRect(node) { // 從緩存中讀取node信息 const exist = nodeMap.get(node); if(exist) { return exist; } let top = 0, left = 0; const width = node.offsetWidth const height = node.offsetHeight; while (node) { top += node.offsetTop; left += node.offsetLeft; node = node.offsetParent; } const rect = { top, left, width, height }; nodeMap.set(node, rect); // 緩存node信息 return rect; } ~~~ &emsp;&emsp;下面是對滾動的監控代碼,throttle() 是一個節流函數,不節流會影響滾動的性能。 &emsp;&emsp;在 startScroll() 函數中會計算滾動條距離頂部和左邊的距離,window 和元素讀取的屬性略有不同。 ~~~ /** * 節流 */ function throttle(fn, wait) { let start = 0; return (e) => { const now = +new Date(); if (now - start > wait) { fn(e); start = now; } }; } /** * 對滾動節流 */ const startScroll = throttle((e) => { const target = e.target; let tag, rect, scroll; if(target.defaultView === window) { tag = 'window'; scroll = { top: window.pageYOffset, left: window.pageXOffset }; }else { tag = target.tagName.toLowerCase(); scroll = { top: target.scrollTop, left: target.scrollLeft }; rect = offsetRect(target); } behaviors.push({ type: 'scrollTo', rect, scroll, tag }); }, 100); /** * 監控頁面的滾動行為 */ window.addEventListener('scroll', (e) => { startScroll(e); }, true); ~~~ **3)還原** &emsp;&emsp;在得到數據結構后,就得讓其還原,呈現完成一系列動作后的頁面。 &emsp;&emsp;我寫的算法比較簡單,還有很大的優化空間。目前就是遍歷存儲的行為數組,然后深度優先搜索 body 內的所有子元素。 &emsp;&emsp;當坐標和尺寸滿足條件時,返回元素。不過這種方式非常依賴這兩個參數,因此只要結構發生變化,那么動作就無法完成。 ~~~ function revert(behaviors) { let isFind = false; // 深度優先遍歷 const dfs = (node, target) => { if (!node) return; const rect = offsetRect(node); const tag = node.tagName.toLowerCase(); // console.log(node, rect, target) // 根據坐標定位元素 if (target.tag === tag && target.rect.top === rect.top && target.rect.left === rect.left && target.rect.width === rect.width && target.rect.height === rect.height) { target.node = node; //記錄元素 isFind = true; return; } node.children && Array.from(node.children).forEach((value) => { if (isFind) { return; } dfs(value, target); }); }; behaviors.forEach(item => { isFind = false; // window對象單獨處理 if(item.tag === 'window') { item.node = window; }else { dfs(document.body, item); } const { node } = item; // 沒有找到符合要求的元素 if(!node) return; switch(item.type) { case 'scrollTo': // 滾動 node.scrollTo({ ...item.scroll, behavior: 'smooth' }); break; default: // 其他事件 node[item.type](); break; } }); } ~~~ &emsp;&emsp;scrollTo() 是一個滾動的方法,smooth 是一種平滑選項,奇怪的是,當我去掉此選項時,滾動就無法完成了。 **4)截圖** &emsp;&emsp;本來是計劃用腳本來實現截圖的,可選的庫是[dom-to-image](https://github.com/tsayen/dom-to-image)和[html2canvas](https://html2canvas.hertzen.com/)。 &emsp;&emsp;但是測試下來得到的截圖結果都不是很理想,于是就仍然采用 puppeteer 來實現截圖。 &emsp;&emsp;先將行為腳本注入,然后等幾秒,最后再截圖。這種截圖得到的結果比較準確,但就是執行過程有點慢,經常需要十幾秒甚至更長。 ~~~ await page.evaluate(async () => { const scrpt = document.createElement("script"); scrpt.src = "xx.js"; document.body.appendChild(scrpt); }); await page.waitForTimeout(2000); await page.screenshot({ path: `xx/1.png`, type: "png" }); ~~~ &emsp;&emsp;兩張截圖的對比可以通過[pixelmatch](https://github.com/mapbox/pixelmatch)完成,下面是官方提供的 node.js 使用示例,[pngjs](https://github.com/lukeapage/pngjs)是一個 png 圖像編解碼器。 ~~~ const fs = require('fs'); const PNG = require('pngjs').PNG; const pixelmatch = require('pixelmatch'); const img1 = PNG.sync.read(fs.readFileSync('img1.png')); const img2 = PNG.sync.read(fs.readFileSync('img2.png')); const {width, height} = img1; const diff = new PNG({width, height}); pixelmatch(img1.data, img2.data, diff.data, width, height, {threshold: 0.1}); fs.writeFileSync('diff.png', PNG.sync.write(diff)); ~~~ ***** > 原文出處: [博客園-Node.js躬行記](https://www.cnblogs.com/strick/category/1688575.html) [知乎專欄-Node.js躬行記](https://zhuanlan.zhihu.com/pwnode) 已建立一個微信前端交流群,如要進群,請先加微信號freedom20180706或掃描下面的二維碼,請求中需注明“看云加群”,在通過請求后就會把你拉進來。還搜集整理了一套[面試資料](https://github.com/pwstrick/daily),歡迎瀏覽。 ![](https://box.kancloud.cn/2e1f8ecf9512ecdd2fcaae8250e7d48a_430x430.jpg =200x200) 推薦一款前端監控腳本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不僅能監控前端的錯誤、通信、打印等行為,還能計算各類性能參數,包括 FMP、LCP、FP 等。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看