feat(expect): allow chaining expects (#27248)

This commit is contained in:
Pavel Feldman 2023-09-22 12:12:17 -07:00 коммит произвёл GitHub
Родитель 49fd9500fe
Коммит a6a0257c88
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
11 изменённых файлов: 295 добавлений и 79 удалений

Просмотреть файл

@ -48,7 +48,7 @@ import {
import { toMatchSnapshot, toHaveScreenshot, toHaveScreenshotStepTitle } from './toMatchSnapshot';
import type { Expect } from '../../types/test';
import { currentTestInfo, currentExpectTimeout, setCurrentExpectConfigureTimeout } from '../common/globals';
import { filteredStackTrace, serializeError, stringifyStackFrames, trimLongString } from '../util';
import { filteredStackTrace, stringifyStackFrames, trimLongString } from '../util';
import {
expect as expectLibrary,
INVERTED_COLOR,
@ -106,7 +106,7 @@ function createMatchers(actual: unknown, info: ExpectMetaInfo): any {
}
function createExpect(info: ExpectMetaInfo) {
const expectInstance: Expect = new Proxy(expectLibrary, {
const expectInstance: Expect<{}> = new Proxy(expectLibrary, {
apply: function(target: any, thisArg: any, argumentsList: [unknown, ExpectMessage?]) {
const [actual, messageOrOptions] = argumentsList;
const message = isString(messageOrOptions) ? messageOrOptions : messageOrOptions?.message || info.message;
@ -123,6 +123,13 @@ function createExpect(info: ExpectMetaInfo) {
if (property === 'configure')
return configure;
if (property === 'extend') {
return (matchers: any) => {
expectLibrary.extend(matchers);
return expectInstance;
};
}
if (property === 'soft') {
return (actual: unknown, messageOrOptions?: ExpectMessage) => {
return configure({ soft: true })(actual, messageOrOptions) as any;
@ -160,7 +167,7 @@ function createExpect(info: ExpectMetaInfo) {
return expectInstance;
}
export const expect: Expect = createExpect({});
export const expect: Expect<{}> = createExpect({});
expectLibrary.setState({ expand: false });
@ -269,9 +276,13 @@ class ExpectMetaInfoProxyHandler implements ProxyHandler<any> {
jestError.stack = jestError.name + ': ' + newMessage + '\n' + stringifyStackFrames(stackFrames).join('\n');
}
const serializedError = serializeError(jestError);
// Serialized error has filtered stack trace.
jestError.stack = serializedError.stack;
// Use the exact stack that we entered the matcher with.
jestError.stack = jestError.name + ': ' + jestError.message + '\n' + stringifyStackFrames(stackFrames).join('\n');
const serializedError = {
message: jestError.message,
stack: jestError.stack,
};
step?.complete({ error: serializedError });
if (this._info.isSoft)
testInfo._failWithError(serializedError, false /* isHardError */);

Просмотреть файл

@ -48,5 +48,5 @@ export async function toBeTruthy(
return matches ? `${header}Expected: not ${expected}\nReceived: ${expected}${logText}` :
`${header}Expected: ${expected}\nReceived: ${unexpected}${logText}`;
};
return { locator: receiver, message, pass: matches, actual, name: matcherName, expected, log };
return { message, pass: matches, actual, name: matcherName, expected, log };
}

Просмотреть файл

@ -67,5 +67,5 @@ export async function toEqual<T>(
// Passing the actual and expected objects so that a custom reporter
// could access them, for example in order to display a custom visual diff,
// or create a different error message
return { locator: receiver, actual: received, expected, message, name: matcherName, pass, log };
return { actual: received, expected, message, name: matcherName, pass, log };
}

Просмотреть файл

@ -99,7 +99,6 @@ export async function toMatchText(
};
return {
locator: receiver,
name: matcherName,
expected,
message,

67
packages/playwright/types/test.d.ts поставляемый
Просмотреть файл

@ -3363,7 +3363,7 @@ export interface TestType<TestArgs extends KeyValue, WorkerArgs extends KeyValue
* ```
*
*/
expect: Expect;
expect: Expect<{}>;
/**
* Extends the `test` object by defining fixtures and/or options that can be used in the tests.
*
@ -5083,38 +5083,81 @@ type AllMatchers<R, T> = PageAssertions & LocatorAssertions & APIResponseAsserti
type IfAny<T, Y, N> = 0 extends (1 & T) ? Y : N;
type Awaited<T> = T extends PromiseLike<infer U> ? U : T;
type MakeMatchers<R, T> = {
type ToUserMatcher<F> = F extends (first: any, ...args: infer Rest) => infer R ? (...args: Rest) => (R extends PromiseLike<infer U> ? Promise<void> : void) : never;
type ToUserMatcherObject<T, ArgType> = {
[K in keyof T as T[K] extends (arg: ArgType, ...rest: any[]) => any ? K : never]: ToUserMatcher<T[K]>;
};
type MatcherHintColor = (arg: string) => string;
export type MatcherHintOptions = {
comment?: string;
expectedColor?: MatcherHintColor;
isDirectExpectCall?: boolean;
isNot?: boolean;
promise?: string;
receivedColor?: MatcherHintColor;
secondArgument?: string;
secondArgumentColor?: MatcherHintColor;
};
export interface ExpectMatcherUtils {
matcherHint(matcherName: string, received: unknown, expected: unknown, options?: MatcherHintOptions): string;
printDiffOrStringify(expected: unknown, received: unknown, expectedLabel: string, receivedLabel: string, expand: boolean): string;
printExpected(value: unknown): string;
printReceived(object: unknown): string;
printWithType<T>(name: string, value: T, print: (value: T) => string): string;
diff(a: unknown, b: unknown): string | null;
stringify(object: unknown, maxDepth?: number, maxWidth?: number): string;
}
type State = {
isNot: boolean;
promise: 'rejects' | 'resolves' | '';
utils: ExpectMatcherUtils;
};
type MatcherReturnType = {
message: () => string;
pass: boolean;
name?: string;
expected?: unknown;
actual?: any;
log?: string[];
};
type MakeMatchers<R, T, ExtendedMatchers> = {
/**
* If you know how to test something, `.not` lets you test its opposite.
*/
not: MakeMatchers<R, T>;
not: MakeMatchers<R, T, ExtendedMatchers>;
/**
* Use resolves to unwrap the value of a fulfilled promise so any other
* matcher can be chained. If the promise is rejected the assertion fails.
*/
resolves: MakeMatchers<Promise<R>, Awaited<T>>;
resolves: MakeMatchers<Promise<R>, Awaited<T>, ExtendedMatchers>;
/**
* Unwraps the reason of a rejected promise so any other matcher can be chained.
* If the promise is fulfilled the assertion fails.
*/
rejects: MakeMatchers<Promise<R>, any>;
} & IfAny<T, AllMatchers<R, T>, SpecificMatchers<R, T>>;
rejects: MakeMatchers<Promise<R>, any, ExtendedMatchers>;
} & IfAny<T, AllMatchers<R, T>, SpecificMatchers<R, T> & ToUserMatcherObject<ExtendedMatchers, T>>;
export type Expect = {
<T = unknown>(actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers<void, T>;
soft: <T = unknown>(actual: T, messageOrOptions?: string | { message?: string }) => MakeMatchers<void, T>;
export type Expect<ExtendedMatchers> = {
<T = unknown>(actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers<void, T, ExtendedMatchers>;
soft: <T = unknown>(actual: T, messageOrOptions?: string | { message?: string }) => MakeMatchers<void, T, ExtendedMatchers>;
poll: <T = unknown>(actual: () => T | Promise<T>, messageOrOptions?: string | { message?: string, timeout?: number, intervals?: number[] }) => BaseMatchers<Promise<void>, T> & {
/**
* If you know how to test something, `.not` lets you test its opposite.
*/
not: BaseMatchers<Promise<void>, T>;
};
extend(matchers: any): void;
extend<MoreMatchers extends Record<string, (this: State, receiver: any, ...args: any[]) => MatcherReturnType | Promise<MatcherReturnType>>>(matchers: MoreMatchers): Expect<ExtendedMatchers & MoreMatchers>;
configure: (configuration: {
message?: string,
timeout?: number,
soft?: boolean,
}) => Expect;
}) => Expect<ExtendedMatchers>;
getState(): {
expand?: boolean;
isNot?: boolean;
@ -5141,7 +5184,7 @@ export const test: TestType<PlaywrightTestArgs & PlaywrightTestOptions, Playwrig
export default test;
export const _baseTest: TestType<{}, {}>;
export const expect: Expect;
export const expect: Expect<{}>;
/**
* Defines Playwright config

25
tests/installation/expect.d.ts поставляемый
Просмотреть файл

@ -1,25 +0,0 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {}
declare global {
namespace PlaywrightTest {
interface Matchers<R, T> {
toHaveLoggedSoftwareDownload(browsers: ("chromium" | "firefox" | "webkit" | "ffmpeg")[]): R;
}
}
}

Просмотреть файл

@ -14,9 +14,6 @@
* limitations under the License.
*/
// eslint-disable-next-line spaced-comment
/// <reference path="./expect.d.ts" />
import { _baseTest as _test, expect as _expect } from '@playwright/test';
import fs from 'fs';
import os from 'os';
@ -32,7 +29,7 @@ export const TMP_WORKSPACES = path.join(os.platform() === 'darwin' ? '/tmp' : os
const debug = debugLogger('itest');
_expect.extend({
const expect = _expect.extend({
toHaveLoggedSoftwareDownload(received: any, browsers: ('chromium' | 'firefox' | 'webkit' | 'ffmpeg')[]) {
if (typeof received !== 'string')
throw new Error(`Expected argument to be a string.`);
@ -42,8 +39,12 @@ _expect.extend({
downloaded.add(browser.toLowerCase());
const expected = browsers;
if (expected.length === downloaded.size && expected.every(browser => downloaded.has(browser)))
return { pass: true };
if (expected.length === downloaded.size && expected.every(browser => downloaded.has(browser))) {
return {
pass: true,
message: () => 'Expected not to download browsers, but did.'
};
}
return {
pass: false,
message: () => [
@ -55,8 +56,6 @@ _expect.extend({
}
});
const expect = _expect;
type ExecOptions = { cwd?: string, env?: Record<string, string>, message?: string, expectToExitWithError?: boolean };
type ArgsOrOptions = [] | [...string[]] | [...string[], ExecOptions] | [ExecOptions];
@ -199,7 +198,7 @@ export const test = _test
},
tsc: async ({ exec }, use) => {
await exec('npm i --foreground-scripts typescript@3.8 @types/node@14');
await use((...args: ArgsOrOptions) => exec('npx', '-p', 'typescript@3.8', 'tsc', ...args));
await use((...args: ArgsOrOptions) => exec('npx', '-p', 'typescript@4.1.6', 'tsc', ...args));
},
});

Просмотреть файл

@ -25,7 +25,6 @@ test('toMatchText-based assertions should have matcher result', async ({ page })
const e = await expect(locator).toHaveText(/Text2/, { timeout: 1 }).catch(e => e);
e.matcherResult.message = stripAnsi(e.matcherResult.message);
expect.soft(e.matcherResult).toEqual({
locator: expect.any(Object),
actual: 'Text content',
expected: /Text2/,
message: expect.stringContaining(`Timed out 1ms waiting for expect(locator).toHaveText(expected)`),
@ -47,7 +46,6 @@ Call log`);
const e = await expect(locator).not.toHaveText(/Text/, { timeout: 1 }).catch(e => e);
e.matcherResult.message = stripAnsi(e.matcherResult.message);
expect.soft(e.matcherResult).toEqual({
locator: expect.any(Object),
actual: 'Text content',
expected: /Text/,
message: expect.stringContaining(`Timed out 1ms waiting for expect(locator).not.toHaveText(expected)`),
@ -73,7 +71,6 @@ test('toBeTruthy-based assertions should have matcher result', async ({ page })
const e = await expect(page.locator('#node2')).toBeVisible({ timeout: 1 }).catch(e => e);
e.matcherResult.message = stripAnsi(e.matcherResult.message);
expect.soft(e.matcherResult).toEqual({
locator: expect.any(Object),
actual: 'hidden',
expected: 'visible',
message: expect.stringContaining(`Timed out 1ms waiting for expect(locator).toBeVisible()`),
@ -95,7 +92,6 @@ Call log`);
const e = await expect(page.locator('#node')).not.toBeVisible({ timeout: 1 }).catch(e => e);
e.matcherResult.message = stripAnsi(e.matcherResult.message);
expect.soft(e.matcherResult).toEqual({
locator: expect.any(Object),
actual: 'visible',
expected: 'visible',
message: expect.stringContaining(`Timed out 1ms waiting for expect(locator).not.toBeVisible()`),
@ -121,7 +117,6 @@ test('toEqual-based assertions should have matcher result', async ({ page }) =>
const e = await expect(page.locator('#node2')).toHaveCount(1, { timeout: 1 }).catch(e => e);
e.matcherResult.message = stripAnsi(e.matcherResult.message);
expect.soft(e.matcherResult).toEqual({
locator: expect.any(Object),
actual: 0,
expected: 1,
message: expect.stringContaining(`Timed out 1ms waiting for expect(locator).toHaveCount(expected)`),
@ -142,7 +137,6 @@ Call log`);
const e = await expect(page.locator('#node')).not.toHaveCount(1, { timeout: 1 }).catch(e => e);
e.matcherResult.message = stripAnsi(e.matcherResult.message);
expect.soft(e.matcherResult).toEqual({
locator: expect.any(Object),
actual: 1,
expected: 1,
message: expect.stringContaining(`Timed out 1ms waiting for expect(locator).not.toHaveCount(expected)`),
@ -171,7 +165,6 @@ test('toBeChecked({ checked: false }) should have expected: false', async ({ pag
const e = await expect(page.locator('#unchecked')).toBeChecked({ timeout: 1 }).catch(e => e);
e.matcherResult.message = stripAnsi(e.matcherResult.message);
expect.soft(e.matcherResult).toEqual({
locator: expect.any(Object),
actual: 'unchecked',
expected: 'checked',
message: expect.stringContaining(`Timed out 1ms waiting for expect(locator).toBeChecked()`),
@ -193,7 +186,6 @@ Call log`);
const e = await expect(page.locator('#checked')).not.toBeChecked({ timeout: 1 }).catch(e => e);
e.matcherResult.message = stripAnsi(e.matcherResult.message);
expect.soft(e.matcherResult).toEqual({
locator: expect.any(Object),
actual: 'checked',
expected: 'checked',
message: expect.stringContaining(`Timed out 1ms waiting for expect(locator).not.toBeChecked()`),
@ -215,7 +207,6 @@ Call log`);
const e = await expect(page.locator('#checked')).toBeChecked({ checked: false, timeout: 1 }).catch(e => e);
e.matcherResult.message = stripAnsi(e.matcherResult.message);
expect.soft(e.matcherResult).toEqual({
locator: expect.any(Object),
actual: 'checked',
expected: 'unchecked',
message: expect.stringContaining(`Timed out 1ms waiting for expect(locator).toBeChecked({ checked: false })`),
@ -237,7 +228,6 @@ Call log`);
const e = await expect(page.locator('#unchecked')).not.toBeChecked({ checked: false, timeout: 1 }).catch(e => e);
e.matcherResult.message = stripAnsi(e.matcherResult.message);
expect.soft(e.matcherResult).toEqual({
locator: expect.any(Object),
actual: 'unchecked',
expected: 'unchecked',
message: expect.stringContaining(`Timed out 1ms waiting for expect(locator).not.toBeChecked({ checked: false })`),

Просмотреть файл

@ -15,7 +15,7 @@
*/
import path from 'path';
import { test, expect, parseTestRunnerOutput } from './playwright-test-fixtures';
import { test, expect, parseTestRunnerOutput, stripAnsi } from './playwright-test-fixtures';
test('should be able to call expect.extend in config', async ({ runInlineTest }) => {
const result = await runInlineTest({
@ -273,7 +273,12 @@ test('should work with custom PlaywrightTest namespace', async ({ runTSC }) => {
'a.spec.ts': `
import { test, expect, type Page, type APIResponse } from '@playwright/test';
test.expect.extend({
toBeWithinRange() { },
toBeWithinRange() {
return {
pass: true,
message: () => '',
};
},
});
const page = {} as Page;
@ -704,3 +709,153 @@ test('should not leak long expect message strings', async ({ runInlineTest }) =>
expect(result.failed).toBe(0);
expect(result.exitCode).toBe(0);
});
test('should chain expect matchers and expose matcher utils (TSC)', async ({ runTSC }) => {
const result = await runTSC({
'a.spec.ts': `
import { test, expect as baseExpect } from '@playwright/test';
import type { Page, Locator } from '@playwright/test';
function callLogText(log: string[] | undefined): string {
if (!log)
return '';
return log.join('\\n');
}
const expect = baseExpect.extend({
async toHaveAmount(locator: Locator, expected: string, options?: { timeout?: number }) {
const baseAmount = locator.locator('.base-amount');
let pass: boolean;
let matcherResult: any;
try {
await baseExpect(baseAmount).toHaveAttribute('data-amount', expected, options);
pass = true;
} catch (e: any) {
matcherResult = e.matcherResult;
pass = false;
}
const expectOptions = {
isNot: this.isNot,
};
const log = callLogText(matcherResult?.log);
const message = pass
? () => this.utils.matcherHint('toBe', locator, expected, expectOptions) +
'\\n\\n' +
\`Expected: \${this.isNot ? 'not' : ''}\${this.utils.printExpected(expected)}\\n\` +
(matcherResult ? \`Received: \${this.utils.printReceived(matcherResult.actual)}\` : '') +
log
: () => this.utils.matcherHint('toBe', locator, expected, expectOptions) +
'\\n\\n' +
\`Expected: \${this.utils.printExpected(expected)}\n\` +
(matcherResult ? \`Received: \${this.utils.printReceived(matcherResult.actual)}\` : '') +
log;
return {
name: 'toHaveAmount',
expected,
message,
pass,
actual: matcherResult?.actual,
log: matcherResult?.log,
};
},
async toBeANicePage(page: Page) {
return {
name: 'toBeANicePage',
expected: 1,
message: () => '',
pass: true,
};
}
});
test('custom matchers', async ({ page }) => {
await page.setContent(\`
<div>
<div class='base-amount' data-amount='2'></div>
</div>
\`);
await expect(page.locator('div')).toHaveAmount('3', { timeout: 1000 });
await expect(page).toBeANicePage();
// @ts-expect-error
await expect(page).toHaveAmount('3', { timeout: 1000 });
// @ts-expect-error
await expect(page.locator('div')).toBeANicePage();
});`
});
expect(result.exitCode).toBe(0);
});
test('should chain expect matchers and expose matcher utils', async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.spec.ts': `
import { test, expect as baseExpect } from '@playwright/test';
import type { Page, Locator } from '@playwright/test';
function callLogText(log: string[] | undefined): string {
if (!log)
return '';
return log.join('\\n');
}
const expect = baseExpect.extend({
async toHaveAmount(locator: Locator, expected: string, options?: { timeout?: number }) {
const baseAmount = locator.locator('.base-amount');
let pass: boolean;
let matcherResult: any;
try {
await baseExpect(baseAmount).toHaveAttribute('data-amount', expected, options);
pass = true;
} catch (e: any) {
matcherResult = e.matcherResult;
pass = false;
}
const expectOptions = {
isNot: this.isNot,
};
const log = callLogText(matcherResult?.log);
const message = pass
? () => this.utils.matcherHint('toBe', locator, expected, expectOptions) +
'\\n\\n' +
\`Expected: \${this.isNot ? 'not' : ''}\${this.utils.printExpected(expected)}\\n\` +
(matcherResult ? \`Received: \${this.utils.printReceived(matcherResult.actual)}\` : '') +
log
: () => this.utils.matcherHint('toBe', locator, expected, expectOptions) +
'\\n\\n' +
\`Expected: \${this.utils.printExpected(expected)}\n\` +
(matcherResult ? \`Received: \${this.utils.printReceived(matcherResult.actual)}\` : '') +
log;
return {
name: 'toHaveAmount',
expected,
message,
pass,
actual: matcherResult?.actual,
log: matcherResult?.log,
};
},
});
test('custom matchers', async ({ page }) => {
await page.setContent(\`
<div>
<div class='base-amount' data-amount='2'></div>
</div>
\`);
await expect(page.locator('div')).toHaveAmount('3', { timeout: 1000 });
});`
}, { workers: 1 });
const output = stripAnsi(result.output);
expect(output).toContain(`await expect(page.locator('div')).toHaveAmount('3', { timeout: 1000 });`);
expect(output).toContain('a.spec.ts:60');
expect(result.failed).toBe(1);
expect(result.exitCode).toBe(1);
});

Просмотреть файл

@ -600,6 +600,7 @@ class TypesGenerator {
'PlaywrightWorkerOptions.defaultBrowserType',
'PlaywrightWorkerArgs.playwright',
'Matchers',
'ExpectMatcherUtils',
]),
doNotExportClassNames: new Set([...assertionClasses, 'TestProject']),
includeExperimental,

67
utils/generate_types/overrides-test.d.ts поставляемый
Просмотреть файл

@ -158,7 +158,7 @@ export interface TestType<TestArgs extends KeyValue, WorkerArgs extends KeyValue
afterAll(title: string, inner: (args: TestArgs & WorkerArgs, testInfo: TestInfo) => Promise<any> | any): void;
use(fixtures: Fixtures<{}, {}, TestArgs, WorkerArgs>): void;
step<T>(title: string, body: () => T | Promise<T>): Promise<T>;
expect: Expect;
expect: Expect<{}>;
extend<T extends KeyValue, W extends KeyValue = {}>(fixtures: Fixtures<T, W, TestArgs, WorkerArgs>): TestType<TestArgs & T, WorkerArgs & W>;
info(): TestInfo;
}
@ -341,38 +341,81 @@ type AllMatchers<R, T> = PageAssertions & LocatorAssertions & APIResponseAsserti
type IfAny<T, Y, N> = 0 extends (1 & T) ? Y : N;
type Awaited<T> = T extends PromiseLike<infer U> ? U : T;
type MakeMatchers<R, T> = {
type ToUserMatcher<F> = F extends (first: any, ...args: infer Rest) => infer R ? (...args: Rest) => (R extends PromiseLike<infer U> ? Promise<void> : void) : never;
type ToUserMatcherObject<T, ArgType> = {
[K in keyof T as T[K] extends (arg: ArgType, ...rest: any[]) => any ? K : never]: ToUserMatcher<T[K]>;
};
type MatcherHintColor = (arg: string) => string;
export type MatcherHintOptions = {
comment?: string;
expectedColor?: MatcherHintColor;
isDirectExpectCall?: boolean;
isNot?: boolean;
promise?: string;
receivedColor?: MatcherHintColor;
secondArgument?: string;
secondArgumentColor?: MatcherHintColor;
};
export interface ExpectMatcherUtils {
matcherHint(matcherName: string, received: unknown, expected: unknown, options?: MatcherHintOptions): string;
printDiffOrStringify(expected: unknown, received: unknown, expectedLabel: string, receivedLabel: string, expand: boolean): string;
printExpected(value: unknown): string;
printReceived(object: unknown): string;
printWithType<T>(name: string, value: T, print: (value: T) => string): string;
diff(a: unknown, b: unknown): string | null;
stringify(object: unknown, maxDepth?: number, maxWidth?: number): string;
}
type State = {
isNot: boolean;
promise: 'rejects' | 'resolves' | '';
utils: ExpectMatcherUtils;
};
type MatcherReturnType = {
message: () => string;
pass: boolean;
name?: string;
expected?: unknown;
actual?: any;
log?: string[];
};
type MakeMatchers<R, T, ExtendedMatchers> = {
/**
* If you know how to test something, `.not` lets you test its opposite.
*/
not: MakeMatchers<R, T>;
not: MakeMatchers<R, T, ExtendedMatchers>;
/**
* Use resolves to unwrap the value of a fulfilled promise so any other
* matcher can be chained. If the promise is rejected the assertion fails.
*/
resolves: MakeMatchers<Promise<R>, Awaited<T>>;
resolves: MakeMatchers<Promise<R>, Awaited<T>, ExtendedMatchers>;
/**
* Unwraps the reason of a rejected promise so any other matcher can be chained.
* If the promise is fulfilled the assertion fails.
*/
rejects: MakeMatchers<Promise<R>, any>;
} & IfAny<T, AllMatchers<R, T>, SpecificMatchers<R, T>>;
rejects: MakeMatchers<Promise<R>, any, ExtendedMatchers>;
} & IfAny<T, AllMatchers<R, T>, SpecificMatchers<R, T> & ToUserMatcherObject<ExtendedMatchers, T>>;
export type Expect = {
<T = unknown>(actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers<void, T>;
soft: <T = unknown>(actual: T, messageOrOptions?: string | { message?: string }) => MakeMatchers<void, T>;
export type Expect<ExtendedMatchers> = {
<T = unknown>(actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers<void, T, ExtendedMatchers>;
soft: <T = unknown>(actual: T, messageOrOptions?: string | { message?: string }) => MakeMatchers<void, T, ExtendedMatchers>;
poll: <T = unknown>(actual: () => T | Promise<T>, messageOrOptions?: string | { message?: string, timeout?: number, intervals?: number[] }) => BaseMatchers<Promise<void>, T> & {
/**
* If you know how to test something, `.not` lets you test its opposite.
*/
not: BaseMatchers<Promise<void>, T>;
};
extend(matchers: any): void;
extend<MoreMatchers extends Record<string, (this: State, receiver: any, ...args: any[]) => MatcherReturnType | Promise<MatcherReturnType>>>(matchers: MoreMatchers): Expect<ExtendedMatchers & MoreMatchers>;
configure: (configuration: {
message?: string,
timeout?: number,
soft?: boolean,
}) => Expect;
}) => Expect<ExtendedMatchers>;
getState(): {
expand?: boolean;
isNot?: boolean;
@ -399,7 +442,7 @@ export const test: TestType<PlaywrightTestArgs & PlaywrightTestOptions, Playwrig
export default test;
export const _baseTest: TestType<{}, {}>;
export const expect: Expect;
export const expect: Expect<{}>;
/**
* Defines Playwright config