diff --git a/packages/playwright/src/matchers/expect.ts b/packages/playwright/src/matchers/expect.ts index a0d5dff91e..18d5c6b597 100644 --- a/packages/playwright/src/matchers/expect.ts +++ b/packages/playwright/src/matchers/expect.ts @@ -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 { 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 */); diff --git a/packages/playwright/src/matchers/toBeTruthy.ts b/packages/playwright/src/matchers/toBeTruthy.ts index 751d4e4756..c7f5a4906e 100644 --- a/packages/playwright/src/matchers/toBeTruthy.ts +++ b/packages/playwright/src/matchers/toBeTruthy.ts @@ -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 }; } diff --git a/packages/playwright/src/matchers/toEqual.ts b/packages/playwright/src/matchers/toEqual.ts index 4c47700e0a..a4bf47030c 100644 --- a/packages/playwright/src/matchers/toEqual.ts +++ b/packages/playwright/src/matchers/toEqual.ts @@ -67,5 +67,5 @@ export async function toEqual( // 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 }; } diff --git a/packages/playwright/src/matchers/toMatchText.ts b/packages/playwright/src/matchers/toMatchText.ts index efeb5280b8..3a5f39b0d9 100644 --- a/packages/playwright/src/matchers/toMatchText.ts +++ b/packages/playwright/src/matchers/toMatchText.ts @@ -99,7 +99,6 @@ export async function toMatchText( }; return { - locator: receiver, name: matcherName, expected, message, diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index abbf6f03ab..cb6be198c8 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -3363,7 +3363,7 @@ export interface TestType; /** * Extends the `test` object by defining fixtures and/or options that can be used in the tests. * @@ -5083,38 +5083,81 @@ type AllMatchers = PageAssertions & LocatorAssertions & APIResponseAsserti type IfAny = 0 extends (1 & T) ? Y : N; type Awaited = T extends PromiseLike ? U : T; -type MakeMatchers = { +type ToUserMatcher = F extends (first: any, ...args: infer Rest) => infer R ? (...args: Rest) => (R extends PromiseLike ? Promise : void) : never; +type ToUserMatcherObject = { + [K in keyof T as T[K] extends (arg: ArgType, ...rest: any[]) => any ? K : never]: ToUserMatcher; +}; + +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(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 = { /** * If you know how to test something, `.not` lets you test its opposite. */ - not: MakeMatchers; + not: MakeMatchers; /** * 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, Awaited>; + resolves: MakeMatchers, Awaited, 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, any>; -} & IfAny, SpecificMatchers>; + rejects: MakeMatchers, any, ExtendedMatchers>; +} & IfAny, SpecificMatchers & ToUserMatcherObject>; -export type Expect = { - (actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers; - soft: (actual: T, messageOrOptions?: string | { message?: string }) => MakeMatchers; +export type Expect = { + (actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers; + soft: (actual: T, messageOrOptions?: string | { message?: string }) => MakeMatchers; poll: (actual: () => T | Promise, messageOrOptions?: string | { message?: string, timeout?: number, intervals?: number[] }) => BaseMatchers, T> & { /** * If you know how to test something, `.not` lets you test its opposite. */ not: BaseMatchers, T>; }; - extend(matchers: any): void; + extend MatcherReturnType | Promise>>(matchers: MoreMatchers): Expect; configure: (configuration: { message?: string, timeout?: number, soft?: boolean, - }) => Expect; + }) => Expect; getState(): { expand?: boolean; isNot?: boolean; @@ -5141,7 +5184,7 @@ export const test: TestType; -export const expect: Expect; +export const expect: Expect<{}>; /** * Defines Playwright config diff --git a/tests/installation/expect.d.ts b/tests/installation/expect.d.ts deleted file mode 100644 index e8ccb161f7..0000000000 --- a/tests/installation/expect.d.ts +++ /dev/null @@ -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 { - toHaveLoggedSoftwareDownload(browsers: ("chromium" | "firefox" | "webkit" | "ffmpeg")[]): R; - } - } -} diff --git a/tests/installation/npmTest.ts b/tests/installation/npmTest.ts index 5c8bd27870..987f75477e 100644 --- a/tests/installation/npmTest.ts +++ b/tests/installation/npmTest.ts @@ -14,9 +14,6 @@ * limitations under the License. */ -// eslint-disable-next-line spaced-comment -/// - 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, 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)); }, }); diff --git a/tests/page/expect-matcher-result.spec.ts b/tests/page/expect-matcher-result.spec.ts index 5d7ae66ca7..f5f4f72579 100644 --- a/tests/page/expect-matcher-result.spec.ts +++ b/tests/page/expect-matcher-result.spec.ts @@ -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 })`), diff --git a/tests/playwright-test/expect.spec.ts b/tests/playwright-test/expect.spec.ts index d2977dae25..ed20b27e6f 100644 --- a/tests/playwright-test/expect.spec.ts +++ b/tests/playwright-test/expect.spec.ts @@ -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(\` +
+
+
+ \`); + 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(\` +
+
+
+ \`); + 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); +}); diff --git a/utils/generate_types/index.js b/utils/generate_types/index.js index c7e15c648c..5d6c19b5b5 100644 --- a/utils/generate_types/index.js +++ b/utils/generate_types/index.js @@ -600,6 +600,7 @@ class TypesGenerator { 'PlaywrightWorkerOptions.defaultBrowserType', 'PlaywrightWorkerArgs.playwright', 'Matchers', + 'ExpectMatcherUtils', ]), doNotExportClassNames: new Set([...assertionClasses, 'TestProject']), includeExperimental, diff --git a/utils/generate_types/overrides-test.d.ts b/utils/generate_types/overrides-test.d.ts index 5df0c147de..f95b9b665c 100644 --- a/utils/generate_types/overrides-test.d.ts +++ b/utils/generate_types/overrides-test.d.ts @@ -158,7 +158,7 @@ export interface TestType Promise | any): void; use(fixtures: Fixtures<{}, {}, TestArgs, WorkerArgs>): void; step(title: string, body: () => T | Promise): Promise; - expect: Expect; + expect: Expect<{}>; extend(fixtures: Fixtures): TestType; info(): TestInfo; } @@ -341,38 +341,81 @@ type AllMatchers = PageAssertions & LocatorAssertions & APIResponseAsserti type IfAny = 0 extends (1 & T) ? Y : N; type Awaited = T extends PromiseLike ? U : T; -type MakeMatchers = { +type ToUserMatcher = F extends (first: any, ...args: infer Rest) => infer R ? (...args: Rest) => (R extends PromiseLike ? Promise : void) : never; +type ToUserMatcherObject = { + [K in keyof T as T[K] extends (arg: ArgType, ...rest: any[]) => any ? K : never]: ToUserMatcher; +}; + +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(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 = { /** * If you know how to test something, `.not` lets you test its opposite. */ - not: MakeMatchers; + not: MakeMatchers; /** * 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, Awaited>; + resolves: MakeMatchers, Awaited, 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, any>; -} & IfAny, SpecificMatchers>; + rejects: MakeMatchers, any, ExtendedMatchers>; +} & IfAny, SpecificMatchers & ToUserMatcherObject>; -export type Expect = { - (actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers; - soft: (actual: T, messageOrOptions?: string | { message?: string }) => MakeMatchers; +export type Expect = { + (actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers; + soft: (actual: T, messageOrOptions?: string | { message?: string }) => MakeMatchers; poll: (actual: () => T | Promise, messageOrOptions?: string | { message?: string, timeout?: number, intervals?: number[] }) => BaseMatchers, T> & { /** * If you know how to test something, `.not` lets you test its opposite. */ not: BaseMatchers, T>; }; - extend(matchers: any): void; + extend MatcherReturnType | Promise>>(matchers: MoreMatchers): Expect; configure: (configuration: { message?: string, timeout?: number, soft?: boolean, - }) => Expect; + }) => Expect; getState(): { expand?: boolean; isNot?: boolean; @@ -399,7 +442,7 @@ export const test: TestType; -export const expect: Expect; +export const expect: Expect<{}>; /** * Defines Playwright config