diff --git a/packages/playwright-test/types/testExpect.d.ts b/packages/playwright-test/types/testExpect.d.ts index 07d5bb31f2..03ddc17685 100644 --- a/packages/playwright-test/types/testExpect.d.ts +++ b/packages/playwright-test/types/testExpect.d.ts @@ -15,11 +15,20 @@ */ import type * as expect from 'expect'; +import type { Page, Locator, APIResponse } from 'playwright-core'; export declare type AsymmetricMatcher = Record; +type IfAny = 0 extends (1 & T) ? Y : N; +type ExtraMatchers = T extends Type ? Matchers : IfAny; + +type MakeMatchers = PlaywrightTest.Matchers & + ExtraMatchers & + ExtraMatchers & + ExtraMatchers + export declare type Expect = { - (actual: T): PlaywrightTest.Matchers; + (actual: T): MakeMatchers; // Sourced from node_modules/expect/build/types.d.ts assertions(arg0: number): void; @@ -36,14 +45,16 @@ export declare type Expect = { stringMatching(expected: string | RegExp): AsymmetricMatcher; }; +type Awaited = T extends PromiseLike ? U : T; + type OverriddenExpectProperties = -'not' | -'resolves' | -'rejects' | -'toMatchInlineSnapshot' | -'toThrowErrorMatchingInlineSnapshot' | -'toMatchSnapshot' | -'toThrowErrorMatchingSnapshot'; + 'not' | + 'resolves' | + 'rejects' | + 'toMatchInlineSnapshot' | + 'toThrowErrorMatchingInlineSnapshot' | + 'toMatchSnapshot' | + 'toThrowErrorMatchingSnapshot'; declare global { export namespace PlaywrightTest { @@ -51,17 +62,17 @@ declare global { /** * If you know how to test something, `.not` lets you test its opposite. */ - not: PlaywrightTest.Matchers; + 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: PlaywrightTest.Matchers>; + resolves: MakeMatchers, R>; /** * Unwraps the reason of a rejected promise so any other matcher can be chained. * If the promise is fulfilled the assertion fails. */ - rejects: PlaywrightTest.Matchers>; + rejects: MakeMatchers>; /** * Match snapshot */ @@ -75,108 +86,113 @@ declare global { toMatchSnapshot(name: string | string[], options?: { threshold?: number }): R; - - /** - * Asserts input is checked (or unchecked if { checked: false } is passed). - */ - toBeChecked(options?: { checked?: boolean, timeout?: number }): Promise; - - /** - * Asserts input is disabled. - */ - toBeDisabled(options?: { timeout?: number }): Promise; - - /** - * Asserts input is editable. - */ - toBeEditable(options?: { timeout?: number }): Promise; - - /** - * Asserts given DOM node or input has no text content or no input value. - */ - toBeEmpty(options?: { timeout?: number }): Promise; - - /** - * Asserts input is enabled. - */ - toBeEnabled(options?: { timeout?: number }): Promise; - - /** - * Asserts given DOM is a focused (active) in document. - */ - toBeFocused(options?: { timeout?: number }): Promise; - - /** - * Asserts given DOM node is hidden or detached from DOM. - */ - toBeHidden(options?: { timeout?: number }): Promise; - - /** - * Asserts given APIResponse's status is between 200 and 299. - */ - toBeOK(): Promise; - - /** - * Asserts given DOM node visible on the screen. - */ - toBeVisible(options?: { timeout?: number }): Promise; - - /** - * Asserts element's text content matches given pattern or contains given substring. - */ - toContainText(expected: string | RegExp | (string|RegExp)[], options?: { timeout?: number, useInnerText?: boolean }): Promise; - - /** - * Asserts element's attributes `name` matches expected value. - */ - toHaveAttribute(name: string, expected: string | RegExp, options?: { timeout?: number }): Promise; - - /** - * Asserts that DOM node has a given CSS class. - */ - toHaveClass(className: string | RegExp | (string|RegExp)[], options?: { timeout?: number }): Promise; - - /** - * Asserts number of DOM nodes matching given locator. - */ - toHaveCount(expected: number, options?: { timeout?: number }): Promise; - - /** - * Asserts element's computed CSS property `name` matches expected value. - */ - toHaveCSS(name: string, expected: string | RegExp, options?: { timeout?: number }): Promise; - - /** - * Asserts element's `id` attribute matches expected value. - */ - toHaveId(expected: string | RegExp, options?: { timeout?: number }): Promise; - - /** - * Asserts JavaScript object that corresponds to the Node has a property with given value. - */ - toHaveJSProperty(name: string, value: any, options?: { timeout?: number }): Promise; - - /** - * Asserts element's text content. - */ - toHaveText(expected: string | RegExp | (string|RegExp)[], options?: { timeout?: number, useInnerText?: boolean }): Promise; - - /** - * Asserts page's title. - */ - toHaveTitle(expected: string | RegExp, options?: { timeout?: number }): Promise; - - /** - * Asserts page's URL. - */ - toHaveURL(expected: string | RegExp, options?: { timeout?: number }): Promise; - - /** - * Asserts input element's value. - */ - toHaveValue(expected: string | RegExp, options?: { timeout?: number }): Promise; } } } +interface LocatorMatchers { + /** + * Asserts input is checked (or unchecked if { checked: false } is passed). + */ + toBeChecked(options?: { checked?: boolean, timeout?: number }): Promise; + + /** + * Asserts input is disabled. + */ + toBeDisabled(options?: { timeout?: number }): Promise; + + /** + * Asserts input is editable. + */ + toBeEditable(options?: { timeout?: number }): Promise; + + /** + * Asserts given DOM node or input has no text content or no input value. + */ + toBeEmpty(options?: { timeout?: number }): Promise; + + /** + * Asserts input is enabled. + */ + toBeEnabled(options?: { timeout?: number }): Promise; + + /** + * Asserts given DOM is a focused (active) in document. + */ + toBeFocused(options?: { timeout?: number }): Promise; + + /** + * Asserts given DOM node is hidden or detached from DOM. + */ + toBeHidden(options?: { timeout?: number }): Promise; + + /** + * Asserts element's text content matches given pattern or contains given substring. + */ + toContainText(expected: string | RegExp | (string | RegExp)[], options?: { timeout?: number, useInnerText?: boolean }): Promise; + + /** + * Asserts element's attributes `name` matches expected value. + */ + toHaveAttribute(name: string, expected: string | RegExp, options?: { timeout?: number }): Promise; + + /** + * Asserts that DOM node has a given CSS class. + */ + toHaveClass(className: string | RegExp | (string | RegExp)[], options?: { timeout?: number }): Promise; + + /** + * Asserts number of DOM nodes matching given locator. + */ + toHaveCount(expected: number, options?: { timeout?: number }): Promise; + + /** + * Asserts element's computed CSS property `name` matches expected value. + */ + toHaveCSS(name: string, expected: string | RegExp, options?: { timeout?: number }): Promise; + + /** + * Asserts element's `id` attribute matches expected value. + */ + toHaveId(expected: string | RegExp, options?: { timeout?: number }): Promise; + + /** + * Asserts JavaScript object that corresponds to the Node has a property with given value. + */ + toHaveJSProperty(name: string, value: any, options?: { timeout?: number }): Promise; + + /** + * Asserts element's text content. + */ + toHaveText(expected: string | RegExp | (string | RegExp)[], options?: { timeout?: number, useInnerText?: boolean }): Promise; + + /** + * Asserts input element's value. + */ + toHaveValue(expected: string | RegExp, options?: { timeout?: number }): Promise; + + /** + * Asserts given DOM node visible on the screen. + */ + toBeVisible(options?: { timeout?: number }): Promise; +} +interface PageMatchers { + /** + * Asserts page's title. + */ + toHaveTitle(expected: string | RegExp, options?: { timeout?: number }): Promise; + + /** + * Asserts page's URL. + */ + toHaveURL(expected: string | RegExp, options?: { timeout?: number }): Promise; +} + +interface APIResponseMatchers { + /** + * Asserts given APIResponse's status is between 200 and 299. + */ + toBeOK(): Promise; +} + export { }; diff --git a/tests/playwright-test/expect.spec.ts b/tests/playwright-test/expect.spec.ts index 9bf015afd6..08df44249e 100644 --- a/tests/playwright-test/expect.spec.ts +++ b/tests/playwright-test/expect.spec.ts @@ -144,29 +144,29 @@ test('should work with custom PlaywrightTest namespace', async ({ runTSC }) => { }); test('should propose only the relevant matchers when custom expect matcher classes were passed', async ({ runTSC }) => { - test.fixme(); const result = await runTSC({ 'a.spec.ts': ` const { test } = pwt; test('custom matchers', async ({ page }) => { await test.expect(page).toHaveURL('https://example.com'); - // @ts-expect-error await test.expect(page).toBe(true); // @ts-expect-error await test.expect(page).toBeEnabled(); await test.expect(page.locator('foo')).toBeEnabled(); - // @ts-expect-error await test.expect(page.locator('foo')).toBe(true); // @ts-expect-error await test.expect(page.locator('foo')).toHaveURL('https://example.com'); const res = await page.request.get('http://i-do-definitely-not-exist.com'); await test.expect(res).toBeOK(); - // @ts-expect-error await test.expect(res).toBe(true); // @ts-expect-error await test.expect(res).toHaveURL('https://example.com'); + + await test.expect(res as any).toHaveURL('https://example.com'); + // @ts-expect-error + await test.expect(123).toHaveURL('https://example.com'); }); ` });