Integration testing for web components

Posted by Ilya on July 09, 2022

When developing custom web components, it's useful to add integration tests for them. The idea of these tests is to run dev server, render the HTML test fixture on it (which can be different for different tests), run (preferrably headless) browser against it and do some actions and assertions.

Surprisingly, there is no 'de-facto standard' like, let say, protractor for end-to-end tests. I tried

  • jest + selenium
  • jest + enzyme
  • cypress
  • @web/test-runner + @open-wc/testing

The latter seems to be the easiest solution, so let stop on it.

Tests with @web/test-runner and @open-wc/testing

Installation

npm  i -D @web/test-runner @esm-bundle/chai @open-ws/testing

Than adjust package.json to add scripts for test run

{
...
"scripts": {
"test": "web-test-runner \"test/**/*.test.js\" --node-resolve",
"test:watch": "web-test-runner \"test/**/*.test.js\" --node-resolve --watch",
...
}
...
}

Headless chrome controlled by puppeteer is used by default to run tests, this behavior can be customized.

Writing the first test

I will use wired-combo to make the example. Let create file test/wired-combo.test.js

import { expect, fixture } from '@open-wc/testing';
import '../packages/wired-combo/lib/wired-combo';

describe('wired-combo', () => {
it('should runat least a simpliest test.... plz plz plz...', async () => {
const elementus = await fixture('<wired-combo></wired-combo>');
expect(elementus.shadowRoot).to.exist;
});
})

Here we check that the custom element renders its shadow DOM. Run

npm run test

It should output

> wired-elements-customization@1.0.0 test /home/ilya/wired-elements-customization
> web-test-runner "test/**/*.test.js" --node-resolve


Chrome: |██████████████████████████████| 2/2 test files | 1 passed, 0 failed

Finished running tests in 0.5s, all tests passed! 🎉

User interaction with the components

We should install one more lib to use helpers like sendKeys:

npm i -D @web/test-runner-commands

Let write a test that clicks the combo and searches for some value:

const code = html`
<wired-combo>
<wired-item value="apple">Apple</wired-item>
<wired-item value="banana">Banana</wired-item>
<wired-item value="cherry">Cherry</wired-item>
</wired-combo>
`
await fixture(code);
elementus.shadowRoot.querySelector('#text').click();
await sendKeys({ type: 'ba' });

We can use special keys:

await sendKeys({ press: 'ArrowUp' });

Testing events

To test that our components fire events when needed, we should add event listeners

const events: SelectedEvent[] = [];
const listener: (evt: Event) => void = evt => events.push(<SelectedEvent>evt);
element.addEventListener('selected', listener);
element.removeEventListener('selected', listener);
/// do some actions
const evt = events.pop();
expect(evt).to.exist;
expect(evt!.detail.selected).to.be.equal("2");

Using TypeScript in tests

We can write tests in TypeScript. Fot this purpose we should install one more lib:

npm i -D @web/dev-server-esbuild

and create (or adjust) web-test-runner.conf.js:

const esbuildPlugin = require("@web/dev-server-esbuild").esbuildPlugin;

module.exports = {
  files: ['**/__tests__/**/*.test.ts', '**/__tests__/**/*.spec.ts'],
  plugins: [esbuildPlugin({ ts: true })],
}

Debugging tests in a real browser

Sometimes its needed to see how our components look like in a real browser to figure out the problem.

Let adjust configuration run to use headed browser instead, and increase timeout to prevent test fail by mocha's default 2s timeout:

const { puppeteerLauncher } = require('@web/test-runner-puppeteer');

module.exports = {
browsers: [
puppeteerLauncher({
launchOptions: {
headless: false,
}
})
],
testFramework: {
config: {
timeout: 1000000,
}
},
}
frontend testing tech

Leave a comment on how you felt of the article...

Author Name
Author Email
Note: This email will not be shared to anyone
Comment