docs: document expect's asymmetric matchers (#24498)

References #24460, #24417.
This commit is contained in:
Dmitry Gozman 2023-07-28 14:04:01 -07:00 committed by GitHub
parent c7beebb40f
commit a74101d98f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 418 additions and 3 deletions

View File

@ -341,6 +341,36 @@ const value = { prop: 1 };
expect(value).toEqual({ prop: 1 });
```
**Non-strict equality**
[`method: GenericAssertions.toEqual`] performs deep equality check that compares contents of the received and expected values. To ensure two objects reference the same instance, use [`method: GenericAssertions.toBe`] instead.
[`method: GenericAssertions.toEqual`] ignores `undefined` properties and array items, and does not insist on object types being equal. For stricter matching, use [`method: GenericAssertions.toStrictEqual`].
**Pattern matching**
[`method: GenericAssertions.toEqual`] can be also used to perform pattern matching on objects, arrays and primitive types, with the help of the following matchers:
* [`method: GenericAssertions.any`]
* [`method: GenericAssertions.anything`]
* [`method: GenericAssertions.arrayContaining`]
* [`method: GenericAssertions.closeTo`]
* [`method: GenericAssertions.objectContaining`]
* [`method: GenericAssertions.stringContaining`]
* [`method: GenericAssertions.stringMatching`]
Here is an example that asserts some of the values inside a complex object:
```js
expect({
list: [1, 2, 3],
obj: { prop: 'Hello world!', another: 'some other value' },
extra: 'extra',
}).toEqual(expect.objectContaining({
list: expect.arrayContaining([2, 3]),
obj: expect.objectContaining({ prop: expect.stringContaining('Hello') }),
}));
```
### param: GenericAssertions.toEqual.expected
* since: v1.9
- `expected` <[any]>
@ -532,3 +562,169 @@ expect(() => {
- `expected` ?<[any]>
Expected error message or error object.
## method: GenericAssertions.any
* since: v1.9
`expect.any()` matches any object instance created from the [`param: constructor`] or a corresponding primitive type. Use it inside [`method: GenericAssertions.toEqual`] to perform pattern matching.
**Usage**
```js
// Match instance of a class.
class Example {}
expect(new Example()).toEqual(expect.any(Example));
// Match any number.
expect({ prop: 1 }).toEqual({ prop: expect.any(Number) });
// Match any string.
expect('abc').toEqual(expect.any(String));
```
### param: GenericAssertions.any.constructor
* since: v1.9
- `constructor` <[Function]>
Constructor of the expected object like `ExampleClass`, or a primitive boxed type like `Number`.
## method: GenericAssertions.anything
* since: v1.9
`expect.anything()` matches everything except `null` and `undefined`. Use it inside [`method: GenericAssertions.toEqual`] to perform pattern matching.
**Usage**
```js
const value = { prop: 1 };
expect(value).toEqual({ prop: expect.anything() });
expect(value).not.toEqual({ otherProp: expect.anything() });
```
## method: GenericAssertions.arrayContaining
* since: v1.9
`expect.arrayContaining()` matches an array that contains all of the elements in the expected array, in any order. Note that received array may be a superset of the expected array and contain some extra elements.
Use this method inside [`method: GenericAssertions.toEqual`] to perform pattern matching.
**Usage**
```js
expect([1, 2, 3]).toEqual(expect.arrayContaining([3, 1]));
expect([1, 2, 3]).not.toEqual(expect.arrayContaining([1, 4]));
```
### param: GenericAssertions.arrayContaining.expected
* since: v1.9
- `expected` <[Array]<[any]>>
Expected array that is a subset of the received value.
## method: GenericAssertions.closeTo
* since: v1.9
Compares floating point numbers for approximate equality. Use this method inside [`method: GenericAssertions.toEqual`] to perform pattern matching. When just comparing two numbers, prefer [`method: GenericAssertions.toBeCloseTo`].
**Usage**
```js
expect({ prop: 0.1 + 0.2 }).not.toEqual({ prop: 0.3 });
expect({ prop: 0.1 + 0.2 }).toEqual({ prop: expect.closeTo(0.3, 5) });
```
### param: GenericAssertions.closeTo.expected
* since: v1.9
- `expected` <[float]>
Expected value.
### param: GenericAssertions.closeTo.numDigits
* since: v1.9
- `numDigits` ?<[int]>
The number of decimal digits after the decimal point that must be equal.
## method: GenericAssertions.objectContaining
* since: v1.9
`expect.objectContaining()` matches an object that contains and matches all of the properties in the expected object. Note that received object may be a superset of the expected object and contain some extra properties.
Use this method inside [`method: GenericAssertions.toEqual`] to perform pattern matching. Object properties can be matchers to further relax the expectation. See examples.
**Usage**
```js
// Assert some of the properties.
expect({ foo: 1, bar: 2 }).toEqual(expect.objectContaining({ foo: 1 }));
// Matchers can be used on the properties as well.
expect({ foo: 1, bar: 2 }).toEqual(expect.objectContaining({ bar: expect.any(Number) }));
// Complex matching of sub-properties.
expect({
list: [1, 2, 3],
obj: { prop: 'Hello world!', another: 'some other value' },
extra: 'extra',
}).toEqual(expect.objectContaining({
list: expect.arrayContaining([2, 3]),
obj: expect.objectContaining({ prop: expect.stringContaining('Hello') }),
}));
```
### param: GenericAssertions.objectContaining.expected
* since: v1.9
- `expected` <[Object]>
Expected object pattern that contains a subset of the properties.
## method: GenericAssertions.stringContaining
* since: v1.9
`expect.stringContaining()` matches a string that contains the expected substring. Use this method inside [`method: GenericAssertions.toEqual`] to perform pattern matching.
**Usage**
```js
expect('Hello world!').toEqual(expect.stringContaining('Hello'));
```
### param: GenericAssertions.stringContaining.expected
* since: v1.9
- `expected` <[string]>
Expected substring.
## method: GenericAssertions.stringMatching
* since: v1.9
`expect.stringMatching()` matches a received string that in turn matches the expected pattern. Use this method inside [`method: GenericAssertions.toEqual`] to perform pattern matching.
**Usage**
```js
expect('123ms').toEqual(expect.stringMatching(/\d+m?s/));
// Inside another matcher.
expect({
status: 'passed',
time: '123ms',
}).toEqual({
status: expect.stringMatching(/passed|failed/),
time: expect.stringMatching(/\d+m?s/),
});
```
### param: GenericAssertions.stringMatching.expected
* since: v1.9
- `expected` <[string]|[RegExp]>
Pattern that expected string should match.

View File

@ -20,7 +20,7 @@ Playwright will be re-testing the element with the test id of `status` until the
By default, the timeout for assertions is set to 5 seconds. Learn more about [various timeouts](./test-timeouts.md).
## List of assertions
## Popular assertions
| Assertion | Description |
| :- | :- |
@ -50,6 +50,41 @@ By default, the timeout for assertions is set to 5 seconds. Learn more about [va
| [`method: PageAssertions.toHaveURL`] | Page has a URL |
| [`method: APIResponseAssertions.toBeOK`] | Response has an OK status |
## Generic assertions
| Assertion | Description |
| :- | :- |
| [`method: GenericAssertions.toBe`] | Value is the same |
| [`method: GenericAssertions.toBeCloseTo`] | Number is approximately equal |
| [`method: GenericAssertions.toBeDefined`] | Value is not `undefined` |
| [`method: GenericAssertions.toBeFalsy`] | Value is falsy, e.g. `false`, `0`, `null`, etc. |
| [`method: GenericAssertions.toBeGreaterThan`] | Number is more than |
| [`method: GenericAssertions.toBeGreaterThanOrEqual`] | Number is more than or equal |
| [`method: GenericAssertions.toBeInstanceOf`] | Object is an instance of a class |
| [`method: GenericAssertions.toBeLessThan`] | Number is less than |
| [`method: GenericAssertions.toBeLessThanOrEqual`] | Number is less than or equal |
| [`method: GenericAssertions.toBeNaN`] | Value is `NaN` |
| [`method: GenericAssertions.toBeNull`] | Value is `null` |
| [`method: GenericAssertions.toBeTruthy`] | Value is truthy, i.e. not `false`, `0`, `null`, etc. |
| [`method: GenericAssertions.toBeUndefined`] | Value is `undefined` |
| [`method: GenericAssertions.toContain#1`] | String contains a substring |
| [`method: GenericAssertions.toContain#2`] | Array or set contains an element |
| [`method: GenericAssertions.toContainEqual`] | Array or set contains a similar element |
| [`method: GenericAssertions.toEqual`] | Value is similar - deep equality and pattern matching |
| [`method: GenericAssertions.toHaveLength`] | Array or string has length |
| [`method: GenericAssertions.toHaveProperty`] | Object has a property |
| [`method: GenericAssertions.toMatch`] | String matches a regular expression |
| [`method: GenericAssertions.toMatchObject`] | Object contains specified properties |
| [`method: GenericAssertions.toStrictEqual`] | Value is similar, including property types |
| [`method: GenericAssertions.toThrow`] | Function throws an error |
| [`method: GenericAssertions.any`] | Matches any instance of a class/primitive |
| [`method: GenericAssertions.anything`] | Matches antyhing |
| [`method: GenericAssertions.arrayContaining`] | Array contains specific elements |
| [`method: GenericAssertions.closeTo`] | Number is approximately equal |
| [`method: GenericAssertions.objectContaining`] | Object contains specific properties |
| [`method: GenericAssertions.stringContaining`] | String contains a substring |
| [`method: GenericAssertions.stringMatching`] | String matches a regular expression |
## Negating Matchers
In general, we can expect the opposite to be true by adding a `.not` to the front

