撩測試MM神器cypress使用入門

不好久不好久之前

聽說某家公司有兩位前端,每天擼bug,爲啥嘞?只怪測試MM傾人國,輕語哥哥有bug。✧(๑•̀ㅂ•́)و✧ 但是最近兩位有點犯愁 Σ(っ °Д °;)っ。測試MM有幾回提了緊急bug,都在旁邊鼓勵他們改bug了,但是線上bug重現排查比較麻煩,並且改了後還發現沒改好,惹得測試MM潸然淚下,好生埋汰。怎麼辦呢?javascript

前端君666某天發現了E2E測試神器cypress後,暗中偷練神功,改bug愈來愈6,測試MM天天笑着對他說,666你真6,MM好喜歡呀(๑•́ ₃ •̀๑) 另外一位前端君555天天面對堆積如山的bug唉聲嘆氣,測試MM提完新bug後都不理他了≡ ̄﹏ ̄≡html

做爲一個追求代碼永無bug、順帶跟測試MM溝通產品的有理想的前端 (ง •̀_•́)ง,我以爲有必要學習一下怎麼使用cypress來進行E2E測試,以此來提升代碼質量。那麼咱們來看看怎麼入門cypress測試框架。前端

cypress三問 - 你是誰

cypress是在mocha式API基礎上構建的一套開箱可用的E2E測試框架,對比其餘測試框架,它提供一套本身的最佳實踐方案,無需其餘測試工具庫,配置方便簡單但功能異常強大,可使用webpack項目配置,還提供了一個強大的GUI圖形工具。入門簡單,上手方便,怎麼舒服怎麼來呀 (。→‿←。)java

cypressGUI方式的測試使用真實瀏覽器,非GUI方式使用chrome-headless,不是用模擬方式進行測試,更真實的展示實際環境中的測試過程和結果。node

cypress三問 - 你有啥優點

