Gremlins testing with puppeteer
Tags: testing, javascript, webdev
Published: 2022-01-03 21:45:15

## Introduction

The idea behind gremlins testing is pretty simple: any user action on your application should not have throw a runtime error. It's like a smoke test with lots of random user's actions. This should be helpful when your project has unit tests but you're not ready yet for end to end tests.

## Implementation

- Install the lib: `yarn add gremlins.js -D`
- Copy gremlins to hosted directory (e.g. dist) `cp node_modules/gremlins.js/dist/gremlins.min.js dist`
- Open your app in a browser
- In the browser console add gremlins to DOM `const s = document.createElement('script'); s.src='gremlins.min.js'; document.head.appendChild(s);`
- Run the gremlins gremlins.createHorde().unleash()
- In result gremlins will execute buch od random events

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d9iwajxv60traq3u0fsa.gif)

If in console there's no error test passes

## Automatisation

Of course we can not run it manually each time. To automate it we can use the puppeteer, it's a nodeJS chromium driver. After installation `yarn add puppeteer -D` do something like this:
```javascript
// run-gremlins.js
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  const errors = [];
  page.on('pageerror', (error) => {
    browserErrors.push(error);
  });
  await page.goto('localhost:4200');
  if (browserErrors.length !== 0) {
    errors.forEach((e) => {
      console.error('e2e error: ', e);
    });
    process.exit(1);
  }
  
  // TODO: insert gremlins
  // TODO: run gremlins
  // TODO: wait 5s
  
  await browser.close();
})();
```

This code will run a browser (in incognito mode to have cookies cleared), open new a page on `localhost:4200`, run gremlins like in the previous part and if any page error appears process will close with falsy status.

#### missing parts:

```javascript
module.exports.insertGremlins = async (page) => {
  await page.evaluate(() => {
    const s = document.createElement('script');
    s.src='gremlins.min.js'; 
    document.head.appendChild(s);
  });
};

module.exports.runGremlins = async (page) => {
  await page.evaluate(() => {
    window.gremlins.createHorde().unleash()
  });
};

module.exports.wait = (time) =>
  new Promise((resolve) => {
    setTimeout(resolve, time);
  });
```

## Verification

To be sure that gremlins are gonna catch an error, add something like this to the app: `setTimeout(() => { throw new Error('debug'); }, 3000);` . If command node `run-gremlins.js` will exit with falsy code then the tester works correctly :)

## Run on docker

Best way to be sure that tester is working on other environment (like CI) is wrap it into a docker container. Unfortunately we need to install some dependencies in the container.

```dockerfile
FROM node:16.13.1 # Specific version, not the lastest

RUN ["apt-get", "update"]
RUN apt-get install gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils -y
RUN apt-get update \
     && apt-get install -y wget --no-install-recommends \
     && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
     && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
     && apt-get update \
     && apt-get install -y google-chrome-unstable --no-install-recommends \
     && rm -rf /var/lib/apt/lists/* \
     && wget --quiet https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh -O /usr/sbin/wait-for-it.sh \
     && chmod +x /usr/sbin/wait-for-it.sh
```

To allow `root` user run puppeteer add `--no-sandbox` flag into `run-gremlins.js` file:

```javascript
const browser = await puppeteer.launch({
  args: ['--no-sandbox'],
});
```

In this case your local web application has to run in the same container, however you can add `--network=host` to the previous command to allow gremlin-tester to communicate with a localhost.

# Conclusion

I'm using it, it saves a lot of my time. Maybe someone else will to take advantage of it.