feat(wdio-runner): automatically include SoftAssertionService (#14546)

* feat: automatically include SoftAssertionService in the core

* fix(docs): clarify default inclusion of SoftAssertionService in documentation

* feat: add autoAssertOnTestEnd option to control soft assertion behavior

* Update website/docs/Configuration.md

* Update website/docs/Assertion.md

* refactor(docs): reorganize Soft Assertions section for clarity

* feat: add SoftAssertionService to expect-webdriverio mock

* fix(tests): adjust snapshot formatting for consistency in React component tests

---------

Co-authored-by: Christian Bromann <git@bromann.dev>
This commit is contained in:
JustasM 2025-06-12 01:16:37 +03:00 committed by GitHub
parent 5d47023f08
commit 10cddfd73c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 123 additions and 45 deletions

View file

@ -3,4 +3,5 @@ process.env.WDIO_ASSERTION_LIB_ACTIVATED = '1'
const setOptions = vi.fn()
const expect = vi.fn()
const matchers = new Map()
export { setOptions, expect, matchers }
const SoftAssertionService = vi.fn()
export { setOptions, expect, matchers, SoftAssertionService }

View file

@ -21,9 +21,9 @@ describe('React Component Testing', () => {
const { container } = render(<App />)
await expect(container).toMatchSnapshot()
await expect(container).toMatchInlineSnapshot(`
"<div>
<button>Current theme: light</button>
</div>"
`)
"<div>
<button>Current theme: light</button>
</div>"
`)
})
})

View file

@ -35,6 +35,7 @@ export const DEFAULT_CONFIGS: () => WebdriverIO.Config = () => ({
specFileRetries: 0,
specFileRetriesDelay: 0,
specFileRetriesDeferred: false,
autoAssertOnTestEnd: true,
reporterSyncInterval: 100,
reporterSyncTimeout: 5000,
cucumberFeaturesWithLineNumbers: [],

View file

@ -39,7 +39,7 @@
"@wdio/types": "workspace:*",
"@wdio/utils": "workspace:*",
"deepmerge-ts": "^7.0.3",
"expect-webdriverio": "^5.1.0",
"expect-webdriverio": "^5.3.0",
"webdriver": "workspace:*",
"webdriverio": "workspace:*"
},

View file

@ -5,7 +5,7 @@ import logger from '@wdio/logger'
import { initializeWorkerService, initializePlugin, executeHooksWithArgs } from '@wdio/utils'
import { ConfigParser } from '@wdio/config/node'
import { _setGlobal } from '@wdio/globals'
import { expect, setOptions, SnapshotService } from 'expect-webdriverio'
import { expect, setOptions, SnapshotService, SoftAssertionService } from 'expect-webdriverio'
import { attach } from 'webdriverio'
import type { Selector } from 'webdriverio'
import type { Options, Capabilities } from '@wdio/types'
@ -75,11 +75,16 @@ export default class Runner extends EventEmitter {
/**
* add built-in services
*/
const softAssertionService = new SoftAssertionService({
autoAssertOnTestEnd: this._config.autoAssertOnTestEnd || true
}, this._caps, this._config)
const snapshotService = SnapshotService.initiate({
updateState: this._config.updateSnapshots,
resolveSnapshotPath: this._config.resolveSnapshotPath
})
// ToDo(Christian): resolve type incompatibility between v8 and v9
this._configParser.addService(softAssertionService as any)
this._configParser.addService(snapshotService as any)
this._caps = this._isMultiremote

View file

@ -6,7 +6,7 @@ import { executeHooksWithArgs } from '@wdio/utils'
import { ConfigParser } from '@wdio/config/node'
import { attach } from 'webdriverio'
import { _setGlobal } from '@wdio/globals'
import { setOptions, SnapshotService } from 'expect-webdriverio'
import { setOptions, SnapshotService, SoftAssertionService } from 'expect-webdriverio'
import WDIORunner from '../src/index.js'
@ -18,6 +18,7 @@ vi.mock('util')
vi.mock('expect-webdriverio', () => ({
setOptions: vi.fn(),
expect: vi.fn(),
SoftAssertionService: vi.fn(),
SnapshotService: {
initiate: vi.fn().mockReturnValue({
results: ['foobar']

View file

@ -246,6 +246,11 @@ export interface Testrunner extends Hooks, WebdriverIO, WebdriverIO.HookFunction
* @default __snapshots__ stores snapshot files in __snapshots__ directory next to the test file.
*/
resolveSnapshotPath?: (testPath: string, snapExtension: string) => string
/**
* If set to true, soft assertions will be automatically asserted at the end of each test.
* @default true
*/
autoAssertOnTestEnd?: boolean
/**
* The number of retry attempts for an entire specfile when it fails as a whole.
*/

View file

@ -1197,8 +1197,8 @@ importers:
specifier: ^7.0.3
version: 7.1.5
expect-webdriverio:
specifier: ^5.1.0
version: 5.1.0(@wdio/globals@packages+wdio-globals)(@wdio/logger@packages+wdio-logger)(webdriverio@packages+webdriverio)
specifier: ^5.3.0
version: 5.3.0(@wdio/globals@packages+wdio-globals)(@wdio/logger@packages+wdio-logger)(webdriverio@packages+webdriverio)
webdriver:
specifier: workspace:*
version: link:../webdriver
@ -5492,9 +5492,6 @@ packages:
'@types/react@19.0.2':
resolution: {integrity: sha512-USU8ZI/xyKJwFTpjSVIrSeHBVAGagkHQKPNbxeWwql/vDmnTIBgx+TJnhFnj1NXgz8XfprU0egV2dROLGpsBEg==}
'@types/react@19.1.0':
resolution: {integrity: sha512-UaicktuQI+9UKyA4njtDOGBD/67t8YEBt2xdfqu8+gP9hqPUPsiXlNPcpS2gVdjmis5GKPG3fCxbQLVgxsQZ8w==}
'@types/react@19.1.6':
resolution: {integrity: sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q==}
@ -5714,6 +5711,9 @@ packages:
'@vitest/pretty-format@2.1.9':
resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==}
'@vitest/pretty-format@3.2.2':
resolution: {integrity: sha512-FY4o4U1UDhO9KMd2Wee5vumwcaHw7Vg4V7yR4Oq6uK34nhEJOmdRYrk3ClburPRUA09lXD/oXWZ8y/Sdma0aUQ==}
'@vitest/runner@2.1.9':
resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==}
@ -5723,6 +5723,9 @@ packages:
'@vitest/snapshot@2.1.9':
resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==}
'@vitest/snapshot@3.2.2':
resolution: {integrity: sha512-aMEI2XFlR1aNECbBs5C5IZopfi5Lb8QJZGGpzS8ZUHML5La5wCbrbhLOVSME68qwpT05ROEEOAZPRXFpxZV2wA==}
'@vitest/spy@2.1.8':
resolution: {integrity: sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==}
@ -6653,10 +6656,6 @@ packages:
resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==}
engines: {node: '>=8'}
ci-info@4.1.0:
resolution: {integrity: sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==}
engines: {node: '>=8'}
ci-info@4.2.0:
resolution: {integrity: sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==}
engines: {node: '>=8'}
@ -8062,6 +8061,14 @@ packages:
'@wdio/logger': ^9.0.0
webdriverio: ^9.0.0
expect-webdriverio@5.3.0:
resolution: {integrity: sha512-EUiibBYXWzSn9mJFuF914yeKUxvRxrhR9nImfe0YttG+wVg0v5jY1DPPnRz8sE0wlmoLHyLr155b5/d1+oZmxg==}
engines: {node: '>=18 || >=20 || >=22'}
peerDependencies:
'@wdio/globals': ^9.0.0
'@wdio/logger': ^9.0.0
webdriverio: ^9.0.0
expect@29.7.0:
resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@ -10963,6 +10970,9 @@ packages:
pathe@2.0.2:
resolution: {integrity: sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==}
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
pathval@2.0.0:
resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==}
engines: {node: '>= 14.16'}
@ -12942,6 +12952,10 @@ packages:
resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==}
engines: {node: '>=14.0.0'}
tinyrainbow@2.0.0:
resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
engines: {node: '>=14.0.0'}
tinyspy@3.0.2:
resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==}
engines: {node: '>=14.0.0'}
@ -14760,7 +14774,7 @@ snapshots:
'@babel/code-frame@7.26.2':
dependencies:
'@babel/helper-validator-identifier': 7.25.9
'@babel/helper-validator-identifier': 7.27.1
js-tokens: 4.0.0
picocolors: 1.1.1
@ -14858,7 +14872,7 @@ snapshots:
dependencies:
'@babel/core': 7.26.8
'@babel/helper-module-imports': 7.25.9
'@babel/helper-validator-identifier': 7.25.9
'@babel/helper-validator-identifier': 7.27.1
'@babel/traverse': 7.26.8
transitivePeerDependencies:
- supports-color
@ -15163,7 +15177,7 @@ snapshots:
'@babel/core': 7.26.8
'@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.8)
'@babel/helper-plugin-utils': 7.25.9
'@babel/helper-validator-identifier': 7.25.9
'@babel/helper-validator-identifier': 7.27.1
'@babel/traverse': 7.26.8
transitivePeerDependencies:
- supports-color
@ -15537,17 +15551,17 @@ snapshots:
'@babel/types@7.26.3':
dependencies:
'@babel/helper-string-parser': 7.25.9
'@babel/helper-validator-identifier': 7.25.9
'@babel/helper-validator-identifier': 7.27.1
'@babel/types@7.26.7':
dependencies:
'@babel/helper-string-parser': 7.25.9
'@babel/helper-validator-identifier': 7.25.9
'@babel/helper-validator-identifier': 7.27.1
'@babel/types@7.26.8':
dependencies:
'@babel/helper-string-parser': 7.25.9
'@babel/helper-validator-identifier': 7.25.9
'@babel/helper-validator-identifier': 7.27.1
'@babel/types@7.27.3':
dependencies:
@ -19071,16 +19085,12 @@ snapshots:
'@types/react-router@5.1.20':
dependencies:
'@types/history': 4.7.11
'@types/react': 19.1.0
'@types/react': 19.1.6
'@types/react@19.0.2':
dependencies:
csstype: 3.1.3
'@types/react@19.1.0':
dependencies:
csstype: 3.1.3
'@types/react@19.1.6':
dependencies:
csstype: 3.1.3
@ -19364,6 +19374,10 @@ snapshots:
dependencies:
tinyrainbow: 1.2.0
'@vitest/pretty-format@3.2.2':
dependencies:
tinyrainbow: 2.0.0
'@vitest/runner@2.1.9':
dependencies:
'@vitest/utils': 2.1.9
@ -19381,6 +19395,12 @@ snapshots:
magic-string: 0.30.17
pathe: 1.1.2
'@vitest/snapshot@3.2.2':
dependencies:
'@vitest/pretty-format': 3.2.2
magic-string: 0.30.17
pathe: 2.0.3
'@vitest/spy@2.1.8':
dependencies:
tinyspy: 3.0.2
@ -19589,7 +19609,7 @@ snapshots:
'@wdio/globals@9.15.0(@wdio/logger@9.15.0)(puppeteer-core@23.11.1)':
optionalDependencies:
expect-webdriverio: 5.1.0(@wdio/globals@9.15.0(@wdio/logger@9.15.0)(puppeteer-core@23.11.1))(@wdio/logger@9.15.0)(webdriverio@9.15.0(puppeteer-core@23.11.1))
expect-webdriverio: 5.3.0(@wdio/globals@9.15.0(@wdio/logger@9.15.0)(puppeteer-core@23.11.1))(@wdio/logger@9.15.0)(webdriverio@9.15.0(puppeteer-core@23.11.1))
webdriverio: 9.15.0(puppeteer-core@23.11.1)
transitivePeerDependencies:
- '@wdio/logger'
@ -19601,7 +19621,7 @@ snapshots:
'@wdio/globals@9.4.3(@wdio/logger@9.1.3)(puppeteer-core@23.11.1)':
optionalDependencies:
expect-webdriverio: 5.1.0(@wdio/globals@9.4.3(@wdio/logger@9.1.3)(puppeteer-core@23.11.1))(@wdio/logger@9.1.3)(webdriverio@9.4.3(puppeteer-core@23.11.1))
expect-webdriverio: 5.3.0(@wdio/globals@9.4.3(@wdio/logger@9.1.3)(puppeteer-core@23.11.1))(@wdio/logger@9.1.3)(webdriverio@9.4.3(puppeteer-core@23.11.1))
webdriverio: 9.4.3(puppeteer-core@23.11.1)
transitivePeerDependencies:
- '@wdio/logger'
@ -20662,8 +20682,6 @@ snapshots:
ci-info@3.9.0: {}
ci-info@4.1.0: {}
ci-info@4.2.0: {}
citty@0.1.6:
@ -22267,17 +22285,6 @@ snapshots:
webdriverio: link:packages/webdriverio
optional: true
expect-webdriverio@5.1.0(@wdio/globals@9.4.3(@wdio/logger@9.1.3)(puppeteer-core@23.11.1))(@wdio/logger@9.1.3)(webdriverio@9.4.3(puppeteer-core@23.11.1)):
dependencies:
'@vitest/snapshot': 2.1.9
'@wdio/globals': 9.4.3(@wdio/logger@9.1.3)(puppeteer-core@23.11.1)
'@wdio/logger': 9.1.3
expect: 29.7.0
jest-matcher-utils: 29.7.0
lodash.isequal: 4.5.0
webdriverio: 9.4.3(puppeteer-core@23.11.1)
optional: true
expect-webdriverio@5.1.0(@wdio/globals@packages+wdio-globals)(@wdio/logger@9.15.0)(webdriverio@packages+webdriverio):
dependencies:
'@vitest/snapshot': 2.1.9
@ -22308,6 +22315,38 @@ snapshots:
lodash.isequal: 4.5.0
webdriverio: link:packages/webdriverio
expect-webdriverio@5.3.0(@wdio/globals@9.15.0(@wdio/logger@9.15.0)(puppeteer-core@23.11.1))(@wdio/logger@9.15.0)(webdriverio@9.15.0(puppeteer-core@23.11.1)):
dependencies:
'@vitest/snapshot': 3.2.2
'@wdio/globals': 9.15.0(@wdio/logger@9.15.0)(puppeteer-core@23.11.1)
'@wdio/logger': 9.15.0
expect: 29.7.0
jest-matcher-utils: 29.7.0
lodash.isequal: 4.5.0
webdriverio: 9.15.0(puppeteer-core@23.11.1)
optional: true
expect-webdriverio@5.3.0(@wdio/globals@9.4.3(@wdio/logger@9.1.3)(puppeteer-core@23.11.1))(@wdio/logger@9.1.3)(webdriverio@9.4.3(puppeteer-core@23.11.1)):
dependencies:
'@vitest/snapshot': 3.2.2
'@wdio/globals': 9.4.3(@wdio/logger@9.1.3)(puppeteer-core@23.11.1)
'@wdio/logger': 9.1.3
expect: 29.7.0
jest-matcher-utils: 29.7.0
lodash.isequal: 4.5.0
webdriverio: 9.4.3(puppeteer-core@23.11.1)
optional: true
expect-webdriverio@5.3.0(@wdio/globals@packages+wdio-globals)(@wdio/logger@packages+wdio-logger)(webdriverio@packages+webdriverio):
dependencies:
'@vitest/snapshot': 3.2.2
'@wdio/globals': link:packages/wdio-globals
'@wdio/logger': link:packages/wdio-logger
expect: 29.7.0
jest-matcher-utils: 29.7.0
lodash.isequal: 4.5.0
webdriverio: link:packages/webdriverio
expect@29.7.0:
dependencies:
'@jest/expect-utils': 29.7.0
@ -23929,7 +23968,7 @@ snapshots:
'@jest/types': 30.0.0-alpha.7
'@types/node': 20.17.57
chalk: 4.1.2
ci-info: 4.1.0
ci-info: 4.2.0
graceful-fs: 4.2.11
picomatch: 4.0.2
@ -24239,7 +24278,7 @@ snapshots:
libnpmpublish@9.0.9:
dependencies:
ci-info: 4.1.0
ci-info: 4.2.0
normalize-package-data: 6.0.2
npm-package-arg: 11.0.2
npm-registry-fetch: 17.1.0
@ -25999,6 +26038,8 @@ snapshots:
pathe@2.0.2: {}
pathe@2.0.3: {}
pathval@2.0.0: {}
pause-stream@0.0.11:
@ -28339,6 +28380,8 @@ snapshots:
tinyrainbow@1.2.0: {}
tinyrainbow@2.0.0: {}
tinyspy@3.0.2: {}
tldts-core@6.1.68: {}

View file

@ -21,6 +21,21 @@ await expect(selectOptions).toHaveChildren({ gte: 1 })
For the full list, see the [expect API doc](/docs/api/expect-webdriverio).
## Soft Assertions
WebdriverIO includes soft assertions by default from expect-webdriver(5.2.0). Soft assertions allow your tests to continue execution even when an assertion fails. All failures are collected and reported at the end of the test.
### Usage
```js
// 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+/);
// Regular assertions still throw immediately
await expect(await $('.add-to-cart').isClickable()).toBe(true);
```
## Migrating from Chai
[Chai](https://www.chaijs.com/) and [expect-webdriverio](https://github.com/webdriverio/expect-webdriverio#readme) can coexist, and with some minor adjustments a smooth transition to expect-webdriverio can be achieved. If you've upgraded to WebdriverIO v6 then by default you will have access to all the assertions from `expect-webdriverio` out of the box. This means that globally wherever you use `expect` you would call an `expect-webdriverio` assertion. That is, unless you you set [`injectGlobals`](/docs/configuration#injectglobals) to `false` or have explicitly overridden the global `expect` to use Chai. In this case you would not have access to any of the expect-webdriverio assertions without explicitly importing the expect-webdriverio package where you need it.

View file

@ -424,6 +424,13 @@ By default, it is set to `false` so logs are printed in real-time.
Type: `Boolean`<br />
Default: `false`
### autoAssertOnTestEnd
Controls whether WebdriverIO automatically asserts all soft assertions at the end of each test. When set to `true`, any accumulated soft assertions will be automatically checked and cause the test to fail if any assertions failed. When set to `false`, you must manually call the assert method to check soft assertions.
Type: `Boolean`<br />
Default: `true`
### services
Services take over a specific job you don't want to take care of. They enhance your test setup with almost no effort.