Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/eklem/batr

Bundle and test CommonJS and ESM in NodeJS and UMD in the browser with AvaJS and Playwright. And repeat with i.e GitHub CI.
https://github.com/eklem/batr

ava-tests bundle commonjs continuous-integration esm nodejs ui-testing umd

Last synced: 22 days ago
JSON representation

Bundle and test CommonJS and ESM in NodeJS and UMD in the browser with AvaJS and Playwright. And repeat with i.e GitHub CI.

Awesome Lists containing this project

README

        

# batr
**B**undle **A**nd **T**est ... and **R**epeat

![batr-logo](https://user-images.githubusercontent.com/236656/115827172-3757dd00-a40c-11eb-9687-70bb6e623d2b.png)

Bundle and test CommonJS and ESM in NodeJS and UMD in the browser with Rollup, AvaJS and Playwright. And repeat with GitHub Actions workflow.

A little blogpost on why I thought Batr was a good idea: [Test setup for JavaScript/web development with less stress and pain— My solution so far: Batr](https://blogg.knowit.no/solutions-no/test-setup-for-javascript/web-development-with-less-stress-and-pain-my-solution-so-far-batr).

I'm using AvaJS since I want a simple enough test framework and don't want to be too smart about assertions. The needs are not that big. For UI tests it's good to be a little repetitive. If you want to test a sequence of interactions A, B, C and D, then test them all synchronously in one go. You'll get to test the transition between the interactions and that the result of interaction A, doesn't screw up interaction B and so on.

[![NPM version](http://img.shields.io/npm/v/batr.svg?style=flat)](https://npmjs.org/package/batr)
[![NPM downloads](http://img.shields.io/npm/dm/batr.svg?style=flat)](https://npmjs.org/package/batr)
[![Build Status](https://github.com/eklem/batr/actions/workflows/tests.yml/badge.svg)](https://github.com/eklem/batr/actions/workflows/tests.yml)
[![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)

## Example setup
For an actual working example, check out [batr-example](http://github.com/eklem/batr-example) on how to use batr. It's an example library with minimal of functions and user-interface to show-case how to set up `batr`. The examples here are lifted from that library.

## Libraries used:
* [AvaJS](https://github.com/avajs/ava)
* [Playwright](https://playwright.dev/docs/intro)
* [Rollup](https://rollupjs.org/guide/en/) + plugins `@rollup/plugin-commonjs`, `@rollup/plugin-json` and `@rollup/plugin-node-resolve`
* [StandardJS](https://standardjs.com/)

**Integrations**
* Using [GitHub Actions workflow](https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions) for continuous integration.

## Get started

### Add batr devDependency
All the dependencies in one. Security updates and version bumps done mostly at the start of every month, so less GitHub dependabot noise.

```javaScript
"devDependencies": {
"batr": "^1.0.5"
}
```
The underlying libraries are used (required and imported) as normal.

### Define main, module and browser
* `main` - CJS - CommonJS
* `module` - ESM - ES Modules
* `browser` - UMD - Universal Module Definitions

```javaScript
"main": "./dist/batr-example.cjs.js",
"module": "./dist/batr-example.esm.mjs",
"browser": "./dist/batr-example.umd.js",
```

Makes pointers to which files are used for what. Used i.e. when bundling correct distribution files with Rollup and to use the correct file when doing `const moduleName = require('moduleName')` or `import moduleName from "moduleName"`.

### Tests

#### Build/bundle and tests from package.json

```javaScript
"scripts": {
"build": "rollup --config",
"test": "standard './*.js' && npm run build && npx ava ./test/test.cjs.js && npx ava ./test/test.esm.mjs && npx ava ./test/ui-test.js"
}
```

#### Rollup config for bundling CJS, ESM and UMD

```javaScript
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import json from '@rollup/plugin-json'
import pkg from './package.json'

export default [
// browser-friendly UMD build
// CommonJS (for Node) and ES module (for bundlers) build.
// (We could have three entries in the configuration array
// instead of two, but it's quicker to generate multiple
// builds from a single configuration where possible, using
// an array for the `output` option, where we can specify
// `file` and `format` for each target)
{
input: './src/index.js',
output: [
{ name: 'math', file: pkg.browser, format: 'umd', exports: 'named' },
{ file: pkg.main, format: 'cjs' },
{ file: pkg.module, format: 'es' }
],
plugins: [
resolve(), // so Rollup can find `ms`
commonjs(), // so Rollup can convert `ms` to an ES module
json() // for Rollup to be able to read content from package.json
]
}
]
```

#### Actual test scripts

##### Main - ./dist/batr-example.cjs.js
```javaScript
const test = require('ava')
const { add, subtract, multiply, divide } = require('../dist/batr-example.cjs.js')

test('addition a + b', (t) => {
const expected = 31
const addition = add(7, 24)
t.deepEqual(addition, expected)
})

test('subtraction a - b', (t) => {
const expected = -17
const subtraction = subtract(7, 24)
t.deepEqual(subtraction, expected)
})

test('multiplication a * b', (t) => {
const expected = 168
const multiplication = multiply(7, 24)
t.deepEqual(multiplication, expected)
})

test('division a * b', (t) => {
const expected = 0.2916666666666667
const division = divide(7, 24)
t.deepEqual(division, expected)
})
```

##### Module - ./dist/batr-example.esm.mjs
Same tests as for `Main`, just using `import` instead of `require`.
```javaScript
import test from 'ava'
import { add, subtract, multiply, divide } from '../dist/batr-example.esm.mjs'

// Tests are identical to Main/CJS tests
})
```

##### Browser - ./dist/ui-test.js
Similar tests, but done through recorded user interactions in a browser. You recorded with `playwright codegen`. Create your prototype and do something like this:
```console
npx playwright codegen -o javascript index.html
```

[Playwright has good documentation on how to record](https://playwright.dev/docs/codegen#generate-tests) user interactions and generating test-code for different programming languages. I'm guessing it's good practice to swap some of the HTML references with a little more solid CSS selectors so that the tests won't fail becuase of small HTML changes.

To see more of what's going on you can set `healess: false` and slow it down with `sloMo: 500`, but it will fail if you try it on i.e. a server, since there it's running headless.

Also, you can test with different browsers or more than one browser, and emulate devices like an Iphone.

```javaScript
const { chromium } = require('playwright')
const test = require('ava')
const browserPromise = chromium.launch({
headless: true
// slowMo: 500
})

const path = require('path')
async function pageMacro (t, callback) {
const browser = await browserPromise
const page = await browser.newPage()
await page.setViewportSize({ width: 640, height: 480 })
try {
await callback(t, page)
} finally {
await page.close()
}
}

test('Add numbers 4 and 7, subtract 7 from 4, multiply 4 and finally divide 4 by 7', pageMacro, async (t, page) => {
// t.plan(4)
const filePath = await path.resolve('./demo/index.html')
const url = 'file://' + filePath

// Go to ./index.html
await page.goto(url)

// Click first number input field and delete
await page.click('#firstNumber')
await page.keyboard.press('Backspace')

// Type number
await page.keyboard.type('4')

// Press Tab twice to get to next number
await page.keyboard.press('Tab')
await page.keyboard.press('Tab')

// Fill #secondNumber
await page.keyboard.type('7')

// Press Tab with modifiers
await page.press('#secondNumber', 'Shift+Tab')

// screenshot, 1st task
await page.screenshot({ path: './screenshots/screenshot-01.png' })

// Test that 4 + 7 gives 11
t.deepEqual(await page.textContent('#result span'), '11')

// Select subtract
await page.selectOption('select[name="calculation"]', 'subtract')

// screenshot, 2nd task
await page.screenshot({ path: './screenshots/screenshot-02.png' })

// Test that 4 - 7 gives -3
t.deepEqual(await page.textContent('#result span'), '-3')

// Select multiply
await page.selectOption('select[name="calculation"]', 'multiply')

// screenshot, 3rd task
await page.screenshot({ path: './screenshots/screenshot-03.png' })

// Test that 3 * 11 gives 28
t.deepEqual(await page.textContent('#result span'), '28')

// Select divide
await page.selectOption('select[name="calculation"]', 'divide')

// screenshot, 4th task
await page.screenshot({ path: './screenshots/screenshot-04.png' })

// Test that 4 / 7 gives 0.5714285714285714
t.deepEqual(await page.textContent('#result span'), '0.5714285714285714')
})
```

#### Continuous integration with GitHub Actions workflow
`ubuntu-latest` is easy going, but you can test OSX and Windows too. Check [GitHubs runs-on documentiation](https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idruns-on).
`.github/workflows/tests.yml`:
```yml
name: tests
on:
- push
- pull_request
jobs:
run-tests:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x, 14.x, 16.x]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: sudo apt-get install xvfb
- run: xvfb-run --auto-servernum npm test
```

## Background and goal
* Use less time on updating the same bundle and test framework code in different libraries.
* Quicker bundling and test setup when creating new libraries.
* As few dependencies as possible, or a good balance between dependencies and function, to not have minor updates all the time.
* New NPM release every month, meaning less noise from Dependabot. Batr + dependencies will only be devDependencies, and security issues will not be a big problem.

### Easy setup of
* Ava tests in Node.js
* Possibly duplicat Ava tests in browser
* User-like interaction tests in browser, supported by Ava
* Bundling & buildin g for the browser, CommonJS and ESM