Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
15e64ed
Fix global jasmine + wdio matchers once for all
dprevost-LMI Jan 31, 2026
fcbbefb
Fixing jasmine prob, once for all
dprevost-LMI Feb 1, 2026
2186f84
Review jasmine asymmetric compatibility
dprevost-LMI Feb 2, 2026
5556b51
Enable test with jasmine.stringContaining + fix typing tests
dprevost-LMI Feb 2, 2026
047cdb4
Fix basi matcher test & doc
dprevost-LMI Feb 2, 2026
e3848b9
Review typing, name matchers var more precisely
dprevost-LMI Feb 2, 2026
7cc965e
Ensure support of network matchers
dprevost-LMI Feb 2, 2026
ccccd63
Real good testing of Jasmine augmentation typing
dprevost-LMI Feb 3, 2026
f5fbf3e
Good test typing for Mocha
dprevost-LMI Feb 3, 2026
bde9f53
Fix jest global + review customs matchers
dprevost-LMI Feb 3, 2026
e676aad
More more good typing jasmine test
dprevost-LMI Feb 3, 2026
692d153
Finalize jest typet est using `expectTypeOf`
dprevost-LMI Feb 3, 2026
4e2deea
working case of Jest augmentation with wdio matchers
dprevost-LMI Feb 4, 2026
0ab9888
Fix sync matcher that need to be async
dprevost-LMI Feb 4, 2026
a8a7059
Code review
dprevost-LMI Feb 4, 2026
8d77907
Use latest jest
dprevost-LMI Feb 4, 2026
3a4fe74
Code review
dprevost-LMI Feb 4, 2026
f0832b5
Code review
dprevost-LMI Feb 4, 2026
082e460
Fix command
dprevost-LMI Feb 4, 2026
1d28a9a
Fix rebase
dprevost-LMI Mar 8, 2026
389abfe
Add readme justifying why existence of playgrounds
dprevost-LMI Mar 8, 2026
79c9b29
Typo
dprevost-LMI Mar 8, 2026
5bb0b0c
Use latest expect-wdio in playgrounds
dprevost-LMI Mar 14, 2026
2ae36ea
Ensure more asymmetric matchers testing with basic-matchers
dprevost-LMI Mar 14, 2026
6cb02db
Ensure type of wdioCustomMatchers is transparent when used with extend
dprevost-LMI Mar 14, 2026
85e968f
Remove unneeded `eslint-disable @typescript-eslint/no-unused-vars`
dprevost-LMI Mar 14, 2026
73b30ea
Update playgrounds to latest versions
dprevost-LMI Mar 14, 2026
c106258
Revert "Update playgrounds to latest versions"
dprevost-LMI Mar 14, 2026
83063df
Update playground and dev deps, but not wdio ones
dprevost-LMI Mar 14, 2026
0a4d991
update all dev & wdio dependencies to non minor latest
dprevost-LMI Mar 14, 2026
1f53589
Add expectation that this type does not belong in expec-wdio
dprevost-LMI Mar 14, 2026
43d6ad1
Fix doc deprecanties
dprevost-LMI Mar 14, 2026
c9ed51e
Better test title
dprevost-LMI Mar 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 53 additions & 63 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -907,60 +907,56 @@ await expect(elem).toHaveElementClass(/Container/i)
In addition to the WebdriverIO matchers, `expect-webdriverio` also provides basic matchers from Jest's [expect](https://jestjs.io/docs/expect) library.

```ts
describe('Expect matchers', () => {
test('Basic matchers', async () => {
// 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');
});
});
// 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');
```

### Jasmine
Expand Down Expand Up @@ -1010,18 +1006,12 @@ await expect(browser).toHaveTitle(expect.not.stringContaining('some title'))

### Jasmine

Even under `@wdio/jasmine-framework`, Jasmine asymmetric matchers do not work with WebdriverIO matchers.
Under `@wdio/jasmine-framework`, some Jasmine asymmetric matchers now work with WebdriverIO matchers and the global import.

```ts
// DOES NOT work
// Jasmine's stringContaining works just like the one from expect
await expect(browser).toHaveTitle(jasmine.stringContaining('some title'))

// Use expect
await expect(browser).toHaveTitle(expect.stringContaining('some title'))
```

However, when using Jasmine original matchers, both works.
```ts
await expect(url).toEqual(jasmine.stringMatching(/^https:\/\//))
await expect(url).toEqual(expect.stringMatching(/^https:\/\//))
```
**Note:** Known limitations still exist with `jasmine.arrayContaining` and `jasmine.objectContaining` with basic matchers.
12 changes: 11 additions & 1 deletion docs/CustomMatchers.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
# Custom Matchers

`expect-webdriverio` registers WebdriverIO custom matchers out of the box for a seamless experience.

