NightwatchJs and GitHub Actions
Tags: testing, github, node, cicd
Published: 2021-06-19 16:28:44

## NightwatchJs
Nightwatch.js is an integrated, easy to use End-to-End testing solution for web applications and websites, written in Node.js. It uses the W3C WebDriver API to drive browsers in order to perform commands and assertions on DOM elements.

## GitHub Actions
GitHub Actions makes it easy to automate all your software workflows, now with world-class CI/CD. You can build, test and deploy your code right from GitHub.

GitHub Actions is available with GitHub Free, GitHub Pro, GitHub Free for organizations, GitHub Team, GitHub Enterprise Cloud, GitHub Enterprise Server, and GitHub AE. GitHub Actions is not available for private repositories owned by accounts using legacy per-repository plans. For more information, see "[GitHub's products](https://docs.github.com/en/articles/github-s-products)."

CI using GitHub Actions offers workflows that can build the code in your repository and run your tests. Workflows can run on GitHub-hosted virtual machines, or on machines that you host yourself.


## Setup CI/CD
At this point, I assume that you've a working project to test and configure CI for. I'm using this project for demonstration [Moving Todo](https://github.com/kiranparajuli589/Moving-Todo). It is a Django application and tests are written in NightwatchJs.

Now, lets plat our CI configurations:
- First, we've to choose an os for CI build
- Second, we've to install dependencies inside the build
- Next, we've to start server to test
- At last, we've to run the tests and make it pass.

### Webdriver
Personally I do not suggest using jar files for selenium. Rather use docker to start selenium as:
```
docker run --rm -d --network="host" --name selenium -p4444:4444 selenium/standalone-chrome
```

Alternatively, you don't have to bother anything about starting and stopping selenium server. Using npm package manages all of that.

- Have the following packages in your `packages.json` file:
```json
  "dependencies": {
    "@cucumber/cucumber": "^7.3.0",
    "axios": "^0.21.1",
    "chromedriver": "^91.0.0",
    "cucumber": ">=6.0.5",
    "cucumber-pretty": "^6.0.0",
    "geckodriver": "^2.0.0",
    "iedriver": "^3.150.1",
    "nightwatch": "^1.3.4",
    "nightwatch-api": "^3.0.1",
    "selenium-server": "^3.141.59"
  },
```
- Configure nightwatch:
```js
module.exports = {
  src_folders: ["tests"],
  // custom_commands_path: ["test/custom_commands"],
  page_objects_path: "./tests/acceptance/pageObjects",

  test_settings: {
    default: {
      launch_url: process.env.BASE_URL,
    },
    selenium: {
      selenium: {
        start_process: true,
        server_path: require("selenium-server").path,
        port: 4444,
        cli_args: {
          "webdriver.gecko.driver": require("geckodriver").path,
          "webdriver.chrome.driver": require("chromedriver").path,
        },
      },
      webdriver: {
        start_process: false,
      },
    },

    chrome: {
      extends: "selenium",
      desiredCapabilities: {
        browserName: "chrome",
        chromeOptions: {
          args: ["--headless", "--no-sandbox", "--disable-gpu"],
          w3c: false,
        },
      },
    },

    firefox: {
      extends: "selenium",
      desiredCapabilities: {
        browserName: "firefox",
        "moz:firefoxOptions": {
          args: ["--headless"],
        },
      },
    },
  },
};
```
- configure cucumber.conf.js
```js
const { setDefaultTimeout, After, Before, BeforeAll, AfterAll } = require("cucumber");
const { createSession, closeSession, startWebDriver, stopWebDriver } = require("nightwatch-api");
const axios = require("axios").default;

setDefaultTimeout(60000);
const availableBrowsers = ["chrome", "firefox"];

const browser = process.env.BROWSER || "chrome";
if (!availableBrowsers.includes(browser)) {
    throw new Error(
    '\nInvalid browser selected.\n' +
    'Available browsers: ' +
    ${availableBrowsers.join(", ")} + '\n'
  );
}

BeforeAll(async () => {
  await startWebDriver({ env: browser });
});

Before(async () => {
  await createSession({ env: browser });
});

After(async () => {
  await closeSession();
});

AfterAll(async () => {
  await stopWebDriver();
});
```
- Assumed that tests files are inside `tests` directory, Now add script to run tests inside your `package.json` file:
```json
"scripts": {
  "test:e2e": "cucumber-js --require cucumber.conf.js --require tests/acceptance/stepDefinitions --format node_modules/cucumber-pretty"
}
```

### Setup workflow
First, touch a file inside `.github/workflows` naming `main.yml`. GitHub uses this file as configuration file for CI build.

Now as our plan lets setup our CI:

```yml
name: CI

on:
  push:
    branches: [master]
  pull_request:
    branches: [master]

  workflow_dispatch:

jobs:
  build:
    name: webUi-${{ matrix.browser }}
    runs-on: ubuntu-latest

    strategy:
      matrix:
        browser: ["chrome", "firefox"]

    steps:
      - uses: actions/checkout@v2

      - name: yarn-install
        run: yarn install

      - name: pip-install
        run: pip install -r requirements.txt

      - name: setup-server
        run: |
          make migrate
          make create-admin
          make serve &

      - name: e2e-test
        env:
          BROWSER: ${{ matrix.browser }}
        run: |
          yarn test:e2e
```
### Check your builds
Push the changes adding the `main.yml` to your repository and navigate to `Actions` tab in your repository, you can see the started build.
Inspect it, fix it, its all yours.


### What I love about GitHub CI
- you can run `sudo` commands easily
- you can easily use docker containers
- you can pipeline your tests into multiple parallel builds