When you're writing tests, you often need to check that values meet certain conditions. expect gives you access to a number of "matchers" that let you validate different things on the browser, an element or mock object.
Soft assertions allow you to continue test execution even when an assertion fails. This is useful when you want to check multiple conditions in a test and collect all failures rather than stopping at the first failure. Failures are collected and reported at the end of the test.
// Mocha example
it('product page smoke', async () => {
// These won't throw immediately if they fail
await expect.soft(await $('h1').getText()).toEqual('Basketball Shoes');
await expect.soft(await $('#price').getText()).toMatch(/€\d+/);
// Also work with basic matcher
const h1Text = await $('h1').getText()
expect.soft(h1Text).toEqual('Basketball Shoes');
// Regular assertions still throw immediately
await expect(await $('.add-to-cart').isClickable()).toBe(true);
});
// At the end of the test, all soft assertion failures
// will be reported together with their detailsCreates a soft assertion that collects failures instead of immediately throwing errors.
await expect.soft(actual).toBeDisplayed();
await expect.soft(actual).not.toHaveText('Wrong text');Get all collected soft assertion failures for the current test.
const failures = expect.getSoftFailures();
console.log(`There are ${failures.length} soft assertion failures`);Manually assert all collected soft failures. This will throw an aggregated error if any soft assertions have failed.
// Manually throw if any soft assertions have failed
expect.assertSoftFailures();Clear all collected soft assertion failures for the current test.
// Clear all collected failures
expect.clearSoftFailures();The soft assertions feature integrates with WebdriverIO's test runner automatically. By default, it will report all soft assertion failures at the end of each test (Mocha/Jasmine) or step (Cucumber).
To use with WebdriverIO, add the SoftAssertionService to your services list:
// wdio.conf.js
import { SoftAssertionService } from 'expect-webdriverio'
export const config = {
// ...
services: [
// ...other services
[SoftAssertionService, {}]
],
// ...
}The SoftAssertionService can be configured with options to control its behavior:
// wdio.conf.js
import { SoftAssertionService } from 'expect-webdriverio'
export const config = {
// ...
services: [
// ...other services
[SoftAssertionService, {
// Disable automatic assertion at the end of tests (default: true)
autoAssertOnTestEnd: false
}]
],
// ...
}- Type:
boolean - Default:
true
When set to true (default), the service will automatically assert all soft assertions at the end of each test and throw an aggregated error if any failures are found. When set to false, you must manually call expect.assertSoftFailures() to verify soft assertions.
This is useful if you want full control over when soft assertions are verified or if you want to handle soft assertion failures in a custom way.
For Jasmine, using wdio-jasmine-framework will give a better plug-and-play experiences, else without it, the soft assertion service and custom matchers might not work/be registered correctly.
Moreover, if Jasmine augmentation is used, the soft assertion function are not exposed in the typing, but could still work depending of your configuration. See this issue for more details.
These default options below are connected to the waitforTimeout and waitforInterval options set in the config.
Only set the options below if you want to wait for specific timeouts for your assertions.
{
wait: 2000, // ms to wait for expectation to succeed
interval: 100, // interval between attempts
}If you like to pick different timeouts and intervals, set these options like this:
// wdio.conf.js
import { setOptions } from 'expect-webdriverio'
export const config = {
// ...
before () {
setOptions({ wait: 5000 })
},
// ...
}Every matcher can take several options that allows you to modify the assertion:
| Name | Type | Details |
|---|---|---|
wait |
number | time in ms to wait for expectation to succeed. Default: 3000 |
interval |
number | interval between attempts. Default: 100 |
beforeAssertion |
function | function to be called before assertion is made |
afterAssertion |
function | function to be called after assertion is made containing assertion results |
message |
string | user message to prepend before assertion error |
This option can be applied in addition to the command options when strings are being asserted.
| Name | Type | Details |
|---|---|---|
ignoreCase |
boolean | apply toLowerCase to both actual and expected values |
trim |
boolean | apply trim to actual value |
replace |
Replacer | Replacer[] | replace parts of the actual value that match the string/RegExp. The replacer can be a string or a function. |
containing |
boolean | expect actual value to contain expected value, otherwise strict equal. |
asString |
boolean | might be helpful to force converting property value to string |
atStart |
boolean | expect actual value to start with the expected value |
atEnd |
boolean | expect actual value to end with the expected value |
atIndex |
number | expect actual value to have the expected value at the given index |
This option can be applied in addition to the command options when numbers are being asserted.
| Name | Type | Details |
|---|---|---|
eq |
number | equals |
lte |
number | less then equals |
gte |
number | greater than or equals |
An HTML entity is a piece of text (“string”) that begins with an ampersand (&) and ends with a semicolon (;). Entities are frequently used to display reserved characters (which would otherwise be interpreted as HTML code), and invisible characters (like non-breaking spaces, e.g. ).
To find or interact with such element use unicode equivalent of the entity. e.g.:
<div data="Some Value">Some Text</div>const myElem = await $('div[data="Some\u00a0Value"]')
await expect(myElem).toHaveAttribute('data', 'div[Some\u00a0Value')
await expect(myElem).toHaveText('Some\u00a0Text')You can find all unicode references in the HTML spec.
Note: unicode is case-insensitive hence both \u00a0 and \u00A0 works. To find element in browser inspect, remove u from unicode e.g.: div[data="Some\00a0Value"]
Checks if browser is on a specific page.
await browser.url('https://webdriver.io/')
await expect(browser).toHaveUrl('https://webdriver.io')await browser.url('https://webdriver.io/')
await expect(browser).toHaveUrl(expect.stringContaining('webdriver'))Checks if website has a specific title.
await browser.url('https://webdriver.io/')
await expect(browser).toHaveTitle('WebdriverIO · Next-gen browser and mobile automation test framework for Node.js')
await expect(browser).toHaveTitle(expect.stringContaining('WebdriverIO'))Checks if the browser has a specific text stored in its clipboard.
import { Key } from 'webdriverio'
await browser.keys([Key.Ctrl, 'a'])
await browser.keys([Key.Ctrl, 'c'])
await expect(browser).toHaveClipboardText('some clipboard text')
await expect(browser).toHaveClipboardText(expect.stringContaining('clipboard text'))Calls isDisplayed on given element.
const elem = await $('#someElem')
await expect(elem).toBeDisplayed()Calls isExisting on given element.
const elem = await $('#someElem')
await expect(elem).toExist()Same as toExist.
const elem = await $('#someElem')
await expect(elem).toBePresent()Same as toExist.
const elem = await $('#someElem')
await expect(elem).toBeExisting()Checks if element has focus. This assertion only works in a web context.
const elem = await $('#someElem')
await expect(elem).toBeFocused()Checks if an element has a certain attribute with a specific value.
const myInput = await $('input')
await expect(myInput).toHaveAttribute('class', 'form-control')
await expect(myInput).toHaveAttribute('class', expect.stringContaining('control'))Same as toHaveAttribute.
const myInput = await $('input')
await expect(myInput).toHaveAttr('class', 'form-control')
await expect(myInput).toHaveAttr('class', expect.stringContaining('control'))Checks if an element has a single class name. Can also be called with an array as a parameter when the element can have multiple class names.
const myInput = await $('input')
await expect(myInput).toHaveElementClass('form-control', { message: 'Not a form control!' })
await expect(myInput).toHaveElementClass(['form-control' , 'w-full'], { message: 'not full width' })
await expect(myInput).toHaveElementClass(expect.stringContaining('form'), { message: 'Not a form control!' })Checks if an element has a certain property.
const elem = await $('#elem')
await expect(elem).toHaveElementProperty('height', 23)
await expect(elem).not.toHaveElementProperty('height', 0)Checks if an input element has a certain value.
const myInput = await $('input')
await expect(myInput).toHaveValue('admin-user', { ignoreCase: true })
await expect(myInput).toHaveValue(expect.stringContaining('user'), { ignoreCase: true })Checks if an element can be clicked by calling isClickable on the element.
const elem = await $('#elem')
await expect(elem).toBeClickable()Checks if an element is disabled by calling isEnabled on the element.
const elem = await $('#elem')
await expect(elem).toBeDisabled()
// same as
await expect(elem).not.toBeEnabled()Checks if an element is enabled by calling isEnabled on the element.
const elem = await $('#elem')
await expect(elem).toBeEnabled()
// same as
await expect(elem).not.toBeDisabled()Checks if an element is enabled by calling isSelected on the element.
const elem = await $('#elem')
await expect(elem).toBeSelected()Same as toBeSelected.
const elem = await $('#elem')
await expect(elem).toBeChecked()Checks if element has a specific computed WAI-ARIA label. Can also be called with an array as parameter in the case where the element can have different labels.
await browser.url('https://webdriver.io/')
const elem = await $('a[href="https://github.com/webdriverio/webdriverio"]')
await expect(elem).toHaveComputedLabel('GitHub repository')
await expect(elem).toHaveComputedLabel(expect.stringContaining('repository'))await browser.url('https://webdriver.io/')
const elem = await $('a[href="https://github.com/webdriverio/webdriverio"]')
await expect(elem).toHaveComputedLabel(['GitHub repository', 'Private repository'])
await expect(elem).toHaveComputedLabel([expect.stringContaining('GitHub'), expect.stringContaining('Private')])Checks if element has a specific computed WAI-ARIA role. Can also be called with an array as parameter in the case where the element can have different labels.
await browser.url('https://webdriver.io/')
const elem = await $('[aria-label="Skip to main content"]')
await expect(elem).toHaveComputedRole('region')
await expect(elem).toHaveComputedRole(expect.stringContaining('ion'))await browser.url('https://webdriver.io/')
const elem = await $('[aria-label="Skip to main content"]')
await expect(elem).toHaveComputedRole(['region', 'section'])
await expect(elem).toHaveComputedRole([expect.stringContaining('reg'), expect.stringContaining('sec')])Checks if link element has a specific link target.
const link = await $('a')
await expect(link).toHaveHref('https://webdriver.io')
await expect(link).toHaveHref(expect.stringContaining('webdriver.io'))Same as toHaveHref.
const link = await $('a')
await expect(link).toHaveLink('https://webdriver.io')
await expect(link).toHaveLink(expect.stringContaining('webdriver.io'))Checks if element has a specific id attribute.
const elem = await $('#elem')
await expect(elem).toHaveId('elem')Checks if an element has specific CSS properties. By default, values must match exactly. Only the CSS properties you specify are validated; other properties on the element are ignored.
const elem = await $('#elem')
await expect(elem).toHaveStyle({
'font-family': 'Faktum',
'font-weight': '500',
'font-size': '12px',
})Checks if element has a specific text. Can also be called with an array as parameter in the case where the element can have different texts.
await browser.url('https://webdriver.io/')
const elem = await $('.container')
await expect(elem).toHaveText('Next-gen browser and mobile automation test framework for Node.js')
await expect(elem).toHaveText(expect.stringContaining('test framework for Node.js'))
await expect(elem).toHaveText(['Next-gen browser and mobile automation test framework for Node.js', 'Get Started'])
await expect(elem).toHaveText([expect.stringContaining('test framework for Node.js'), expect.stringContaining('Started')])In case there is a list of elements in the div below:
<ul>
<li>Coffee</li>
<li>Tea</li>
<li>Milk</li>
</ul>
You can assert them using an array:
const elem = await $$('ul > li')
await expect(elem).toHaveText(['Coffee', 'Tea', 'Milk'])Checks if element has a specific text. Can also be called with an array as parameter in the case where the element can have different texts.
await browser.url('https://webdriver.io/')
const elem = await $('.hero__subtitle')
await expect(elem).toHaveHTML('<p class="hero__subtitle">Next-gen browser and mobile automation test framework for Node.js</p>')
await expect(elem).toHaveHTML(expect.stringContaining('Next-gen browser and mobile automation test framework for Node.js'))
await expect(elem).toHaveHTML('Next-gen browser and mobile automation test framework for Node.js', { includeSelectorTag: false })await browser.url('https://webdriver.io/')
const elem = await $('.hero__subtitle')
await expect(elem).toHaveHTML(['Next-gen browser and mobile automation test framework for Node.js', 'Get Started'], { includeSelectorTag: false })
await expect(elem).toHaveHTML([expect.stringContaining('automation test framework for Node.js'), expect.stringContaining('Started')], { includeSelectorTag: false })Checks if an element is within the viewport by calling isDisplayedInViewport on the element.
const elem = await $('#elem')
await expect(elem).toBeDisplayedInViewport()Checks amount of the fetched element's children by calling element.$('./*') command.
const list = await $('ul')
await expect(list).toHaveChildren() // the list has at least one item
// same as
await expect(list).toHaveChildren({ gte: 1 })
await expect(list).toHaveChildren(3) // the list has 3 items
// same as
await expect(list).toHaveChildren({ eq: 3 })Checks if element has a specific width.
await browser.url('http://github.com')
const logo = await $('.octicon-mark-github')
await expect(logo).toHaveWidth(32)Checks if element has a specific height.
await browser.url('http://github.com')
const logo = await $('.octicon-mark-github')
await expect(logo).toHaveHeight(32)Checks if element has a specific size.
await browser.url('http://github.com')
const logo = await $('.octicon-mark-github')
await expect(logo).toHaveSize({ width: 32, height: 32 })Checks amount of fetched elements using $$ command.
Note: This matcher will update the passed array with the latest elements if the assertion passes. However, if you've reassigned the variable, you'll need to fetch the elements again.
const listItems = await $$('ul>li')
await expect(listItems).toBeElementsArrayOfSize(5) // 5 items in the list
await expect(listItems).toBeElementsArrayOfSize({ lte: 10 })
// same as
assert.ok(listItems.length <= 10)Checks that mock was called
const mock = browser.mock('**/api/todo*')
await expect(mock).toBeRequested()Checks that mock was called for the expected amount of times
const mock = browser.mock('**/api/todo*')
await expect(mock).toBeRequestedTimes(2) // await expect(mock).toBeRequestedTimes({ eq: 2 })
await expect(mock).toBeRequestedTimes({ gte: 5, lte: 10 }) // request called at least 5 times but less than 11Checks that mock was called according to the expected options.
Most of the options supports expect/jasmine partial matchers like expect.objectContaining
const mock = browser.mock('**/api/todo*', { method: 'POST' })
await expect(mock).toBeRequestedWith({
url: 'http://localhost:8080/api/todo', // [optional] string | function | custom matcher
method: 'POST', // [optional] string | array
statusCode: 200, // [optional] number | array
requestHeaders: { Authorization: 'foo' }, // [optional] object | function | custom matcher
responseHeaders: { Authorization: 'bar' }, // [optional] object | function | custom matcher
postData: { title: 'foo', description: 'bar' }, // [optional] object | function | custom matcher
response: { success: true }, // [optional] object | function | custom matcher
})
await expect(mock).toBeRequestedWith({
url: expect.stringMatching(/.*\/api\/.*/i),
method: ['POST', 'PUT'], // either POST or PUT
statusCode: [401, 403], // either 401 or 403
requestHeaders: headers => headers.Authorization.startsWith('Bearer '),
postData: expect.objectContaining({ released: true, title: expect.stringContaining('foobar') }),
response: r => Array.isArray(r) && r.data.items.length === 20
})WebdriverIO supports basic snapshot tests as well as DOM snapshot testing.
Checks if any arbitrary object matches a certain value. If you pass in an WebdriverIO.Element it will automatically snapshot the outerHTML state of it.
// snapshot arbitrary objects (no "await" needed here)
expect({ foo: 'bar' }).toMatchSnapshot()
// snapshot `outerHTML` of WebdriverIO.Element (DOM snapshot, requires "await")
await expect($('elem')).toMatchSnapshot()
// snapshot result of element command
await expect($('elem').getCSSProperty('background-color')).toMatchSnapshot()Similarly, you can use the toMatchInlineSnapshot() to store the snapshot inline within the test file. For example, given:
await expect($('img')).toMatchInlineSnapshot()Instead of creating a snapshot file, WebdriverIO will modify the test file directly to update the snapshot as a string:
await expect($('img')).toMatchInlineSnapshot(`"<img src="/public/apple-touch-icon-precomposed.png">"`)The following matcher are implemented as part of the @wdio/visual-service plugin and only available when the service is set up. Make sure you follow the set-up instructions accordingly.
Checks that if given element matches with snapshot of baseline.
await expect($('.hero__title-logo')).toMatchElementSnapshot('wdioLogo', 0, {
// options
})The expected result is by default 0, so you can write the same assertion as:
await expect($('.hero__title-logo')).toMatchElementSnapshot('wdioLogo', {
// options
})or not pass in any options at all:
await expect($('.hero__title-logo')).toMatchElementSnapshot()Checks that if current screen matches with snapshot of baseline.
await expect(browser).toMatchScreenSnapshot('partialPage', 0, {
// options
})The expected result is by default 0, so you can write the same assertion as:
await expect(browser).toMatchScreenSnapshot('partialPage', {
// options
})or not pass in any options at all:
await expect(browser).toMatchScreenSnapshot('partialPage')Checks that if the full page screenshot matches with snapshot of baseline.
await expect(browser).toMatchFullPageSnapshot('fullPage', 0, {
// options
})The expected result is by default 0, so you can write the same assertion as:
await expect(browser).toMatchFullPageSnapshot('fullPage', {
// options
})or not pass in any options at all:
await expect(browser).toMatchFullPageSnapshot('fullPage')Checks that if the full page screenshot including tab marks matches with snapshot of baseline.
await expect(browser).toMatchTabbablePageSnapshot('tabbable', 0, {
// options
})The expected result is by default 0, so you can write the same assertion as:
await expect(browser).toMatchTabbablePageSnapshot('tabbable', {
// options
})or not pass in any options at all:
await expect(browser).toMatchTabbablePageSnapshot('tabbable')You can also directly use regular expressions for all matchers that do text comparison.
await browser.url('https://webdriver.io/')
const elem = await $('.container')
await expect(elem).toHaveText(/node\.js/i)
await expect(elem).toHaveText([/node\.js/i, 'Get Started'])
await expect(browser).toHaveTitle(/webdriverio/i)
await expect(browser).toHaveUrl(/webdriver\.io/)
await expect(elem).toHaveElementClass(/Container/i)In addition to the expect-webdriverio matchers you can use builtin Jest's expect assertions or expect/expectAsync for Jasmine.
WebdriverIO supports usage of asymmetric matchers wherever you compare text values, e.g.:
await expect(browser).toHaveTitle(expect.stringContaining('some title'))or
await expect(browser).toHaveTitle(expect.not.stringContaining('some title'))