|
| 1 | +# Expect-WebDriverIO Framework |
| 2 | + |
| 3 | +Expect-WebDriverIO is inspired by [`expect`](https://www.npmjs.com/package/expect) but also extends it. Therefore, we can use everything provided by the expect API with some WebDriverIO enhancements. Yes, this is a package of Jest but it is usable without Jest. |
| 4 | + |
| 5 | +## Compatibility |
| 6 | + |
| 7 | +We can pair `expect-webdriverio` with [Jest](https://jestjs.io/), [Mocha](https://mochajs.org/), and [Jasmine](https://jasmine.github.io/) and even [Cucumber](https://www.npmjs.com/package/@cucumber/cucumber) |
| 8 | + |
| 9 | +It is highly recommended to use it with a [WDIO Testrunner](https://webdriver.io/docs/clioptions) which provides additional auto-configuration for a plug-and-play experience. |
| 10 | + |
| 11 | +When used <u>**outside of [WDIO Testrunner](https://webdriver.io/docs/clioptions)**</u>, types need to be added to your [`tsconfig.json`](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html), and some additional configuration for WDIO matchers, soft assertions, and snapshot service is required. |
| 12 | + |
| 13 | +### Jest |
| 14 | +We can use `expect-webdriverio` with [Jest](https://jestjs.io/) using [`@jest/globals`](https://www.npmjs.com/package/@jest/globals) alone (preferred) and optionally [`@types/jest`](https://www.npmjs.com/package/@types/jest) (which has global ambient support). |
| 15 | + - Note: Jest maintainers do not support [`@types/jest`](https://www.npmjs.com/package/@types/jest). If this library gets out of date or has problems, support might be dropped. |
| 16 | + - Note: With Jest, the matchers `toMatchSnapshot` and `toMatchInlineSnapshot` are overloaded. To resolve the types correctly, `expect-webdriverio/jest` must be last. |
| 17 | + |
| 18 | +#### With `@jest/globals` |
| 19 | +When paired only with [`@jest/globals`](https://www.npmjs.com/package/@jest/globals), we should `import` the `expect` function from `expect-webdriverio`. |
| 20 | + |
| 21 | +```ts |
| 22 | +import { expect } from 'expect-webdriverio' |
| 23 | +import { describe, it, expect as jestExpect } from '@jest/globals' |
| 24 | + |
| 25 | +describe('My tests', async () => { |
| 26 | + it('should verify my browser to have the expected url', async () => { |
| 27 | + await expect(browser).toHaveUrl('https://example.com') |
| 28 | + }) |
| 29 | +}) |
| 30 | +``` |
| 31 | + |
| 32 | +No `types` are expected in `tsconfig.json`. |
| 33 | +Optionally, to avoid needing `import { expect } from 'expect-webdriverio'`, you can use the following: |
| 34 | + |
| 35 | + |
| 36 | +```json |
| 37 | +{ |
| 38 | + "compilerOptions": { |
| 39 | + "types": ["expect-webdriverio/expect-global"] |
| 40 | + } |
| 41 | +} |
| 42 | +``` |
| 43 | +##### Augmenting `@jest/globals` JestMatchers |
| 44 | +Multiple attempts were made to augment `@jest/globals` to support `expect-webdriverio` matchers directly on JestMatchers. However, no namespace is available to augment it; therefore, only module augmentation can be used. This method does not allow adding matchers with the `extends` keyword; instead, they need to be added directly in the interface of the module declaration augmentation, which would create a lot of code duplication. |
| 45 | + |
| 46 | +This [Jest issue](https://github.com/jestjs/jest/issues/12424) seems to target this problem, but it is still in progress. |
| 47 | + |
| 48 | +#### With `@types/jest` |
| 49 | +When also paired with [`@types/jest`](https://www.npmjs.com/package/@types/jest), no imports are required. Global ambient types are already defined correctly and you can simply use Jest's `expect` directly. |
| 50 | + |
| 51 | +If you are NOT using WDIO Testrunner, some prerequisite configuration is required. |
| 52 | + |
| 53 | +Option 1: Replace the expect globally with the `expect-webdriverio` one: |
| 54 | +```ts |
| 55 | +import { expect } from "expect-webdriverio"; |
| 56 | +(globalThis as any).expect = expect; |
| 57 | +``` |
| 58 | + |
| 59 | +Option 2: Reconfigure Jest's expect with the custom matchers and the soft assertion: |
| 60 | +```ts |
| 61 | +// Configure the custom matchers: |
| 62 | +import { expect } from "@jest/globals"; |
| 63 | +import { matchers } from "expect-webdriverio"; |
| 64 | + |
| 65 | +beforeAll(async () => { |
| 66 | + expect.extend(matchers as Record<string, any>); |
| 67 | +}); |
| 68 | +``` |
| 69 | + |
| 70 | +[Optional] For the soft assertion, the `createSoftExpect` is currently not correctly exposed but the below works: |
| 71 | +```ts |
| 72 | +// @ts-ignore |
| 73 | +import * as createSoftExpect from "expect-webdriverio/lib/softExpect"; |
| 74 | + |
| 75 | +beforeAll(async () => { |
| 76 | + Object.defineProperty(expect, "soft", { |
| 77 | + value: <T = unknown>(actual: T) => createSoftExpect.default(actual), |
| 78 | + }); |
| 79 | + |
| 80 | + // Add soft assertions utility methods |
| 81 | + Object.defineProperty(expect, "getSoftFailures", { |
| 82 | + value: (testId?: string) => SoftAssertService.getInstance().getFailures(testId), |
| 83 | + }); |
| 84 | + |
| 85 | + Object.defineProperty(expect, "assertSoftFailures", { |
| 86 | + value: (testId?: string) => SoftAssertService.getInstance().assertNoFailures(testId), |
| 87 | + }); |
| 88 | + |
| 89 | + Object.defineProperty(expect, "clearSoftFailures", { |
| 90 | + value: (testId?: string) => SoftAssertService.getInstance().clearFailures(testId), |
| 91 | + }); |
| 92 | +}); |
| 93 | +``` |
| 94 | + |
| 95 | +Then as shown below, no imports are required and we can use WDIO matchers directly on Jest's `expect`: |
| 96 | +```ts |
| 97 | +describe('My tests', async () => { |
| 98 | + it('should verify my browser to have the expected url', async () => { |
| 99 | + await expect(browser).toHaveUrl('https://example.com') |
| 100 | + }) |
| 101 | +}) |
| 102 | +``` |
| 103 | + |
| 104 | +Expected in `tsconfig.json`: |
| 105 | +```json |
| 106 | +{ |
| 107 | + "compilerOptions": { |
| 108 | + "types": [ |
| 109 | + "@types/jest", |
| 110 | + "expect-webdriverio/jest" // Must be last for overloaded matchers `toMatchSnapshot` and `toMatchInlineSnapshot` |
| 111 | + ] |
| 112 | + } |
| 113 | +} |
| 114 | +``` |
| 115 | + |
| 116 | +### Mocha |
| 117 | +When paired with [Mocha](https://mochajs.org/), it can be used without (standalone) or with [`chai`](https://www.chaijs.com/) (or any other assertion library). |
| 118 | + |
| 119 | +#### Standalone |
| 120 | +No import is required; everything is set globally. |
| 121 | + |
| 122 | +```ts |
| 123 | +describe('My tests', async () => { |
| 124 | + it('should verify my browser to have the expected url', async () => { |
| 125 | + await expect(browser).toHaveUrl('https://example.com') |
| 126 | + }) |
| 127 | +}) |
| 128 | +``` |
| 129 | + |
| 130 | +Expected in `tsconfig.json`: |
| 131 | +```json |
| 132 | +{ |
| 133 | + "compilerOptions": { |
| 134 | + "types": [ |
| 135 | + "@types/mocha", |
| 136 | + "expect-webdriverio/expect-global" |
| 137 | + ] |
| 138 | + } |
| 139 | +} |
| 140 | +``` |
| 141 | + |
| 142 | +#### Chai |
| 143 | +`expect-webdriverio` can coexist with the [Chai](https://www.chaijs.com/) assertion library by importing both libraries explicitly. |
| 144 | +See also this [documentation](https://webdriver.io/docs/assertion/#migrating-from-chai). |
| 145 | + |
| 146 | +### Jasmine |
| 147 | +When paired with [Jasmine](https://jasmine.github.io/), [`@wdio/jasmine-framework`](https://www.npmjs.com/package/@wdio/jasmine-framework) is also required to configure it correctly, as it needs to force `expect` to be `expectAsync` and also register the WDIO matchers with `addAsyncMatcher` since `expect-webdriverio` only supports the Jest-style `expect.extend` version. |
| 148 | + |
| 149 | +The types `expect-webdriverio/jasmine` are still offered but are subject to removal or being moved into `@wdio/jasmine-framework`. The usage of `expectAsync` is also subject to future removal. |
| 150 | + |
| 151 | +#### Jasmine `expectAsync` |
| 152 | +When not using `@wdio/globals/types` or having `@types/jasmine` before it, the Jasmine expect is shown as the global ambient type. Therefore, when also defining `expect-webdriverio/jasmine`, we can use WDIO custom matchers on the `expectAsync`. Without `@wdio/jasmine-framework`, matchers will need to be registered manually. |
| 153 | + |
| 154 | +```ts |
| 155 | +describe('My tests', async () => { |
| 156 | + it('should verify my browser to have the expected url', async () => { |
| 157 | + await expectAsync(browser).toHaveUrl('https://example.com') |
| 158 | + await expectAsync(true).toBe(true) |
| 159 | + }) |
| 160 | +}) |
| 161 | +``` |
| 162 | + |
| 163 | +Expected in `tsconfig.json`: |
| 164 | +```json |
| 165 | +{ |
| 166 | + "compilerOptions": { |
| 167 | + "types": [ |
| 168 | + "@types/jasmine", |
| 169 | + "expect-webdriverio/jasmine" |
| 170 | + ] |
| 171 | + } |
| 172 | +} |
| 173 | +``` |
| 174 | + |
| 175 | +#### Global `expectAsync` force as `expect` |
| 176 | +When the global ambiant is the `expect` of wdio but forced to be `expectAsync` under the hood, like when using `@wdio/jasmine-framework`, then even the basic matchers need to be awaited |
| 177 | + |
| 178 | +```ts |
| 179 | +describe('My tests', async () => { |
| 180 | + it('should verify my browser to have the expected url', async () => { |
| 181 | + await expect(browser).toHaveUrl('https://example.com') |
| 182 | + |
| 183 | + // Even basic matchers requires expect since they are promises underneath |
| 184 | + await expect(true).toBe(true) |
| 185 | + }) |
| 186 | +}) |
| 187 | +``` |
| 188 | + |
| 189 | +Expected in `tsconfig.json`: |
| 190 | +```json |
| 191 | +{ |
| 192 | + "compilerOptions": { |
| 193 | + "types": [ |
| 194 | + "@wdio/globals/types", |
| 195 | + "@wdio/jasmine-framework", |
| 196 | + "@types/jasmine", |
| 197 | + "expect-webdriverio/jasmine-wdio-expect-async", // Force expect to return Promises |
| 198 | + ] |
| 199 | + } |
| 200 | +} |
| 201 | +``` |
| 202 | + |
| 203 | +#### `expect` of `expect-webdriverio` |
| 204 | +It is preferable to use the `expect` from `expect-webdriverio` to guarantee future compatibility. |
| 205 | + |
| 206 | +```ts |
| 207 | +// Required if we do not force the 'expect-webdriverio' expect globally with `"expect-webdriverio/expect-global"` |
| 208 | +import { expect as wdioExpect } from 'expect-webdriverio' |
| 209 | + |
| 210 | +describe('My tests', async () => { |
| 211 | + it('should verify my browser to have the expected url', async () => { |
| 212 | + await wdioExpect(browser).toHaveUrl('https://example.com') |
| 213 | + |
| 214 | + // No required await |
| 215 | + wdioExpect(true).toBe(true) |
| 216 | + }) |
| 217 | +}) |
| 218 | + |
| 219 | + |
| 220 | +Expected in `tsconfig.json`: |
| 221 | +```json |
| 222 | +{ |
| 223 | + "compilerOptions": { |
| 224 | + "types": [ |
| 225 | + "@types/jasmine", |
| 226 | + // "expect-webdriverio/expect-global", // Optional to have the global ambient expect the one of wdio |
| 227 | + ] |
| 228 | + } |
| 229 | +} |
| 230 | +``` |
| 231 | + |
| 232 | + |
| 233 | +#### Asymmetric matchers |
| 234 | +Asymmetric matchers have limited support. Even though `jasmine.stringContaining` does not produce a typing error, it may not work even with `@wdio/jasmine-framework`. However, the example below should work: |
| 235 | + |
| 236 | +```ts |
| 237 | +describe('My tests', async () => { |
| 238 | + it('should verify my browser to have the expected url', async () => { |
| 239 | + await expectAsync(browser).toHaveUrl(wdioExpect.stringContaining('WebdriverIO')) |
| 240 | + }) |
| 241 | +}) |
| 242 | +``` |
| 243 | + |
| 244 | + |
| 245 | +### Jest & Jasmine Augmentation Notes |
| 246 | + |
| 247 | +If you are already using Jest or Jasmine globally, using `import { expect } from 'expect-webdriverio'` is the most compatible approach, even though augmentation exists. |
| 248 | +It is recommended to build your project using this approach instead of relying on augmentation, to ensure future compatibility and avoid augmentation limitations. See [this issue](https://github.com/webdriverio/expect-webdriverio/issues/1893) for more information. |
| 249 | + |
| 250 | +### Cucumber |
| 251 | + |
| 252 | +More details to come. In short, when paired with `@wdio/cucumber-framework`, you can use WDIO's expect with Cucumber and even [Gherkin](https://www.npmjs.com/package/@cucumber/gherkin). |
0 commit comments