Skip to content

Latest commit

 

History

History
257 lines (203 loc) · 10.6 KB

File metadata and controls

257 lines (203 loc) · 10.6 KB

Expect-WebDriverIO Framework

Expect-WebDriverIO is inspired by 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.

Compatibility

We can pair expect-webdriverio with Jest, Mocha, and Jasmine and even Cucumber

It is highly recommended to use it with a WDIO Testrunner which provides additional auto-configuration for a plug-and-play experience.

When used outside of WDIO Testrunner, types need to be added to your tsconfig.json, and some additional configuration for WDIO matchers, soft assertions, and snapshot service is required.

Jest

We can use expect-webdriverio with Jest using @jest/globals alone (preferred) and optionally @types/jest (which has global ambient support).

  • Note: Jest maintainers do not support @types/jest. If this library gets out of date or has problems, support might be dropped.
  • Note: With Jest, the matchers toMatchSnapshot and toMatchInlineSnapshot are overloaded. To resolve the types correctly, expect-webdriverio/jest must be last.

With @jest/globals

When paired only with @jest/globals, we should import the expect function from expect-webdriverio.

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')
    })
})        

No types are expected in tsconfig.json. Optionally, to avoid needing import { expect } from 'expect-webdriverio', you can use the following:

{
  "compilerOptions": {
    "types": ["expect-webdriverio/expect-global"]
  }
}
Augmenting @jest/globals JestMatchers

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.

This Jest issue seems to target this problem, but it is still in progress.

With @types/jest

When also paired with @types/jest, no imports are required. Global ambient types are already defined correctly and you can simply use Jest's expect directly.

If you are NOT using WDIO Testrunner, some prerequisite configuration is required.

Option 1: Replace the expect globally with the expect-webdriverio one:

import { expect } from "expect-webdriverio";
(globalThis as any).expect = expect;

Option 2: Reconfigure Jest's expect with the custom matchers and the soft assertion:

// Configure the custom matchers:
import { expect } from "@jest/globals";
import { wdioCustomMatchers } from "expect-webdriverio";

beforeAll(async () => { 
    expect.extend(wdioCustomMatchers);
});

[Optional] For the soft assertion, the createSoftExpect is currently not correctly exposed but the below works:

// @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` 
      ]
  }
}

Mocha

When paired with Mocha, it can be used without (standalone) or with chai (or any other assertion library).

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')
    })
})     

Expected in tsconfig.json:

{
  "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.

Jasmine

When paired with Jasmine, @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.

Jasmine differs from other assertion libraries in two key ways:

  1. Jasmine performs soft assertions by default, collecting failures and only failing the test at the end. Because of this, the SoftAssertion service is not needed or supported.
  2. Forcing expectAsync as expect (by @wdio/jasmine-framework) makes even basic matchers asynchronous. However, since Jasmine handles all promises at the end of the test, assertions appear to work properly—unlike in other frameworks, where using await is mandatory for correct behavior.

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.

Jasmine expectAsync

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.

describe('My tests', async () => {
    it('should verify my browser to have the expected url', async () => {
        await expectAsync(browser).toHaveUrl('https://example.com')
        await expectAsync(true).toBe(true)
    })
})     

Expected in tsconfig.json:

{
  "compilerOptions": {
    "types": [
        "@types/jasmine",
        "expect-webdriverio/jasmine"
      ]
  }
}

Global expectAsync force as expect

When the global ambient expect is actually expectAsync under the hood (as with @wdio/jasmine-framework), it is recommended to await even basic matchers, even though Jasmine will handle any un-awaited assertions at the end of the test.

describe('My tests', async () => {
    it('should verify my browser to have the expected url', async () => {
        await expect(browser).toHaveUrl('https://example.com')

        // Even basic matchers should have `await` since they are promises underneath
        await expect(true).toBe(true)
    })
})     

Expected in tsconfig.json:

{
  "compilerOptions": {
    "types": [
      "@wdio/globals/types",
      "@wdio/jasmine-framework",
      "@types/jasmine",
      "expect-webdriverio/jasmine-wdio-expect-async", // Force expect to return Promises
    ]
  }
}

expect of expect-webdriverio

It is preferable to use the expect from expect-webdriverio to guarantee future compatibility.

// Required if we do not force the 'expect-webdriverio' expect globally with `"expect-webdriverio/expect-global"`
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')

        // No required await
        wdioExpect(true).toBe(true)        
    })
})     


Expected in `tsconfig.json`:
```json
{
  "compilerOptions": {
    "types": [
        "@types/jasmine",
        // "expect-webdriverio/expect-global", // Optional to have the global ambient expect the one of wdio
      ]
  }
}

Asymmetric matchers

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:

describe('My tests', async () => {
    it('should verify my browser to have the expected url', async () => {
        await expectAsync(browser).toHaveUrl(wdioExpect.stringContaining('WebdriverIO'))
    })
})     

Jest & Jasmine Augmentation Notes

If you are already using Jest or Jasmine globally, using import { expect } from 'expect-webdriverio' is the most compatible approach, even though augmentation exists. 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 for more information.

Cucumber

More details to come. In short, when paired with @wdio/cucumber-framework, you can use WDIO's expect with Cucumber and even Gherkin.