WebdriverIO Component Testing Service
A WebdriverIO service that allows to run component tests for Lit, Vue or Svelte web components.
This WebdriverIO service allows to render components of your favorite frontend framework and run tests on it.
Supported Frameworks:
Install
To set-up the service, run:
npm i --save-dev wdio-component-testing-service
or using Yarn:
yarn add --dev wdio-component-testing-service
Then add the service to the service list in your wdio.conf.(t|j)s
:
// wdio.conf.js
export const config = {
// ...
services: [
['component-testing', {
framework: 'lit' // (optional), also available: "vue", "svelte"
}]
]
}
Usage
The service adds two commands to the browser object that you can use to run your tests: mount
and the more versatile render
method.
mount
The mount
method allows to specify a single file component and its props to be rendered within the browser. It returns the root element of the rendered web component.
Signature
browser.mount(fileName: string, props: Record<string, any>, options: RenderOptions) => WebdriverIO.Element
Parameters
-
fileName
: path to component file -
props
: props object to be passed as input parameters to the component -
options
: render options-
framework
: (optional) framework used to render the component (mostly detect automatically) -
imports
: (optional) a set of file references or inline scripts to be loaded before the component is rendered
-
Using Lit
Given the following Lit component:
./example/lit/HelloWorld.ts
import { LitElement, html } from 'lit'
import { customElement } from 'lit/decorators.js'
@customElement('hello-world')
export default class MyElement extends LitElement {
declare verb: string
declare subject: string
static properties = {
verb: { type: String },
subject: { type: String }
}
constructor () {
super()
this.verb = 'Hello'
this.subject = 'World'
}
render() {
return html/*html*/`<b>${this.verb} ${this.subject}!</b>
}
}
A component test using this service could look like this:
describe('component testing service', () => {
it('should mount a lit component', async () => {
const elem = await browser.mount('./example/lit/HelloWorld.ts', {}, {
framework: 'lit'
})
await expect(elem).toHaveText('Hello World!')
})
it('should mount a lit component with props', async () => {
const options = { framework: 'lit' as const }
const elem = await browser.mount('./example/lit/HelloWorld.ts', {
verb: 'Love',
subject: 'Web Components'
}, options)
await expect(elem).toHaveText('Love Web Components!')
})
})
Using Svelte
Given the following Svelte component:
./example/svelte/HelloWorld.svelte
<script>
export let verb = 'Hello';
export let subject = 'World';
</script>
<h1>{verb} {subject}!</h1>
A component test using this service could look like this:
describe('component testing service', () => {
it('should mount a Svelte component', async () => {
const elem = await browser.mount('./example/svelte/HelloWorld.svelte')
await expect(elem).toHaveText('Hello World!')
})
it('should mount a Svelte component with props', async () => {
const elem = await browser.mount('./example/svelte/HelloWorld.svelte', {
verb: 'Love',
subject: 'Web Components'
})
await expect(elem).toHaveText('Love Web Components!')
})
})
Using Vue.js
Given the following Vue.js component:
./example/svelte/HelloWorld.svelte
<script>
export default {
props: {
verb: String,
subject: String
},
data() {
return {
message: `${this.verb || 'Hello'} ${this.subject || 'World'}!`
}
},
methods: {
reverseMessage() {
this.message = this.message.split('').reverse().join('')
},
notify() {
alert('navigation was prevented.')
}
}
}
</script>
<template>
<!--
Note we don't need .value inside templates because
refs are automatically "unwrapped" in templates.
-->
<h1>{{ message }}</h1>
<!--
Bind to a method/function.
The @click syntax is short for v-on:click.
-->
<button @click="reverseMessage">Reverse Message</button>
<!-- Can also be an inline expression statement -->
<button @click="message += '!'">Append "!"</button>
<!--
Vue also provides modifiers for common tasks
such as e.preventDefault() and e.stopPropagation()
-->
<a href="https://vuejs.org" @click.prevent="notify">
A link with e.preventDefault()
</a>
</template>
<style>
button,
a {
display: block;
margin-bottom: 1em;
}
</style>
A component test using this service could look like this:
describe('component testing service', () => {
it('should mount a Vue component', async () => {
const elem = await browser.mount('./example/vue/HelloWorld.vue')
const h1 = await elem.$('h1')
const [ reverseBtn, appendBtn ] = await elem.$$('button')
await expect(h1).toHaveText('Hello World!')
await reverseBtn.click()
await expect(h1).toHaveText('!dlroW olleH')
await appendBtn.click()
await expect(h1).toHaveText('!dlroW olleH!')
})
it('should mount a Vue component with props', async () => {
const elem = await browser.mount('./example/vue/HelloWorld.vue', {
verb: 'Love',
subject: 'Web Components'
})
const h1 = await elem.$('h1')
await expect(h1).toHaveText('Love Web Components!')
})
})
render
The render
method is more versatile and allows rendering all kinds of HTML. It is being used internally when calling the mount
.
Signature
browser.render(html: string | Function, options: RenderOptions) => WebdriverIO.Element
Parameters
-
fileName
: path to component file -
options
: render options-
framework
: (optional) framework used to render the component (mostly detect automatically) -
imports
: (optional) a set of file references or inline scripts to be loaded before the component is rendered -
props
: props object to be passed as input parameters to the component
-
Example
The following test shows how render
can be used to render dynamic templates, e.g.:
describe('component testing service base features', () => {
it('should allow referenced imports', async () => {
const elem = await browser.render(({ tagName, verb, subject }) => (
/*html*/`<${a.tagName}>${a.verb} ${a.subject}</${a.tagName}>`
), {
imports: {
tagName: () => 'i',
verb: () => 'Hello',
subject: () => 'World!'
}
})
expect(await elem.getHTML()).toBe('<i>Hello World!</i>')
})
})