Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

撩测试MM神器cypress使用入门 #4

Open
maple-leaf opened this issue Apr 19, 2018 · 0 comments
Open

撩测试MM神器cypress使用入门 #4

maple-leaf opened this issue Apr 19, 2018 · 0 comments

Comments

@maple-leaf
Copy link
Owner

maple-leaf commented Apr 19, 2018

不很久不很久以前

据说某家公司有两位前端,天天撸bug,为啥嘞?只怪测试MM倾人国,轻语哥哥有bug。✧(๑•̀ㅂ•́)و✧ 可是最近两位有点犯愁 Σ(っ °Д °;)っ。测试MM有几次提了紧急bug,都在旁边鼓励他们改bug了,可是线上bug重现排查比较麻烦,而且改了后还发现没改好,惹得测试MM潸然泪下,好生埋汰。怎么办呢?

前端君666某天发现了E2E测试神器cypress后,暗中偷练神功,改bug越来越6,测试MM每天笑着对他说,666你真6,MM好喜欢呀(๑•́ ₃ •̀๑) 另一位前端君555每天面对堆积如山的bug长吁短叹,测试MM提完新bug后都不理他了≡ ̄﹏ ̄≡

作为一个追求代码永无bug、顺带跟测试MM沟通产品的有理想的前端 (ง •̀_•́)ง,我觉得有必要学习一下怎么使用cypress来进行E2E测试,以此来提高代码质量。那么我们来看看怎么入门cypress测试框架。

cypress三问 - 你是谁

cypress是在mocha式API基础上构建的一套开箱可用的E2E测试框架,对比其他测试框架,它提供一套自己的最佳实践方案,无需其他测试工具库,配置方便简单但功能异常强大,可以使用webpack项目配置,还提供了一个强大的GUI图形工具。入门简单,上手方便,怎么舒服怎么来呀 (。→‿←。)

cypressGUI方式的测试使用真实浏览器,非GUI方式使用chrome-headless,不是用模拟方式进行测试,更真实的展现实际环境中的测试过程和结果。

cypress三问 - 你有啥优势

cypress有几大自带的强大功能:

  • 自带GUI工具,想测啥就点啥,还可以查看整个测试过程,想录屏还可以录屏哟(录屏可以发给测试MM看,保准她说哥哥真厉害哟。 一般人我不告诉他๑乛◡乛๑)
  • 测试的每一步都有snapshot,可以通过GUI工具查看每个过程的页面状态,不是截图而是真是的页面DOM环境哟!
  • 自带数据mock和请求拦截机制,还原线上数据引起的bug别提有多轻松了
  • 和wepbakc配置,实现无论修改测试文件还是被测试代码都可以自动重测
    • 小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生成默认配置和目录

  • 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') 是不一样的。

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,选择重新刷新并统计代码执行覆盖率。

untitled4

那么,high起来

为了高(撩)质(测)量(试)代(M)码(M),high起来。喜欢前端MM的可以手把手教起来了 (¬_¬)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant