From 10cddfd73c40f1ba50c228f8bac024b000511c01 Mon Sep 17 00:00:00 2001 From: JustasM <59362982+JustasMonkev@users.noreply.github.com> Date: Thu, 12 Jun 2025 01:16:37 +0300 Subject: [PATCH] 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 --- __mocks__/expect-webdriverio.ts | 3 +- e2e/browser-runner/react.test.tsx | 8 +- packages/wdio-config/src/constants.ts | 1 + packages/wdio-runner/package.json | 2 +- packages/wdio-runner/src/index.ts | 7 +- packages/wdio-runner/tests/index.test.ts | 3 +- packages/wdio-types/src/Options.ts | 5 + pnpm-lock.yaml | 117 ++++++++++++++++------- website/docs/Assertion.md | 15 +++ website/docs/Configuration.md | 7 ++ 10 files changed, 123 insertions(+), 45 deletions(-) diff --git a/__mocks__/expect-webdriverio.ts b/__mocks__/expect-webdriverio.ts index 5b60f1afa..362f85a7b 100644 --- a/__mocks__/expect-webdriverio.ts +++ b/__mocks__/expect-webdriverio.ts @@ -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 } diff --git a/e2e/browser-runner/react.test.tsx b/e2e/browser-runner/react.test.tsx index 07086e06c..630c93c34 100644 --- a/e2e/browser-runner/react.test.tsx +++ b/e2e/browser-runner/react.test.tsx @@ -21,9 +21,9 @@ describe('React Component Testing', () => { const { container } = render() await expect(container).toMatchSnapshot() await expect(container).toMatchInlineSnapshot(` - "
- -
" - `) +"
+ +
" +`) }) }) diff --git a/packages/wdio-config/src/constants.ts b/packages/wdio-config/src/constants.ts index ee66266dd..8775ccb7b 100644 --- a/packages/wdio-config/src/constants.ts +++ b/packages/wdio-config/src/constants.ts @@ -35,6 +35,7 @@ export const DEFAULT_CONFIGS: () => WebdriverIO.Config = () => ({ specFileRetries: 0, specFileRetriesDelay: 0, specFileRetriesDeferred: false, + autoAssertOnTestEnd: true, reporterSyncInterval: 100, reporterSyncTimeout: 5000, cucumberFeaturesWithLineNumbers: [], diff --git a/packages/wdio-runner/package.json b/packages/wdio-runner/package.json index 8d2921176..ab4e85816 100644 --- a/packages/wdio-runner/package.json +++ b/packages/wdio-runner/package.json @@ -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:*" }, diff --git a/packages/wdio-runner/src/index.ts b/packages/wdio-runner/src/index.ts index a3b15c5ba..ca9949bdc 100644 --- a/packages/wdio-runner/src/index.ts +++ b/packages/wdio-runner/src/index.ts @@ -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 diff --git a/packages/wdio-runner/tests/index.test.ts b/packages/wdio-runner/tests/index.test.ts index 23d4467c2..bf7105e31 100644 --- a/packages/wdio-runner/tests/index.test.ts +++ b/packages/wdio-runner/tests/index.test.ts @@ -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'] diff --git a/packages/wdio-types/src/Options.ts b/packages/wdio-types/src/Options.ts index 1b2a18ad2..1dcea085b 100644 --- a/packages/wdio-types/src/Options.ts +++ b/packages/wdio-types/src/Options.ts @@ -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. */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e81715709..4a03920cf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: {} diff --git a/website/docs/Assertion.md b/website/docs/Assertion.md index 7f42b0810..9f8d842e7 100644 --- a/website/docs/Assertion.md +++ b/website/docs/Assertion.md @@ -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. diff --git a/website/docs/Configuration.md b/website/docs/Configuration.md index 29b0760bd..2e83d9f47 100644 --- a/website/docs/Configuration.md +++ b/website/docs/Configuration.md @@ -424,6 +424,13 @@ By default, it is set to `false` so logs are printed in real-time. Type: `Boolean`
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`
+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.