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.
Note: Browser, element, network and snapshot matchers also support multi-remote, checking every browser instance with a single expected value or, for browser, element and network matchers, one per instance.
Examples that do not open a page or show their HTML assume the guinea pig test page is open:
await browser.url('https://guinea-pig.webdriver.io/')
// or, with multi-remote
await multiRemoteBrowser.url('https://guinea-pig.webdriver.io/')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('test page smoke', async () => {
// These won't throw immediately if they fail
await expect.soft(await $('header h1').getText()).toEqual('WebdriverJS Testpage');
await expect.soft(await $('.sometext').getText()).toMatch(/some/);
// Also work with basic matcher
const h1Text = await $('header h1').getText()
expect.soft(h1Text).toEqual('WebdriverJS Testpage');
// Regular assertions still throw immediately
await expect(await $('.sendBtn').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) 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.
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.
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 { setDefaultOptions } from 'expect-webdriverio'
export const config = {
// ...
before () {
setDefaultOptions({ 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: 2000 |
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. A RegExp expected value is matched case-insensitively instead (the i flag is applied), since a pattern cannot be lowercased safely. |
trim |
boolean | apply trim to actual value. Default: true |
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 matchers take a number or a NumberMatcher as the expected value, and the command options as the next argument, e.g. toHaveWidth({ gte: 32 }, { wait: 0 }).
| Name | Type | Details |
|---|---|---|
eq |
number | equals |
lte |
number | less then equals |
gte |
number | greater than or equals |
Give eq alone, or a range with gte, lte or both. The types reject {} and eq with gte or lte.
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 support the multi-remote browser, with a single expected value or one per instance, see Multi-remote Support.
The browser matchers also accept a WebdriverIO v10 browsing context: a tab, a window or a frame that browser.url(), browser.newWindow(), browser.browsingContexts() or context.frame() give in a WebDriver BiDi session. They read the document of that context, not the one of the current context of the session.
const page = await browser.url('https://webdriver.io/')
await expect(page).toHaveTitle(expect.stringContaining('WebdriverIO'))
const frame = await page.frame(page.$('iframe'))
await expect(frame).toHaveUrl(expect.stringContaining('/embed'))
await expect(frame).toHaveLocalStorageItem('key') // the local storage of the frame's origin- A failure message names the browser, the window or the frame, and its URL, e.g.
Expect chrome's frame (https://webdriver.io/embed) to have title. It is the URL of the last navigation of the context (context.url). A frame found by its element has no URL untilnavigate()orgetUrl(), so the message shows no URL for it.toHaveUrldoes not show it, because theReceivedline already shows the URL. - The title of a frame document without
<title>is''. toHaveClipboardTextsets the clipboard permission for the whole session. In a cross-origin frame, the permissions policy of the page can blockclipboard-read.- A browsing context is of one browser: there are no per-instance values.
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'))Checks if browser has a specific item in localStorage with an optional value.
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+$/)Use expect.arrayContaining() to match values from an element collection in any order, allowing extra elements. This works with toHaveText, toHaveHTML, toHaveAttribute, toHaveElementProperty, toHaveValue, toHaveElementClass, toHaveComputedLabel, toHaveComputedRole, toHaveId, and toHaveHref (including their aliases). To compare with expect.oneOf(), see Choosing the expected value.
await expect($$('header a')).toHaveText(expect.arrayContaining(['2', '1']))
await expect($$('form input')).toHaveValue(expect.arrayContaining(['b', 'a']))
await expect($$('a')).toHaveAttribute('href', expect.arrayContaining([expect.stringContaining('two.html')]))
await expect($$('button')).not.toHaveComputedLabel(expect.arrayContaining(['Delete']))Each attempt reads one value per element concurrently and applies the asymmetric matcher once to the complete array. Selector-backed collections are refetched on retries. Nested matchers, .not, and expect.not.arrayContaining() retain their normal matching rules. An empty collection matches arrayContaining([]); static empty arrays cannot be retried into a non-empty result.
String comparison options such as trim, ignoreCase, and containing do not transform the collected values or nested matchers. Getter options still apply, such as includeSelectorTag for HTML and asString for properties. Class matching collects each element's complete class attribute, not individual class tokens.
This collection comparison does not change boolean assertions, style or size assertions, or some()'s per-element matching. A single element's array-valued property can still be matched directly with toHaveElementProperty.
Calls isDisplayed on given element.
await expect($('#someElem')).toBeDisplayed()Calls isExisting on given element.
await expect($('#someElem')).toExist()Same as toExist.
await expect($('#someElem')).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 $('.searchinput')
await expect(myInput).toHaveAttribute('name', 'searchinput')
await expect(myInput).toHaveAttribute('name', expect.stringContaining('search'))Checks if an element has a specific attribute.
const myInput = await $('.searchinput')
await expect(myInput).toHaveAttribute('name')
// With options
await expect(myInput).toHaveAttribute('name', expect.anything(), { wait: 1000 })Checks if an element does not have the specified attribute.
const myInput = await $('.searchinput')
await expect(myInput).not.toHaveAttribute('disabled')
// With options
await expect(myInput).not.toHaveAttribute('disabled', expect.anything(), { wait: 1000 })Checks if one of the classes of an element matches the expected value: a class name, a regular expression or an asymmetric matcher. Each class is compared, also with an asymmetric matcher. To compare the full class attribute, use toHaveAttribute('class', ...).
const box = await $('#purplebox')
await expect(box).toHaveElementClass('box', { message: 'Not a box!' })
await expect(box).toHaveElementClass(expect.stringContaining('purp'), { message: 'Not a purple box!' })
// has any of the classes
await expect(box).toHaveElementClass(expect.oneOf('purple', 'red'))
// has all the classes: 1 assertion for each class
await expect(box).toHaveElementClass('box')
await expect(box).toHaveElementClass('purple')An array of expected values works only with $$(): one expected value for each element. On a single element, it fails the assertion.
Checks if an element has a certain property and value
const elem = await $('#elem')
await expect(elem).toHaveElementProperty('height', 23)
await expect(elem).not.toHaveElementProperty('height', 0)Checks if an element has a certain property.
const elem = await $('#elem')
await expect(elem).toHaveElementProperty('height')
// With options
await expect(elem).toHaveElementProperty('height', expect.anything(), { wait : 1 })
// Does not have height property
await expect(elem).not.toHaveElementProperty('height')
// With options
await expect(elem).not.toHaveElementProperty('height', expect.anything(), { wait : 1 })Checks if an input element has a certain value.
const myInput = await $('.waitForValueEnabled')
await expect(myInput).toHaveValue('Some Content', { ignoreCase: true })
await expect(myInput).toHaveValue(expect.stringContaining('Content'), { ignoreCase: true })Checks if an element can be clicked by calling isClickable on the element.
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.
await expect($('#elem')).toBeSelected()Same as toBeSelected.
await expect($('#elem')).toBeChecked()Checks if element has a specific computed WAI-ARIA label. Use expect.oneOf() when the element can have different labels.
await browser.url('https://webdriver.io/')
const elem = await $('a.navbar__link.header-github-link')
await expect(elem).toHaveComputedLabel('GitHub repository')
await expect(elem).toHaveComputedLabel(expect.stringContaining('repository'))await browser.url('https://webdriver.io/')
const elem = await $('a.navbar__link.header-github-link')
await expect(elem).toHaveComputedLabel(expect.oneOf('GitHub repository', 'Private repository'))
await expect(elem).toHaveComputedLabel(expect.oneOf(expect.stringContaining('GitHub'), expect.stringContaining('Private')))Checks if element has a specific computed WAI-ARIA role. Use expect.oneOf() when the element can have different roles.
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(expect.oneOf('region', 'section'))
await expect(elem).toHaveComputedRole(expect.oneOf(expect.stringContaining('reg'), expect.stringContaining('sec')))Checks if link element has a specific link target.
const link = await $('#githubRepo')
await expect(link).toHaveHref('https://github.com')
await expect(link).toHaveHref(expect.stringContaining('github.com'))Same as toHaveHref.
const link = await $('#githubRepo')
await expect(link).toHaveLink('https://github.com')
await expect(link).toHaveLink(expect.stringContaining('github.com'))Checks if element has a specific id attribute.
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.
await expect($('#elem')).toHaveStyle({
'font-family': 'Faktum',
'font-weight': '500',
'font-size': '12px',
})Checks if an element matches a specific text exactly. You can also pass an asymmetric matcher like expect.stringContaining() for partial matches, or use expect.oneOf() if the element can have different possible texts.
Note: An array of expected values is for multiple elements $$() only, one value per element. On a single element, it fails the assertion: use expect.oneOf().
await browser.url('https://webdriver.io/')
const elem = await $('.hero__subtitle')
// Exact match assertion
await expect(elem).toHaveText('Next-gen browser and mobile automation test framework for Node.js')
// Partial match assertion using stringContaining
await expect(elem).toHaveText(expect.stringContaining('test framework for Node.js'))
// Succeeds if one of the text options matches (v6.0.0+)
await expect(elem).toHaveText(expect.oneOf('Next-gen browser and mobile automation test framework for Node.js', 'Get Started'))
await expect(elem).toHaveText(expect.oneOf(expect.stringContaining('test framework for Node.js'), expect.stringContaining('Started')))If you have a list of elements like the HTML structure below:
<ul>
<li>Coffee</li>
<li>Tea</li>
<li>Milk</li>
</ul>
You can assert all of them at once using an array:
// Index-based: each element must match the value at its index
await expect($$('ul > li')).toHaveText(['Coffee', 'Tea', 'Milk'])Use expect.arrayContaining() to check for a subset of texts in any order. Extra elements are allowed. See Matching a subset of element values for retry behavior, options, and other supported matchers.
await expect($$('ul > li')).toHaveText(expect.arrayContaining(['Tea', 'Coffee']))
await expect($$('ul > li')).toHaveText(expect.arrayContaining([expect.stringContaining('Coff')]))
await expect($$('ul > li')).not.toHaveText(expect.arrayContaining(['Juice']))Checks if an element matches a specific text exactly. You can also pass an asymmetric matcher like expect.stringContaining() for partial matches, or use expect.oneOf() if the element can have different possible texts.
Note: An array of expected values is for multiple elements $$() only, one value per element. On a single element, it fails the assertion: use expect.oneOf().
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(expect.oneof('Next-gen browser and mobile automation test framework for Node.js', 'Get Started'), { includeSelectorTag: false })
await expect(elem).toHaveHTML(expect.oneof(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.
await expect($('#elem')).toBeDisplayedInViewport()Checks amount of the fetched element's children by calling element.$('./*') command.
const list = await $('#selectbox')
await expect(list).toHaveChildren() // the list has at least one option
// same as
await expect(list).toHaveChildren({ gte: 1 })
await expect(list).toHaveChildren(3) // the list has 3 options
// same as
await expect(list).toHaveChildren({ eq: 3 })Checks if element has a specific width.
await browser.url('http://github.com')
const logo = await $('[aria-label="Homepage"] .octicon-mark-github')
await expect(logo).toHaveWidth(32)
// Same as
await expect(logo).toHaveWidth({ eq: 32 })
// Greater/Less than equals or in between
await expect(logo).toHaveWidth({ gte: 32 })
await expect(logo).toHaveWidth({ lte: 34 })
await expect(logo).toHaveWidth({ gte: 32, lte: 34 })Checks if element has a specific height.
await browser.url('http://github.com')
const logo = await $('[aria-label="Homepage"] .octicon-mark-github')
await expect(logo).toHaveHeight(32)
// Same as
await expect(logo).toHaveHeight({ eq: 32 })
// Greater/Less than equals or in between
await expect(logo).toHaveHeight({ gte: 32 })
await expect(logo).toHaveHeight({ lte: 34 })
await expect(logo).toHaveHeight({ gte: 32, lte: 34 })Checks if element has a specific size. Note: gte and lte are not supported yet.
await browser.url('http://github.com')
const logo = await $('[aria-label="Homepage"] .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 $$('.box')
await expect(listItems).toBeElementsArrayOfSize(5) // 5 boxes
// Greater/Less then
await expect(listItems).toBeElementsArrayOfSize({ lte: 10 })
// same as
assert.ok(listItems.length <= 10)
await expect(listItems).toBeElementsArrayOfSize({ gte: 5 })
// In between
await expect(listItems).toBeElementsArrayOfSize({ gte: 5, lte: 5 })With multi-remote, the size is checked per browser instance. Pass a single size that every instance must match, or one size per instance (every instance must be listed):
const listItems = await multiRemoteBrowser.$$('.box')
await expect(listItems).toBeElementsArrayOfSize(5) // 5 boxes in every browser
await expect(listItems).toBeElementsArrayOfSize(expect.multiRemote({ chrome: 5, firefox: { gte: 3 } }))All element matchers support arrays of elements returned from $$():
- Standard Behavior: Every element must pass. One failure fails the assertion.
- Using
.not: Every element must not meet the matcher condition. One match fails the assertion. - Empty Arrays: Empty element arrays will fail the assertion by default.
- Note: Only the
toExist,toBeExisting, andtoBePresentmatchers succeed when using.noton an empty element array.
- Note: Only the
- Retry / Array Refresh: On failure or stale references, the element array is automatically re-fetched until the matcher passes or times out.
- Multi-remote: The same rules apply per browser instance, each on its own elements, see Multi-remote Support.
- See MultipleElements.md for more details.
// Elements awaited selector syntax
const elements = await $$('#someElements')
await expect(elements).toBeDisplayed()
// Elements non-awaited
await expect($$('#someElements')).toBeDisplayed()
// Works with filtered elemnts array
await expect($$('#someElements').filter((t) => t.isExisting())).toBeDisplayed()
// Using the .not modifier (Asserts NO elements are displayed)
await expect($$('#someElements')).not.toBeDisplayed()const elements = await $$('#someElem')
// Single value: checked against every element
await expect(elements).toHaveAttribute('class', 'form-control')
// One of: checked against every element and succeed if one of the expected text matches
await expect(elements).toHaveAttribute('class', expect.oneOf('form-control1', 'form-control2'))
// Array: each value checked at corresponding element index (must match length)
await expect(elements).toHaveAttribute('class', ['control1', 'control2'])
// Use asymmetric matchers for flexible matching
await expect(elements).toHaveAttribute('class', [expect.stringContaining('control1'), 'control2'])
// Use RegEx `i` for case insensitive
await expect(elements).toHaveAttribute('class', [/Control1/i, 'control2'])
// Use RegEx with `|` to mimic contains
await expect(elements).toHaveAttribute('class', /Control1|Control2/)
await expect(elements).toHaveAttribute('class', [/Control1|Control2/, 'control3'])
// Elements non-awaited
await expect($$('#someElem')).toHaveAttribute('class', 'form-control')
// Works with filtered elements array too
await expect($$('#someElem').filter(el => el.isExisting())).toHaveAttribute('class', ['control1', 'control2'])Network matchers also support the mocks of a multi-remote mock(), with one expected value for every browser instance or, with expect.multiRemote(), one per instance, see Multi-remote Support.
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)
// same as
await expect(mock).toBeRequestedTimes({ eq: 2 })
// request called at least 5 times but less than 11
await expect(mock).toBeRequestedTimes({ gte: 5, lte: 10 }) Checks 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
})Note on
postData/responsetiming: unlike the other options, the request/response body is collected asynchronously (an extra round-trip after the request/response headers are already available), so it may not be attached to a call yet at the very first check. Regular assertions retry within theirwaittimeout and pick it up once it arrives..not.toBeRequestedWith({ postData / response })also retries withinwait- but only while there's a call that already matches every other criterion and is just waiting on its body to attach; if nothing matches at all (wrong URL, no call made, etc.) it still resolves immediately, same as any other.notassertion. The one residual case this can't close: if the body genuinely takes longer to arrive than your configuredwait, a.notassertion can still report a false pass. If you rely on.notwithpostData/responseand see intermittent false passes, increasewait(or await a signal that the request has fully completed) before asserting.
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 `outerHTML` of multi-remote elements, $() or $$(): shared by every instance, else keyed by instance name (requires "await")
await expect(multiRemoteBrowser.$('elem')).toMatchSnapshot()
await expect(multiRemoteBrowser.$$('.box')).toMatchSnapshot()
// snapshot `outerHTML` of every element of $$() as an array (requires "await")
await expect($$('.box')).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($('#secondPageLink')).toMatchInlineSnapshot()Instead of creating a snapshot file, WebdriverIO will modify the test file directly to update the snapshot as a string:
await expect($('#secondPageLink')).toMatchInlineSnapshot(`"<a href="./two.html" id="secondPageLink">two</a>"`)With $$(), the snapshot is the outerHTML of every element as an array:
await expect($$('header a')).toMatchInlineSnapshot(`
[
"<a href="pointer.html">3</a>",
"<a href="gestureTest.html">2</a>",
"<a href="index.html">1</a>",
]
`)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($('#purplebox')).toMatchElementSnapshot('purpleBox', 0, {
// options
})The expected result is by default 0, so you can write the same assertion as:
await expect($('#purplebox')).toMatchElementSnapshot('purpleBox', {
// options
})or not pass in any options at all:
await expect($('#purplebox')).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 $('.hero__subtitle')
await expect(elem).toHaveText(/node\.js/i)
await expect(elem).toHaveText(expect.oneOf(/node\.js/i, 'Get Started'))
await expect(browser).toHaveTitle(/webdriverio/i)
await expect(browser).toHaveUrl(/webdriver\.io/)
await expect(elem).toHaveElementClass(/Hero__Subtitle/i)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');For Jasmine, see the official documentation for expect/expectAsync, matchers, and async-matchers.
Note:
- With the global
expectof@wdio/jasmine-framework, the Jasmine synchronous matchers stay synchronous. The WebdriverIO matchers and the Jasmine async matchers return a promise. See Jasmine. - Default matchers are still available if you import
expectdirectly fromexpect-webdriverioinstead of using the global.
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.
since: v6.0.0
The custom some() function (distinct syntax compared to .not) allows you to assert that at least one element from multiple elements meets the assertion.
import { some } from 'expect-webdriverio/api'
// Allow to succeed only if one element matches (or not match)
// Succeeds if at least one element matches either 'optionA' or 'optionB'
await expect(some($$('elements'))).toHaveText(expect.oneOf('optionA', 'optionB'));
// Succeeds if at least one element does not matches either 'forbiddenTextA' or 'forbiddenTextB'
await expect(some($$('elements'))).not.toHaveText(/forbiddenTextA|forbiddenTextB/);
// Succeeds if the first element matches 'valueForIndex0' OR the second matches 'valueForIndex1'
await expect(some($$('elements'))).toHaveText(['valueForIndex0', 'valueForIndex1']);Note: With multi-remote, some() requires at least one matching element in every browser instance.
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'))With multi-remote, passes one expected value per browser instance, keyed by instance name. Every instance must be listed.
await expect(multiRemoteBrowser).toHaveTitle(expect.multiRemote({ chrome: 'WebdriverJS Testpage', firefox: expect.stringContaining('Testpage') }))
await expect(multiRemoteBrowser.$('header h1')).toHaveStyle(expect.multiRemote({ chrome: { color: 'red' }, firefox: { color: 'blue' } }))See Expected Values for the plain object shorthand.
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(),
})jasmine.arrayContaining() is also supported for element collection values, including nested Jasmine matchers. Limitations with Jasmine collection and object matchers may still apply in other assertion contexts.