[Puppeteer][Mocha] Upgrade your implementation code with coverage.
Tags: javascript, testing, tutorial, coverage
Published: 2019-08-21 07:49:23

### Test pyramid
Since [Puppeteer](https://developers.google.com/web/tools/puppeteer/) releases, end-2-end testing becomes a fast and reliable way for testing features. Most things that you can do manually in the browser can be done using Puppeteer. Even more, headless Chrome reduces the performance overhead and native access to the DevTools Protocol makes Puppeteer awesome. Imagine, each time we develop front-end we just check the final view in the browser, and without TDD we faced with Test Pyramid Ice cream Anti-pattern ![Software testing Ice cream Anti-pattern](https://images.contentful.com/3cttzl4i3k1h/6Qc0lWQ4IE4KeisAgyeUAC/365239996e6c8e83afc5fd8fbce69831/automation-pyramid-2.png) But we like Ice cream, so why we need a trade-off? I show you how to upgrade your sources with tests no matter what engine your use with confidence that your application works as expected because Puppeteer will check Features instead of you.
### Setup
I have complete step-by-step instruction [README.md](https://github.com/storenth/web-developer-toolkit#feature-testing) based on a simple [project](https://github.com/hex25/web-developer-toolkit) I forked from and had provided it with feature-rich test upgrade to show off. So, if you have one other please:
1) Install dependencies in your root 
`npm i puppeteer mocha puppeteer-to-istanbul nyc -D`
2) Expose your instance on endpoint (my light solution for `*.html` [http-server](https://www.npmjs.com/package/http-server))
3) Create `test` directory and fill `{yourFeature}_test.js` with next suitable template (note **_before_** and **_after_** hooks), try to extend it with your project-specific selectors and behaviors:
```javascript
const puppeteer = require('puppeteer');
const pti = require('puppeteer-to-istanbul');
const assert = require('assert');

/**
 * ./test/script_test.js
 * @name Feature testing
 * @desc Create Chrome instance and interact with page.
 */

let browser;
let page;

describe('Feature one...', () => {
    before(async () => {
        // Create browser instance
        browser = await puppeteer.launch()
        page = await browser.newPage()
        await page.setViewport({ width: 1280, height: 800 });
        // Enable both JavaScript and CSS coverage
        await Promise.all([
            page.coverage.startJSCoverage(),
            page.coverage.startCSSCoverage()
          ]);
        // Endpoint to emulate feature-isolated environment
        await page.goto('http://localhost:8080', { waitUntil: 'networkidle2' });
    });
    // First Test-suit
    describe('Visual regress', () => {
        it('title contain `Some Title`', async () => {
            // Setup
            let expected = 'Some Title';
            // Execute
            let title = await page.title();
            // Verify
            assert.equal(title, expected);
        }).timeout(50000);

    });
    // Second Test-suit
    describe('E2E testing', () => {
        it('Some button clickable', async () => {
            // Setup
            let expected = true;
            let expectedCssLocator = '#someIdSelector';
            let actual;
            // Execute
            let actualPromise = await page.waitForSelector(expectedCssLocator);
            if (actualPromise != null) {
                await page.click(expectedCssLocator);
                actual = true;
            }
            else
                actual = false;
            // Verify
            assert.equal(actual, expected);
        }).timeout(50000);
    // Save coverage and close browser context
    after(async () => {
        // Disable both JavaScript and CSS coverage
        const jsCoverage = await page.coverage.stopJSCoverage();
        await page.coverage.stopCSSCoverage();

        let totalBytes = 0;
        let usedBytes = 0;
        const coverage = [...jsCoverage];
        for (const entry of coverage) {
            totalBytes += entry.text.length;
            console.log(`js fileName covered: ${entry.url}`);
            for (const range of entry.ranges)
                usedBytes += range.end - range.start - 1;
        }
        // log original byte-based coverage
        console.log(`Bytes used: ${usedBytes / totalBytes * 100}%`);
        pti.write(jsCoverage);
        // Close browser instance
        await browser.close();
    });
});
``` 
### Execute
1. Run your test described above against scripts on endpoint with `mocha` command
2. Get coverage collected while test run with `nyc report`.
3. I suggest you instrument your `package.json` with the next scripts makes it super easy to run tasks like `npm test` or `npm run coverage` 

```
  "scripts": {
    "pretest": "rm -rf coverage && rm -rf .nyc_output",
    "test": "mocha --timeout 5000 test/**/*_test.js",
    "server": "http-server ./public",
    "coverage": "nyc report --reporter=html"
  },
```
### Coverage
In **my [project](https://github.com/storenth/web-developer-toolkit)** I have coverage about 62% ![Code coverage](https://github.com/storenth/web-developer-toolkit/blob/master/output/code_coverage_2.png?raw=true)
We can report it as `html` and look closer ![html coverage report](https://github.com/storenth/web-developer-toolkit/blob/master/output/html_coverage_report.png?raw=true)
You can see that **_Branches_** and **_Functions_** both 100% covered. While I tested Puppeteer coverage feature (like [Coverage devTool](https://developers.google.com/web/updates/2017/04/devtools-release-notes#coverage)) I filed that bug
{% github https://github.com/istanbuljs/puppeteer-to-istanbul/issues/22 %}  
### Unit vs E2E testing wars
Someone who has enough passion to research without getting bored I tell what, we need more attention to unit testing frameworks like [Mocha](https://mochajs.org) used to write tests from _Unit_ to _Acceptance_ but not to unit or end tests themselves. No matter what test you write if your codebase covered I think. Times have changed. Now, with coverage available feature other tools like the _Traceability matrix_ as a measure of quality look ugly, because stakeholders still need to believe in tester's word. But pay attention to the [forth best practices in the domain of code coverage](https://testing.googleblog.com/2020/08/code-coverage-best-practices.html) from Google to use coverage information pragmatically.
### Contribute
I strongly recommend take some time and review **my github-working-draft [project](https://github.com/storenth/web-developer-toolkit)** before you will stuck.
I will appreciate any collaboration and feedback. Feel free to reach me any questions.