Expect
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
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.
Usage
// 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 details
Soft Assertion API
expect.soft()
Creates a soft assertion that collects failures instead of immediately throwing errors.
await expect.soft(actual).toBeDisplayed();
await expect.soft(actual).not.toHaveText('Wrong text');
expect.getSoftFailures()
Get all collected soft assertion failures for the current test.
const failures = expect.getSoftFailures();
console.log(`There are ${failures.length} soft assertion failures`);
expect.assertSoftFailures()
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();
expect.clearSoftFailures()
Clear all collected soft assertion failures for the current test.
// Clear all collected failures
expect.clearSoftFailures();
Integration with Test Frameworks
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) 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, {}]
],
// ...
}
Configuration Options
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
}]
],
// ...
}
autoAssertOnTestEnd
- 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.
Known limitations
The soft assertions service is not supported under Jasmine (e.g. @wdio/jasmine-framework) using the global import because Jasmine is already designed to provide similar behavior out of the box.
Default Options
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 })
},
// ...
}
Matcher Options
Every matcher can take several options that allows you to modify the assertion:
Command Options
| 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 |
String Options
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 |
Number Options
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 |
Handling HTML Entities
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"]
Browser Matchers
toHaveUrl
Checks if browser is on a specific page.
Usage
await browser.url('https://webdriver.io/')
await expect(browser).toHaveUrl('https://webdriver.io')
Usage
await browser.url('https://webdriver.io/')
await expect(browser).toHaveUrl(expect.stringContaining('webdriver'))
toHaveTitle
Checks if website has a specific title.
Usage
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'))
toHaveClipboardText
Checks if the browser has a specific text stored in its clipboard.
Usage
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'))
toHaveLocalStorageItem
Checks if browser has a specific item in localStorage with an optional value.
Usage
await browser.url('https://webdriver.io/')
// Check if localStorage item exists
await expect(browser).toHaveLocalStorageItem('existingKey')
// Check localStorage item with exact value
await expect(browser).toHaveLocalStorageItem('someLocalStorageKey', 'someLocalStorageValue')
// Check with case insensitive
await expect(browser).toHaveLocalStorageItem('key', 'uppercase', { ignoreCase: true })
// Check with trim
await expect(browser).toHaveLocalStorageItem('key', 'value', { trim: true })
// Check with containing
await expect(browser).toHaveLocalStorageItem('key', 'long', { containing: true })
// Check with regex
await expect(browser).toHaveLocalStorageItem('userId', /^user_\d+$/)
Element Matchers
toBeDisplayed
Calls isDisplayed on given element.
Usage
const elem = await $('#someElem')
await expect(elem).toBeDisplayed()
toExist
Calls isExisting on given element.
Usage
const elem = await $('#someElem')
await expect(elem).toExist()
toBePresent
Same as toExist.
Usage
const elem = await $('#someElem')
await expect(elem).toBePresent()
toBeExisting
Same as toExist.
Usage
const elem = await $('#someElem')
await expect(elem).toBeExisting()
toBeFocused
Checks if element has focus. This assertion only works in a web context.
Usage
const elem = await $('#someElem')
await expect(elem).toBeFocused()
toHaveAttribute
Checks if an element has a certain attribute with a specific value.
Usage
const myInput = await $('input')
await expect(myInput).toHaveAttribute('class', 'form-control')
await expect(myInput).toHaveAttribute('class', expect.stringContaining('control'))
toHaveAttr
Same as toHaveAttribute.
Usage
const myInput = await $('input')
await expect(myInput).toHaveAttr('class', 'form-control')
await expect(myInput).toHaveAttr('class', expect.stringContaining('control'))
toHaveElementClass
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.
Usage
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!' })
toHaveElementProperty
Checks if an element has a certain property.
Usage
const elem = await $('#elem')
await expect(elem).toHaveElementProperty('height', 23)
await expect(elem).not.toHaveElementProperty('height', 0)
toHaveValue
Checks if an input element has a certain value.
Usage
const myInput = await $('input')
await expect(myInput).toHaveValue('admin-user', { ignoreCase: true })
await expect(myInput).toHaveValue(expect.stringContaining('user'), { ignoreCase: true })
toBeClickable
Checks if an element can be clicked by calling isClickable on the element.
Usage
const elem = await $('#elem')
await expect(elem).toBeClickable()
toBeDisabled
Checks if an element is disabled by calling isEnabled on the element.
Usage
const elem = await $('#elem')
await expect(elem).toBeDisabled()
// same as
await expect(elem).not.toBeEnabled()
toBeEnabled
Checks if an element is enabled by calling isEnabled on the element.
Usage
const elem = await $('#elem')
await expect(elem).toBeEnabled()
// same as
await expect(elem).not.toBeDisabled()
toBeSelected
Checks if an element is enabled by calling isSelected on the element.
Usage
const elem = await $('#elem')
await expect(elem).toBeSelected()
toBeChecked
Same as toBeSelected.
Usage
const elem = await $('#elem')
await expect(elem).toBeChecked()
toHaveComputedLabel
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.
Usage
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'))
Usage
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')])
toHaveComputedRole
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.
Usage
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'))
Usage
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')])
toHaveHref
Checks if link element has a specific link target.
Usage
const link = await $('a')
await expect(link).toHaveHref('https://webdriver.io')
await expect(link).toHaveHref(expect.stringContaining('webdriver.io'))
toHaveLink
Same as toHaveHref.
Usage
const link = await $('a')
await expect(link).toHaveLink('https://webdriver.io')
await expect(link).toHaveLink(expect.stringContaining('webdriver.io'))
toHaveId
Checks if element has a specific id attribute.
Usage
const elem = await $('#elem')
await expect(elem).toHaveId('elem')
toHaveStyle
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.
Usage
const elem = await $('#elem')
await expect(elem).toHaveStyle({
'font-family': 'Faktum',
'font-weight': '500',
'font-size': '12px',
})
toHaveText
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.
Usage
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'])
toHaveHTML
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.
Usage
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 })
Usage
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 })
toBeDisplayedInViewport
Checks if an element is within the viewport by calling isDisplayedInViewport on the element.
Usage
const elem = await $('#elem')
await expect(elem).toBeDisplayedInViewport()
toHaveChildren
Checks amount of the fetched element's children by calling element.$('./*') command.
Usage
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 })
toHaveWidth
Checks if element has a specific width.
Usage
await browser.url('http://github.com')
const logo = await $('.octicon-mark-github')
await expect(logo).toHaveWidth(32)
toHaveHeight
Checks if element has a specific height.
Usage
await browser.url('http://github.com')
const logo = await $('.octicon-mark-github')
await expect(logo).toHaveHeight(32)
toHaveSize
Checks if element has a specific size.
Usage
await browser.url('http://github.com')
const logo = await $('.octicon-mark-github')
await expect(logo).toHaveSize({ width: 32, height: 32 })
toBeElementsArrayOfSize
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.
Usage
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)
Network Matchers
toBeRequested
Checks that mock was called
Usage
const mock = browser.mock('**/api/todo*')
await expect(mock).toBeRequested()
toBeRequestedTimes
Checks that mock was called for the expected amount of times
Usage
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 11
toBeRequestedWith
Checks that mock was called according to the expected options.
Most of the options supports expect/jasmine partial matchers like expect.objectContaining
Usage
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
})
Snapshot Matcher
WebdriverIO supports basic snapshot tests as well as DOM snapshot testing.
toMatchSnapshot
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.
Usage
// 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()
toMatchInlineSnapshot
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" />"`)
Visual Snapshot Matchers
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.
toMatchElementSnapshot
Checks that if given element matches with snapshot of baseline.
Usage
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()
toMatchScreenSnapshot
Checks that if current screen matches with snapshot of baseline.
Usage
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')
toMatchFullPageSnapshot
Checks that if the full page screenshot matches with snapshot of baseline.
Usage
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')
toMatchTabbablePageSnapshot
Checks that if the full page screenshot including tab marks matches with snapshot of baseline.
Usage
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')
Using regular expressions
You can also directly use regular expressions for all matchers that do text comparison.
Usage
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)
Default Matchers
In addition to the WebdriverIO matchers, expect-webdriverio also provides basic matchers from Jest's expect library.
// Equality
expect(2 + 2).toBe(4);
expect({a: 1}).toEqual({a: 1});
expect([1, 2, 3]).toStrictEqual([1, 2, 3]);
expect(2 + 2).not.toBe(5);
// Truthiness
expect(null).toBeNull();
expect(undefined).toBeUndefined();
expect(0).toBeFalsy();
expect(1).toBeTruthy();
expect(NaN).toBeNaN();
// Numbers
expect(4).toBeGreaterThan(3);
expect(4).toBeGreaterThanOrEqual(4);
expect(4).toBeLessThan(5);
expect(4).toBeLessThanOrEqual(4);
expect(0.2 + 0.1).toBeCloseTo(0.3, 5);
// Strings
expect('team').toMatch(/team/);
expect('Christoph').toContain('stop');
// Arrays and iterables
expect([1, 2, 3]).toContain(2);
expect([{a: 1}, {b: 2}]).toContainEqual({a: 1});
expect([1, 2, 3]).toHaveLength(3);
// Objects
expect({a: 1, b: 2}).toHaveProperty('a');
expect({a: {b: 2}}).toHaveProperty('a.b', 2);
// Errors
expect(() => { throw new Error('error!') }).toThrow('error!');
expect(() => { throw new TypeError('wrong type') }).toThrow(TypeError);
// Asymmetric matchers
expect({foo: 'bar', baz: 1}).toEqual(expect.objectContaining({foo: expect.any(String)}));
expect([1, 2, 3]).toEqual(expect.arrayContaining([2]));
expect('abc').toEqual(expect.stringContaining('b'));
expect('abc').toEqual(expect.stringMatching(/b/));
expect(123).toEqual(expect.any(Number));
// Others
expect(new Set([1, 2, 3])).toContain(2);
// .resolves / .rejects (async)
await expect(Promise.resolve(42)).resolves.toBe(42);
await expect(Promise.reject(new Error('fail'))).rejects.toThrow('fail');
Jasmine
For Jasmine, see the official documentation for expect/expectAsync, matchers, and async-matchers.
Note:
- With the global import in @wdio/jasmine-framework, only WebdriverIO custom matchers are registered on expectAsync (assigned to global expect), so all matchers are always async, even those that are normally synchronous.
- Default matchers are still available if you import
expectdirectly fromexpect-webdriverioinstead of using the global.
Modifiers
WebdriverIO supports usage of modifiers as .not and it will wait until the reverse condition is meet
// Wait until the element is no longer present
await expect(element).not.toBeDisplayed()
// Wait until the text is no more 'some title'
await expect(browser).not.toHaveTitle('some title')
In case immediate assertion is required, use { wait: 0 }
// Ensure element is not present right now
await expect(element).not.toBeDisplayed({ wait: 0 })
// Ensure the text is not 'some title' right now
await expect(browser).not.toHaveTitle('some title', { wait: 0 })
Note: You can pair .not with asymmetric matchers, but to enable the wait-until behavior, .not must be used directly on the expect() call.
Asymmetric Matchers
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'))
Jasmine
Under @wdio/jasmine-framework, some Jasmine asymmetric matchers now work with WebdriverIO matchers and the global import.
// Jasmine's stringContaining works just like the one from expect
await expect(browser).toHaveTitle(jasmine.stringContaining('some title'))
await expect(browser).toHaveTitle(expect.stringContaining('some title'))
// Jasmine's stringMatching works
await expect(browser).toHaveUrl(jasmine.stringMatching('/WebdriverIO/'))
// Jasmine's any & anything works
await expect(browser).toHaveUrl(jasmine.any(String))
await expect(browser).toHaveUrl(jasmine.anything())
// Jasmine's objectContaining works with Network Matchers
await expect(mock).toBeRequestedWith({
method: 'POST',
requestHeaders: expect.objectContaining({
Authorization: 'foo'
}),
})
// Jasmine's any & anything works with Network Matchers
await expect(mock).toBeRequestedWith({
method: 'POST',
url: jasmine.any(String),
requestHeaders: jasmine.anything(),
})
Note: Known limitations still exist with jasmine.arrayContaining and jasmine.objectContaining.
Types Definition
TypeScript
If you are using the WDIO Testrunner everything will be automatically setup. Just follow the setup guide from the docs. However if you run WebdriverIO with a different testrunner or in a simple Node.js script you will need to add expect-webdriverio to types in the tsconfig.json.
"expect-webdriverio"for everyone except Jasmine/Jest users."expect-webdriverio/jasmine"for Jasmine"expect-webdriverio/jest"for Jest"expect-webdriverio/expect-global"// Optional, if you wish to use expect ofexpect-webdriverioglobally without explicit import- Note: Same as the former
"expect-webdriverio/types", now deprecated!
- Note: Same as the former
JavaScript (VSCode)
It's required to create jsconfig.json in project root and refer to the type definitions to make autocompletion work in vanilla js.
{
"include": [
"**/*.js",
"**/*.json",
"node_modules/expect-webdriverio"
]
}
Jasmine special case
Jasmine is different from Jest or the standard expect definition since it supports promises using expectAsync which makes it quite challenging.
Even though this library by itself is not fully Jasmine-ready, it offers the types of the matcher only on the AsyncMatcher since using jasmine.expect does not work out-of-the-box. However, if you are pulling on the expect of expect-webdriverio, you will be able to have the WebDriverIO matcher types on expect.
Support of expectAsync keyword is subject to change and may be dropped in the future!
Dependency on @wdio/jasmine-framework
As mentioned above, this library alone is not working with Jasmine. It is required to manually do some tweaks, or it is strongly recommended to also pair it with @wdio/jasmine-framework. See Framework.md for more information.
When using @wdio/jasmine-framework, since it replaces jasmine.expect with jasmine.expectAsync, then matchers are usable on the keyword expect, but still typing on expect directly from Jasmine namespace is not supported as of today!
Expect-WebDriverIO Framework
expect-webdriverio extends Jest's expect API with WebDriverIO-specific enhancements. It can be used standalone or within other testing environments.
Compatibility
It is highly recommended to use this package with the WDIO Testrunner and a compatible framework adapter, which together provide a plug-and-play experience.
Pair it with your preferred framework using the appropriate adapter:
- Mocha: Use
@wdio/mocha-framework - Cucumber: Use
@wdio/cucumber-framework - Jasmine: Use
@wdio/jasmine-framework - Jest: Works (no framework exists; requires manual configuration—see note below)
Note: When using Jest, or when running outside of the WDIO Testrunner without a compatible framework adapter, additional manual configuration may be required, such as adding types to your
tsconfig.jsonand configuring WDIO matchers, soft assertions, and the snapshot service.
Playgrounds
Example playgrounds are available, though their tsconfig.json files may use modified configurations for development purposes.
- See Mocha, Jasmine, and Jest examples here.
Mocha
When pairing with Mocha, you can use expect-webdriverio directly or combine it with chai (or any other assertion library).
- It is strongly recommended to leverage
@wdio/mocha-frameworkfor automatic configuration and a plug-and-play experience. - See Mocha playground example here
Standalone
No import is required; everything is set globally.
describe('My tests', async () => {
it('should verify my browser to have the expected url', async () => {
await expect(browser).toHaveUrl('https://example.com')
})
})
Minimum types expected in tsconfig.json:
When depending on @wdio/mocha-framework
{
"compilerOptions": {
"types": [
"@wdio/mocha-framework",
"@wdio/globals/types"
]
}
}
When not depending on @wdio/mocha-framework
{
"compilerOptions": {
"types": [
"@types/mocha",
"expect-webdriverio/expect-global"
]
}
}
Chai
expect-webdriverio can coexist with the Chai assertion library by importing both libraries explicitly.
See also this documentation.
Jest
You can use expect-webdriverio with Jest by leveraging either @types/jest (which provides global ambient support) or @jest/globals alone.
- Note: Jest maintainers do not officially support
@types/jest. Should this package become outdated or experience issues, support may be dropped. - Note: With Jest, the matchers
toMatchSnapshotandtoMatchInlineSnapshotare overloaded. To resolve the types correctly,expect-webdriverio/jestmust be listed last. - Note: WebdriverIO does not provide a compatible framework adapter for Jest; manual configuration is required.
With @types/jest
When paired with @types/jest, no imports are required in your test files. Global ambient types are already defined correctly, allowing you to use Jest's expect directly after some manual configuration.
- Note:
jestandts-jestare also required. - See the Jest with
@types/jestplayground example.
Since no WDIO Testrunner and framework adapter are used, additional prerequisite configuration is required.
Option 1: Replace the global expect with the expect-webdriverio instance:
import { expect } from "expect-webdriverio";
(globalThis as any).expect = expect;
Option 2: Extend Jest's global expect with custom matchers and soft assertions:
If not already set, define a file path for setupFilesAfterEnv in your Jest configuration:
setupFilesAfterEnv: ['./jest.setup.after-env.ts'],
Then, add the following configuration to your jest.setup.after-env.ts file:
import { expect } from "@jest/globals";
import { wdioCustomMatchers } from "expect-webdriverio";
beforeAll(async () => {
// Extend the imported Jest expect instance with WDIO matchers
expect.extend(wdioCustomMatchers);
});
Optional: For soft assertions, createSoftExpect is currently not correctly exposed, but the configuration below works:
import { SoftAssertService } from "expect-webdriverio";
// @ts-ignore
import * as createSoftExpect from "expect-webdriverio/lib/softExpect";
beforeAll(async () => {
Object.defineProperty(expect, "soft", {
value: <T = unknown>(actual: T) => createSoftExpect.default(actual),
});
// Add soft assertions utility methods
Object.defineProperty(expect, "getSoftFailures", {
value: (testId?: string) => SoftAssertService.getInstance().getFailures(testId),
});
Object.defineProperty(expect, "assertSoftFailures", {
value: (testId?: string) => SoftAssertService.getInstance().assertNoFailures(testId),
});
Object.defineProperty(expect, "clearSoftFailures", {
value: (testId?: string) => SoftAssertService.getInstance().clearFailures(testId),
});
});
Then, as shown below, no imports are required and we can use WDIO matchers directly on Jest's expect:
describe('My tests', async () => {
it('should verify my browser to have the expected url', async () => {
await expect(browser).toHaveUrl('https://example.com')
})
})
Expected in tsconfig.json:
{
"compilerOptions": {
"types": [
"@types/jest",
"expect-webdriverio/jest" // Must be last for overloaded matchers `toMatchSnapshot` and `toMatchInlineSnapshot`
]
}
}
With Only @jest/globals
When using @jest/globals directly instead of global ambient types, you explicitly import Jest's utilities. To use expect-webdriverio you have two approaches:
- Note: No example playground available
Option 1: Explicit Imports
import { expect } from 'expect-webdriverio'
import { describe, it, expect as jestExpect } from '@jest/globals'
describe('My tests', async () => {
it('should verify my browser to have the expected url', async () => {
await expect(browser).toHaveUrl('https://example.com')
// Jest's native expect is still usable
jestExpect(myFunction).toHaveBeenCalled()
})
})
No types are expected in tsconfig.json.
Option 2: Global Type Definition
To avoid explicitly importing expect from expect-webdriverio in every test file, add the global entry point to your tsconfig.json:
{
"compilerOptions": {
"types": ["expect-webdriverio/expect-global"]
}
}
Augmenting @jest/globals JestMatchers
Unlike @types/jest, @jest/globals does not export a global namespace that can be easily extended. While module augmentation is possible, it does not support inheriting matchers via the extends keyword. Supporting it would require manually duplicating all expect-webdriverio matcher interfaces inside the module declaration.
This limitation is a known upstream issue tracked in Jest.
Jasmine
When paired with Jasmine, @wdio/jasmine-framework is required to ensure proper runtime configuration. The adapter forces the global expect to map to Jasmine's native expectAsync and registers the necessary WDIO matchers via addAsyncMatcher.
Jasmine differs from other standard assertion libraries in two key ways:
- Built-in Soft Assertions: Jasmine executes soft assertions out-of-the-box by tracking and collecting validation failures until a spec block finishes execution. Because this mechanism is native to Jasmine, the
expect-webdriverioSoftAssertion service is neither required nor supported. - Implicit Promise Handling: Forcing
expectAsyncto act as the globalexpectbinding makes even basic matchers asynchronous. Because Jasmine automatically hooks into outstanding spec promises and flushes them at the end of the test, assertions may appear to execute correctly even if you omit theawaitkeyword—unlike in other frameworks whereawaitis strictly mandatory.
⚠️ Warning: Omitting
awaitdirectly conflicts with Jasmine's official async matcher recommendations and can introduce silent timing issues or unhandled rejections into your test suite. Always explicitlyawaityour assertions.
Available Type Definitions
-
expect-webdriverio/jasmineAugments Jasmine's nativeexpectAsyncinterface directly with WebDriverIO custom matchers. -
expect-webdriverio/jasmine-wdio-expect-asyncSpecifically dedicated to aligning with the@wdio/jasmine-frameworkarchitecture. This entry point is subject to breaking changes and may be moved directly into the framework adapter in a future release. It performs the following modifications:- Augments
expectwith WebDriverIO custom matchers. - Transforms synchronous, native Jasmine matchers on the
expectinterface to return promises (making them asynchronous). - Establishes a global
expecttype definition with the above modifications.
- Augments
Global expectAsync forced as expect
When using @wdio/jasmine-framework, the global ambient expect is forced to behave as Jasmine's native expectAsync under the hood. It is strongly recommended to explicitly await all assertions—including basic, non-WDIO matchers. While Jasmine automatically processes un-awaited spec promises at the end of test execution, omitting the keyword can introduce unpredictable timing issues or silent validation bypasses.
describe('My tests', async () => {
it('should verify my browser to have the expected url', async () => {
await expect(browser).toHaveUrl('https://example.com')
// Always await basic assertions as well since they resolve to promises under the hood
await expect(true).toBe(true)
})
})
Expected in tsconfig.json:
{
"compilerOptions": {
"types": [
// Enforces Promise-based assertion return types (Beta: Subject to future integration into @wdio/jasmine-framework)
"expect-webdriverio/jasmine-wdio-expect-async",
"@wdio/globals/types",
"@types/jasmine"
]
}
}
Warning: Because
@wdio/jasmine-frameworkoverrides synchronous matchers and introduces complicated type augmentations, a proposal was made for WebdriverIO v10 to preserve Jasmine's cleanexpectAsyncAPI, attach custom WDIO matchers directly to it, and keep basic matchers synchronous.
Note: When using Jasmine, Jest's expect matchers are not leveraged, meaning standard Jest-specific assertion matchers are unavailable.
Jasmine expectAsync
When you do not use @wdio/globals/types (or when @types/jasmine takes type-resolution priority), the global ambient expect resolves to Jasmine's native behavior. By defining expect-webdriverio/jasmine in your types, you can use WDIO custom matchers directly on expectAsync. Note that if you are running outside of @wdio/jasmine-framework, these matchers must be registered manually.
describe('My tests', async () => {
it('should verify my browser to have the expected url', async () => {
await expectAsync(browser).toHaveUrl('https://example.com')
// Standard Jasmine async matchers work as expected
await expectAsync(Promise.resolve(true)).toBeResolvedTo(true)
})
})
Expected in tsconfig.json:
{
"compilerOptions": {
"types": [
"@types/jasmine",
"expect-webdriverio/jasmine"
]
}
}
Use expect from expect-webdriverio
The expect export from expect-webdriverio remains available under Jasmine if you prefer an explicit import strategy. See the playground example.
import { expect as wdioExpect } from 'expect-webdriverio'
describe('My tests', async () => {
it('should verify my browser to have the expected url', async () => {
await wdioExpect(browser).toHaveUrl('https://example.com')
// Does not require await
wdioExpect(true).toBe(true)
})
})
Asymmetric matchers
Jasmine's asymmetric matchers have improved, but certain limitations may still exist.
jasmine.stringContaining,jasmine.stringMatching,jasmine.any(Type), andjasmine.anything()work seamlessly across the board.- Network matchers support
jasmine.objectContaining, whereas support in other areas (such as element matchers) might be limited. - WDIO asymmetric matchers also work properly.
import { expect as wdioExpect } from 'expect-webdriverio'
describe('My tests', async () => {
it('should verify my browser to have the expected url', async () => {
// Working Jasmine asymmetric matchers
await expectAsync(browser).toHaveUrl(jasmine.stringContaining('WebdriverIO'))
await expectAsync(browser).toHaveUrl(jasmine.stringMatching('/WebdriverIO/'))
await expectAsync(browser).toHaveUrl(jasmine.any(String))
await expectAsync(browser).toHaveUrl(jasmine.anything())
// Working WDIO asymmetric matcher
await expectAsync(browser).toHaveUrl(wdioExpect.stringContaining('WebdriverIO'))
})
})
Cucumber
More details to come. In short, when paired with @wdio/cucumber-framework, you can use WebDriverIO's expect library seamlessly within your Cucumber step definitions and Gherkin-based tests.
Custom Matchers
expect-webdriverio registers WebdriverIO custom matchers out of the box for a seamless experience.
To use WebdriverIO custom matchers (except asymmetric matchers) directly in:
- Jest: Register matchers manually with
expect.extend. - Jasmine: Using
@wdio/jasmine-frameworkprovides an out-of-the-box experience.- Else, register matchers manually with
jasmine.addAsyncMatchers, then they will be available onexpectAsync.
- Else, register matchers manually with
- Types: Type augmentation for custom matchers is provided. See Types.md for details.
Adding your own matchers
Similar to how expect-webdriverio provide custom matchers it's possible to add your own custom matchers.
- Jasmine see custom matchers doc
- Everyone else see Jest's expect.extend
Custom matchers should be added in wdio before hook
// wdio.conf.js
{
async before () {
const { addCustomMatchers } = await import('./myMatchers')
addCustomMatchers()
}
}
// myMatchers.js - Jest example
export function addCustomMatchers () {
if (global.expect.expect !== undefined) { // Temporary workaround. See https://github.com/webdriverio/expect-webdriverio/issues/835
global.expect = global.expect.expect;
}
expect.extend({
myMatcher (actual, expected) {
return { pass: actual === expected, message: () => 'some message' }
}
})
}
Examples
describe('suite', () => {
before(async () => {
await browser.url('https://github.com/webdriverio/expect-webdriverio')
await expect(browser).toHaveUrl('https://github.com/webdriverio/expect-webdriverio')
await expect(browser).toHaveTitle('expect-webdriverio', { containing: true })
})
it('find elements', async () => {
const formInputs = await $$('form input')
// make sure every form element is enabled
// waits automatically for formInputs to have at least one element
await expect(formInputs).toBeEnabled({ wait: 5000 })
const selectOptions = await $$('form select>option')
// make sure there is at least one option in select
await expect(selectOptions).toBeElementsArrayOfSize({ gte: 1 })
// or pass in an element directly
await expect($$('button')).toBeElementsArrayOfSize(3)
})
it('checks text values', async () => {
// assert certain text accurate
const repoTitle = await $('expect-webdriverio')
await expect(repoTitle).toHaveText('expect-webdriverio')
})
it('advanced', async () => {
const myInput = await $('input')
await expect(myInput).toHaveElementClass('form-control', { message: 'Not a form control!', })
await expect(myInput).toHaveAttribute('class', 'form-control') // alias toHaveAttr
await expect(myInput).toHaveValue('value', 'user', { containing: true, ignoreCase: true })
// Simply invert assertions
await expect(myInput).not.toHaveElementProperty('height', 0)
})
})
Boilerplate Projects
WebdriverIO test runner
- Mocha https://github.com/mgrybyk/webdriverio-devtools
- Cucumber https://gitlab.com/bar_foo/wdio-cucumber-typescript
- Jasmine https://github.com/webdriverio/jasmine-boilerplate
Standalone
more boilerplate projects coming soon, feel free to propose yours!