View File

@ -4334,13 +4334,148 @@ export type PlaywrightTestConfig<TestArgs = {}, WorkerArgs = {}> = Config<Playwr
type AsymmetricMatcher = Record<string, any>;
type AsymmetricMatchers = {
interface AsymmetricMatchers {
/**
* `expect.any()` matches any object instance created from the `constructor` or a corresponding primitive type. Use it
* inside
* [genericAssertions.toEqual(expected)](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-to-equal)
* to perform pattern matching.
*
* **Usage**
*
* ```js
* // Match instance of a class.
* class Example {}
* expect(new Example()).toEqual(expect.any(Example));
*
* // Match any number.
* expect({ prop: 1 }).toEqual({ prop: expect.any(Number) });
*
* // Match any string.
* expect('abc').toEqual(expect.any(String));
* ```
*
* @param constructor Constructor of the expected object like `ExampleClass`, or a primitive boxed type like `Number`.
*/
any(sample: unknown): AsymmetricMatcher;
/**
* `expect.anything()` matches everything except `null` and `undefined`. Use it inside
* [genericAssertions.toEqual(expected)](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-to-equal)
* to perform pattern matching.
*
* **Usage**
*
* ```js
* const value = { prop: 1 };
* expect(value).toEqual({ prop: expect.anything() });
* expect(value).not.toEqual({ otherProp: expect.anything() });
* ```
*
*/
anything(): AsymmetricMatcher;
/**
* `expect.arrayContaining()` matches an array that contains all of the elements in the expected array, in any order.
* Note that received array may be a superset of the expected array and contain some extra elements.
*
* Use this method inside
* [genericAssertions.toEqual(expected)](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-to-equal)
* to perform pattern matching.
*
* **Usage**
*
* ```js
* expect([1, 2, 3]).toEqual(expect.arrayContaining([3, 1]));
* expect([1, 2, 3]).not.toEqual(expect.arrayContaining([1, 4]));
* ```
*
* @param expected Expected array that is a subset of the received value.
*/
arrayContaining(sample: Array<unknown>): AsymmetricMatcher;
/**
* Compares floating point numbers for approximate equality. Use this method inside
* [genericAssertions.toEqual(expected)](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-to-equal)
* to perform pattern matching. When just comparing two numbers, prefer
* [genericAssertions.toBeCloseTo(expected[, numDigits])](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-to-be-close-to).
*
* **Usage**
*
* ```js
* expect({ prop: 0.1 + 0.2 }).not.toEqual({ prop: 0.3 });
* expect({ prop: 0.1 + 0.2 }).toEqual({ prop: expect.closeTo(0.3, 5) });
* ```
*
* @param expected Expected value.
* @param numDigits The number of decimal digits after the decimal point that must be equal.
*/
closeTo(sample: number, precision?: number): AsymmetricMatcher;
/**
* `expect.objectContaining()` matches an object that contains and matches all of the properties in the expected
* object. Note that received object may be a superset of the expected object and contain some extra properties.
*
* Use this method inside
* [genericAssertions.toEqual(expected)](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-to-equal)
* to perform pattern matching. Object properties can be matchers to further relax the expectation. See examples.
*
* **Usage**
*
* ```js
* // Assert some of the properties.
* expect({ foo: 1, bar: 2 }).toEqual(expect.objectContaining({ foo: 1 }));
*
* // Matchers can be used on the properties as well.
* expect({ foo: 1, bar: 2 }).toEqual(expect.objectContaining({ bar: expect.any(Number) }));
*
* // Complex matching of sub-properties.
* expect({
* list: [1, 2, 3],
* obj: { prop: 'Hello world!', another: 'some other value' },
* extra: 'extra',
* }).toEqual(expect.objectContaining({
* list: expect.arrayContaining([2, 3]),
* obj: expect.objectContaining({ prop: expect.stringContaining('Hello') }),
* }));
* ```
*
* @param expected Expected object pattern that contains a subset of the properties.
*/
objectContaining(sample: Record<string, unknown>): AsymmetricMatcher;
/**
* `expect.stringContaining()` matches a string that contains the expected substring. Use this method inside
* [genericAssertions.toEqual(expected)](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-to-equal)
* to perform pattern matching.
*
* **Usage**
*
* ```js
* expect('Hello world!').toEqual(expect.stringContaining('Hello'));
* ```
*
* @param expected Expected substring.
*/
stringContaining(sample: string): AsymmetricMatcher;
/**
* `expect.stringMatching()` matches a received string that in turn matches the expected pattern. Use this method
* inside
* [genericAssertions.toEqual(expected)](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-to-equal)
* to perform pattern matching.
*
* **Usage**
*
* ```js
* expect('123ms').toEqual(expect.stringMatching(/\d+m?s/));
*
* // Inside another matcher.
* expect({
* status: 'passed',
* time: '123ms',
* }).toEqual({
* status: expect.stringMatching(/passed|failed/),
* time: expect.stringMatching(/\d+m?s/),
* });
* ```
*
* @param expected Pattern that expected string should match.
*/
stringMatching(sample: string | RegExp): AsymmetricMatcher;
}
@ -4616,6 +4751,45 @@ interface GenericAssertions<R> {
* expect(value).toEqual({ prop: 1 });
* ```
*
* **Non-strict equality**
*
* [genericAssertions.toEqual(expected)](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-to-equal)
* performs deep equality check that compares contents of the received and expected values. To ensure two objects
* reference the same instance, use
* [genericAssertions.toBe(expected)](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-to-be)
* instead.
*
* [genericAssertions.toEqual(expected)](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-to-equal)
* ignores `undefined` properties and array items, and does not insist on object types being equal. For stricter
* matching, use
* [genericAssertions.toStrictEqual(expected)](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-to-strict-equal).
*
* **Pattern matching**
*
* [genericAssertions.toEqual(expected)](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-to-equal)
* can be also used to perform pattern matching on objects, arrays and primitive types, with the help of the following
* matchers:
* - [genericAssertions.any(constructor)](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-any)
* - [genericAssertions.anything()](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-anything)
* - [genericAssertions.arrayContaining(expected)](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-array-containing)
* - [genericAssertions.closeTo(expected[, numDigits])](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-close-to)
* - [genericAssertions.objectContaining(expected)](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-object-containing)
* - [genericAssertions.stringContaining(expected)](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-string-containing)
* - [genericAssertions.stringMatching(expected)](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-string-matching)
*
* Here is an example that asserts some of the values inside a complex object:
*
* ```js
* expect({
* list: [1, 2, 3],
* obj: { prop: 'Hello world!', another: 'some other value' },
* extra: 'extra',
* }).toEqual(expect.objectContaining({
* list: expect.arrayContaining([2, 3]),
* obj: expect.objectContaining({ prop: expect.stringContaining('Hello') }),
* }));
* ```
*
* @param expected Expected value.
*/
toEqual(expected: unknown): R;

View File

@ -95,6 +95,8 @@ class TypesGenerator {
const handledClasses = new Set();
let overrides = await parseOverrides(overridesFile, className => {
if (className === 'AsymmetricMatchers')
return '';
const docClass = this.docClassForName(className);
if (!docClass)
return '';
@ -569,6 +571,13 @@ class TypesGenerator {
'TestOptions',
'TestConfig.use',
'TestProject.use',
'GenericAssertions.any',
'GenericAssertions.anything',
'GenericAssertions.arrayContaining',
'GenericAssertions.closeTo',
'GenericAssertions.objectContaining',
'GenericAssertions.stringContaining',
'GenericAssertions.stringMatching',
]),
overridesToDocsClassMapping: new Map([
['TestType', 'Test'],
@ -580,6 +589,7 @@ class TypesGenerator {
['PlaywrightTestOptions', 'TestOptions'],
['PlaywrightWorkerArgs', 'Fixtures'],
['PlaywrightTestArgs', 'Fixtures'],
['AsymmetricMatchers', 'GenericAssertions'],
]),
ignoreMissing: new Set([
'FullConfig.configFile',

View File

@ -280,7 +280,7 @@ export type PlaywrightTestConfig<TestArgs = {}, WorkerArgs = {}> = Config<Playwr
type AsymmetricMatcher = Record<string, any>;
type AsymmetricMatchers = {
interface AsymmetricMatchers {
any(sample: unknown): AsymmetricMatcher;
anything(): AsymmetricMatcher;
arrayContaining(sample: Array<unknown>): AsymmetricMatcher;