cypress有幾大自帶的強大功能:webpack

  • 自帶GUI工具,想測啥就點啥,還能夠查看整個測試過程,想錄屏還能夠錄屏喲(錄屏能夠發給測試MM看,保準她說哥哥真厲害喲。 通常人我不告訴他๑乛◡乛๑)
  • 測試的每一步都有snapshot,能夠經過GUI工具查看每一個過程的頁面狀態,不是截圖而是真是的頁面DOM環境喲!
  • 自帶數據mock和請求攔截機制,還原線上數據引發的bug別提有多輕鬆了
  • 和wepbakc配置,實現不管修改測試文件仍是被測試代碼均可以自動重測git

    • 小Tips:能夠給測試用例加上only或者skip來避免重測測試文件裏的全部用例: it.only('只測試這個喲); it.skip('不要測這個');

cypress三問 - 怎麼用

安裝

  • yarn add cypress 或者 npm install cypress
  • 安裝完畢後,./node_modules/.bin/cypress install安裝cypress環境(包括GUI工具)

配置

  • package.json: 配置GUI和非GUI(terminal)兩種方式來運行cypress
"scripts": {
        "cypress": "cypress run",
        "cypress-gui": "cypress open",

⚠️ 配置好後 先運行 yarn cypress[-gui] 或者 npm run cypress[-gui](中括號意思是可選)來初始化cypress,__生成默認配置和目錄__github

  • cypress.json(與package.json同級目錄): cypress提供比較靈活的配置,能夠根據本身須要定製行爲,如下列一下我對一個項目的配置
{
    "baseUrl": "http://localhost:8080", // 本地開發服務地址(webpack-dev-server)
    "integrationFolder": "src", // 自定義"src"爲測試文件根目錄,默認是"cypress/integration"
    "testFiles": "**/*.cypress.spec.js", // 自定義測試文件的匹配正則,默認是"**/*.*",即全部文件
    "videoRecording": false, // 關閉錄屏功能, 若是開啓錄屏功能,記得將"cypress/screenshots"目錄加入".gitignore",防止不當心將錄屏加到git中
    "viewportHeight": 800, // 設置測試環境的頁面視圖的高度
    "viewportWidth": 1600 // 設置測試環境的頁面視圖的寬度
}
  • cypress/plugins/index.js: cypress運行環境配置,能夠用來配置webpack等。如下是配置webpack別名範例。默認這裏不須要配置。
// 參考官方例子地址 https://github.com/cypress-io/cypress-example-recipes/blob/master/examples/preprocessors__typescript-webpack/cypress/plugins/index.js
const wp = require("@cypress/webpack-preprocessor");
const path = require('path');

function resolve(dir) {
    return path.join(__dirname, "../..", dir);
}

module.exports = on => {
    const options = {
        webpackOptions: {
            resolve: {
                alias: {
                    "@": resolve("src"),
                    cypress: resolve("cypress")
                }
            }
        }
    };
    on("file:preprocessor", wp(options));
};

萬事俱備,測測測

  • 簡單的一個例子
describe('測試頁面包含某元素', () => {
    it('有云 "前端哥哥們真帥,前端妹妹們真漂亮"', () => {
        cy.contains("前端哥哥們真帥,前端妹妹們真漂亮");
    });

    it('要有一個連接', () => {
        cy.get('a').should('have.length', 1);
    });

    it('不存在class含有abc的元素', () => {
        cy.get('.abc').should('have.length', 0);
    });
});
  • 互動的例子
describe('一塊兒動', () => {
    it('獲取輸入框,輸入文字並按enter鍵', () => {
        const text = 'not exist';
        // type api用法: https://docs.cypress.io/api/commands/type.html#Usage
        cy.get('input').type(`${text}{enter}`);
    });

    it('點擊按鈕', () => {
        cy.get('button').click();
    });
});
  • 網絡請求mock例子
Tip1: cy.route的路徑匹配是嚴格的,因此要注意是否須要加通配符。如 cy.route('/api/search', [])不會攔截 /api/search?keyword=abc,只會攔截 /api/search

Tip2: cy.route的method要注意,默認是GETcy.route('/api/posts')cy.route('POST', '/api/posts') 是不同的。web

describe('要啥給啥', () => {
     beforeEach(() => {
        cy.server(); // 必定要在 cy.route 前調用
        cy
            .fixture('/posts/list.json') // 咱們在 cypress/fixtures 內建立mock用的數據
            .as('postsData'); // 給 mock 數據取別名,之後 cy.route 使用
        cy
            .route('/api/posts', '@postsData')
            .as('getPostsRoute'); // 給請求取別名,以供 cy.wait 使用
    })

    it('進入列表頁,攔截列表請求接口', () => {
        cy.wait('@getPostsRoute'); // 等待被攔截的接口請求完成

        cy.get('.post').should('have.length', 10); // 要有10條數據被渲染到頁面上
    });
})
  • 實際場景例子: 結合上面全部姿式,咱們如今測試搜索頁面的搜索、操做結果
describe('test search page', () => {
    // 幾個 route 路徑變量
    const searchRoutePath = '/api/items/activities?query=*';
    const deleteActivityRoutePath = '/api/activities/*/items/batch?num_iids[]=*';
    const undoActivityRoutePath = '/api/activities/*/items/undo';

    function search(keyword) {
        // 將搜索行爲和等待搜索返回封裝起來
        cy
            .fixture('items/activities.json')
            // 處理mock數據,只返回符合搜索結構的數據
            .then(data => data.filter(item => item.title.indexOf(keyword) !== -1))
            .as('searchResult');
        cy.server();
        cy.route(searchRoutePath, '@searchResult').as('searchRoute');

        const input = cy.get('input');
        input.clear(); // 清空輸入框內文本

        input.type(`${keyword}{enter}`);

        cy.wait('@searchRoute');
    }

    before(() => {
        // 進行全部測試前,先訪問搜索頁
        cy.visit('/activities/search');
    });

    it('should show no data tip when search result is empty', () => {
        const text = 'not exist';
        search(text);
        cy.contains(`沒有找到關於 ${text} 的結果`);
    });

    it('should remove activity from list when clean successful', () => {
        search('成功');

        cy
            .route('delete', deleteActivityRoutePath, {
                success: 0,
                fail: 0,
                waiting: 0,
            })
            .as('deleteActivityResponse');

        // within是讓cy執行的context保持在'.activities-search'這個dom節點內
        // 默認cy的執行是以上一個cy命令結果做爲context
        // 如 "cy.get('a'); cy.get('span')",cy會在上一個命令找到的'a'標籤中查找'span'
        cy.get('.activities-search').within(() => {
            const items = cy.get('.result-item');
            items.should('have.length', 1);
            const applyList = items.get('.apply-list');

            applyList.should('not.be.visible'); // 每一個數據項內詳細內容區域是隱藏的

            const toggleBtn = items.get('.item-apply-count');
            toggleBtn.click(); // 點擊顯示詳細內容區
            applyList.should('be.visible');
            applyList.children().should('have.length', 1); // 詳細內容區內數據只有1條

            const cleanBtn = cy.contains('退出');
            cleanBtn.click(); // 點擊詳細內容區裏的「退出」按鈕

            cy.wait('@deleteActivityResponse'); // 等待「退出」請求返回
            cy.get('.apply-list').should('be', null); // 退出成功後,詳細內容區數據減1,即空
        });
    });
});

幾個必讀文檔

關於測試覆蓋率

目前cypress沒有內置測試覆蓋率統計功能,github上有專門的issue在跟蹤這個,後續應該會有。issue上也有幾個臨時方案,目前我傾向使用chrome自帶的來查看。在GUI打開的測試的瀏覽器中打開devtools,切到Sources, 按下cmd+shift+p(windows用戶按ctrl+shift+p),輸入coverage,選擇從新刷新並統計代碼執行覆蓋率。chrome

untitled4

那麼,high起來

爲了高(撩)質(測)量(試)代(M)碼(M),high起來。喜歡前端MM的能夠手把手教起來了 (¬_¬)


本文章首發於本人公衆號:楓之葉。有興趣的能夠長按下方二維碼關注。^v^

圖片描述

相關文章
相關標籤/搜索