Integration testing for web components
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,
}
},
}