To use WebdriverIO custom matchers (except asymmetric matchers) directly in:
- **Jest**: Register matchers manually with `expect.extend`.
- **Jasmine**: Using `@wdio/jasmine-framework` provides an out-of-the-box experience.
- Else, register matchers manually with `jasmine.addAsyncMatchers`, then they will be available on `expectAsync`.
- **Types**: Type augmentation for custom matchers is provided. See [Types.md](Types.md) for details.

## Adding your own matchers

Similar to how `expect-webdriverio` extends Jasmine/Jest matchers it's possible to add custom matchers.
Similar to how `expect-webdriverio` provide custom matchers it's possible to add your own custom matchers.

- [Jasmine](https://jasmine.github.io/) see [custom matchers](https://jasmine.github.io/tutorials/custom_matchers) doc
- Everyone else see [Jest's expect.extend](https://jestjs.io/docs/expect#expectextendmatchers)
Expand Down
4 changes: 2 additions & 2 deletions docs/Framework.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ Option 2: Reconfigure Jest's expect with the custom matchers and the soft assert
```ts
// Configure the custom matchers:
import { expect } from "@jest/globals";
import { matchers } from "expect-webdriverio";
import { wdioCustomMatchers } from "expect-webdriverio";

beforeAll(async () => {
expect.extend(matchers as Record<string, any>);
expect.extend(wdioCustomMatchers);
});
```

Expand Down
71 changes: 50 additions & 21 deletions jasmine-wdio-expect-async.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
/// <reference types="./types/expect-webdriverio.d.ts"/>

/**
* Augment the Jasmine namespace to match the behavior of `@wdio/jasmine-framework`.
* Only custom WDIO matchers are available under `expectAsync`, as well as Jasmine's built-in matchers.
* `expectAsync` is forced into the `expect` global ambient, making all Jasmine sync-matchers asynchronous.
*
* When using `@wdio/jasmine-framework`, specify `expect-webdriverio/jasmine-wdio-expect-async` in the tsconfig.json's types.
* TODO move into `@wdio/jasmine-framework` and deprecated it from `expect-webdriverio`.
*/

declare namespace jasmine {

/**
Expand All @@ -10,28 +19,36 @@ declare namespace jasmine {
*
* We force Matchers to return a `Promise<void>` since Jasmine's `expectAsync` expects a promise in all cases (different from Jest)
*/

// eslint-disable-next-line @typescript-eslint/no-unused-vars -- U is required to properly override Jasmine's AsyncMatchers
interface AsyncMatchers<T, U> extends ExpectWebdriverIO.Matchers<Promise<void>, T> {}

// Needed to reference it below for the withContext method
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface AsyncMatchers<T, U> extends Omit<ExpectWebdriverIO.Matchers<Promise<void>, T>, 'toMatchSnapshot' | 'toMatchInlineSnapshot'> {
/**
* snapshot matcher
* @param label optional snapshot label
*/
toMatchSnapshot(label?: string): Promise<void>;
/**
* inline snapshot matcher
* @param snapshot snapshot string (autogenerated if not specified)
* @param label optional snapshot label
*/
toMatchInlineSnapshot(snapshot?: string, label?: string): Promise<void>;
}
interface Matchers<T> {}
}

/**
* Overrides the default WDIO expect specifically for Jasmine, since `expectAsync` is forced into `expect`, making all matchers fully asynchronous. This is not the case under Jest or Mocha.
* Using `jasmine.AsyncMatchers` pull on WdioMatchers above but also allow to using Jasmine's built-in matchers and also `withContext` matcher.
*/
declare namespace ExpectWebdriverIO {
interface Expect {

// Should be the same as https://github.com/webdriverio/webdriverio/blob/ea0e3e00288abced4c739ff9e46c46977b7cdbd2/packages/wdio-jasmine-framework/src/index.ts#L21-L29
interface JasmineAsymmetricMatchers extends Pick<ExpectWebdriverIO.AsymmetricMatchers, 'any' | 'anything' | 'arrayContaining' | 'objectContaining' | 'stringContaining' | 'stringMatching'> {}

// Hack to convert all sync matchers to return Promise<void> since `wdio/jasmine-framework` forces `expect` to be async
type JasmineSyncMatchers<T> = {
[K in keyof jasmine.Matchers<T>]: K extends 'not'
? JasmineSyncMatchers<T>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
: jasmine.Matchers<T>[K] extends (...args: any) => any
// eslint-disable-next-line @typescript-eslint/no-explicit-any
? (...args: any[]) => Promise<void>
: jasmine.Matchers<T>[K]
}

/**
* Overrides the default WDIO expect specifically for Jasmine, since `expectAsync` is forced into `expect`, making all matchers fully asynchronous. This is not the case under Jest or Mocha.
* Using `jasmine.AsyncMatchers` includes the WdioMatchers from above, but also allows using Jasmine's built-in matchers and the `withContext` matcher.
*/
interface JasmineExpect extends ExpectWebdriverIO.JasmineAsymmetricMatchers, ExpectLibInverse<ExpectWebdriverIO.JasmineAsymmetricMatchers> {
/**
* The `expect` function is used every time you want to test a value.
* You will rarely call `expect` by itself.
Expand All @@ -40,10 +57,22 @@ declare namespace ExpectWebdriverIO {
* - T: the type of the actual value, e.g. any type, not just WebdriverIO.Browser or WebdriverIO.Element
* - R: the type of the return value, e.g. Promise<void> or void
*
* Note: The function must stay here in the namespace to overwrite correctly the expect function from the expect library.
*
* @param actual The value to apply matchers against.
*/
<T = unknown>(actual: T): jasmine.AsyncMatchers<T, void>
<T = unknown>(actual: T): {
withContext(message: string): jasmine.AsyncMatchers<T, Promise<void>> & JasmineSyncMatchers<T>;
} & jasmine.AsyncMatchers<T, Promise<void>> & JasmineSyncMatchers<T>
}
}

/**
* Under `@wdio/jasmine-framework`, the global `expect` is overridden to use Jasmine's `expectAsync`.
* It contains custom WebdriverIO matchers as well as Jasmine's built-in async & sync matchers but not the basic Jest's expect library matchers.
*/
// @ts-expect-error: IDE might flag this, but ignore it. This way the `tsc:root-types` can pass!
declare const expect: ExpectWebdriverIO.JasmineExpect
declare namespace NodeJS {
interface Global {
expect: ExpectWebdriverIO.JasmineExpect
}
}
25 changes: 9 additions & 16 deletions jasmine.d.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,20 @@
/// <reference types="./types/expect-webdriverio.d.ts"/>

/**
* Augment the Jasmine namespace to include the WDIO custom async matchers only.
* When using the vanilla Jasmine Library, use `jasmine.addAsyncMatchers(wdioCustomMatchers)` and specify `expect-webdriverio/jasmine` in the tsconfig.json's types.
*/

declare namespace jasmine {

/**
* Async matchers for Jasmine to allow the typing of `expectAsync` with WebDriverIO matchers.
* Async matchers for Jasmine to allow the typing of `expectAsync` with WebDriverIO custom matchers.
* T is the type of the actual value
* U is the type of the expected value
* Both T,U must stay named as they are to override the default `AsyncMatchers` type from Jasmine.
*
* We force Matchers to return a `Promise<void>` since Jasmine's `expectAsync` expects a promise in all cases (different from Jest)
* We force Matchers to return a `Promise<void>` since under Jasmine's `expectAsync` everything is a promise.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface AsyncMatchers<T, U> extends Omit<ExpectWebdriverIO.Matchers<Promise<void>, T>, 'toMatchSnapshot' | 'toMatchInlineSnapshot'> {
/**
* snapshot matcher
* @param label optional snapshot label
*/
toMatchSnapshot(label?: string): Promise<void>;
/**
* inline snapshot matcher
* @param snapshot snapshot string (autogenerated if not specified)
* @param label optional snapshot label
*/
toMatchInlineSnapshot(snapshot?: string, label?: string): Promise<void>;
}
}
interface AsyncMatchers<T, U> extends ExpectWebdriverIO.Matchers<Promise<void>, T> {}
}
12 changes: 6 additions & 6 deletions jest.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/// <reference types="./types/expect-webdriverio.d.ts"/>

/**
* Augment the Jest namespace to include the matchers from expect-webdriverio.
* When Jest Library is used, it specifies `expect-webdriverio/jest` for this file in the tsconfig.json's types.
* Augment the Jest namespace to include the WebDriverIO custom matchers only.
* When Jest Library is used, specify `expect-webdriverio/jest` for this file in the tsconfig.json's types.
*/

declare namespace jest {
Expand All @@ -13,25 +13,25 @@ declare namespace jest {
* Below are overloaded Jest's matchers not part of `expect` but of `jest-snapshot`.
* @see https://github.com/jestjs/jest/blob/73dbef5d2d3195a1e55fb254c54cce70d3036252/packages/jest-snapshot/src/types.ts#L37
*
* Note: We need to define them below so that they are correctly typed overloaded.
* Note: We need to define them below so that they are correctly overloaded.
* Else even when extending `WdioJestOverloadedMatchers` we have typing errors.
*/

/**
* snapshot matcher
* @param label optional snapshot label
*/
toMatchSnapshot(label?: string): T extends WdioPromiseLike ? Promise<void> : void;
toMatchSnapshot(label?: string): T extends WdioElementOrPromiseLike ? Promise<void> : R;

/**
* inline snapshot matcher
* @param snapshot snapshot string (autogenerated if not specified)
* @param label optional snapshot label
*/
toMatchInlineSnapshot(snapshot?: string, label?: string): T extends WdioPromiseLike ? Promise<void> : void;
toMatchInlineSnapshot(snapshot?: string, label?: string): T extends WdioElementOrPromiseLike ? Promise<void> : R;
}

interface Expect extends ExpectWebdriverIO.Expect {}

interface InverseAsymmetricMatchers extends ExpectWebdriverIO.AsymmetricMatchers {}
}
}
Loading