feat(expect): narrow down available assertions for Page/Locator/APIResponse (#26658)
Fixes #26381.
This commit is contained in:
Родитель
197f79c933
Коммит
81cc39ea6e
|
@ -4610,9 +4610,6 @@ interface AsymmetricMatchers {
|
|||
stringMatching(sample: string | RegExp): AsymmetricMatcher;
|
||||
}
|
||||
|
||||
type IfAny<T, Y, N> = 0 extends (1 & T) ? Y : N;
|
||||
type ExtraMatchers<T, Type, Matchers> = T extends Type ? Matchers : IfAny<T, Matchers, {}>;
|
||||
|
||||
/**
|
||||
* The {@link GenericAssertions} class provides assertion methods that can be used to make assertions about any values
|
||||
* in the tests. A new instance of {@link GenericAssertions} is created by calling
|
||||
|
@ -5067,33 +5064,41 @@ interface GenericAssertions<R> {
|
|||
|
||||
}
|
||||
|
||||
type BaseMatchers<R, T> = GenericAssertions<R> & PlaywrightTest.Matchers<R, T>;
|
||||
type FunctionAssertions = {
|
||||
/**
|
||||
* Retries the callback until it passes.
|
||||
*/
|
||||
toPass(options?: { timeout?: number, intervals?: number[] }): Promise<void>;
|
||||
};
|
||||
|
||||
type MakeMatchers<R, T> = BaseMatchers<R, T> & {
|
||||
/**
|
||||
* If you know how to test something, `.not` lets you test its opposite.
|
||||
*/
|
||||
not: MakeMatchers<R, T>;
|
||||
/**
|
||||
* 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>>;
|
||||
/**
|
||||
* 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>, Awaited<T>>;
|
||||
} & SnapshotAssertions &
|
||||
ExtraMatchers<T, Page, PageAssertions> &
|
||||
ExtraMatchers<T, Locator, LocatorAssertions> &
|
||||
ExtraMatchers<T, APIResponse, APIResponseAssertions> &
|
||||
ExtraMatchers<T, Function, {
|
||||
/**
|
||||
* Retries the callback until it passes.
|
||||
*/
|
||||
toPass(options?: { timeout?: number, intervals?: number[] }): Promise<void>;
|
||||
}>;
|
||||
type BaseMatchers<R, T> = GenericAssertions<R> & PlaywrightTest.Matchers<R, T> & SnapshotAssertions;
|
||||
type AllowedGenericMatchers<R> = Pick<GenericAssertions<R>, 'toBe' | 'toBeDefined' | 'toBeFalsy' | 'toBeNull' | 'toBeTruthy' | 'toBeUndefined'>;
|
||||
|
||||
type SpecificMatchers<R, T> =
|
||||
T extends Page ? PageAssertions & AllowedGenericMatchers<R> :
|
||||
T extends Locator ? LocatorAssertions & AllowedGenericMatchers<R> :
|
||||
T extends APIResponse ? APIResponseAssertions & AllowedGenericMatchers<R> :
|
||||
BaseMatchers<R, T> & (T extends Function ? FunctionAssertions : {});
|
||||
type AllMatchers<R, T> = PageAssertions & LocatorAssertions & APIResponseAssertions & FunctionAssertions & BaseMatchers<R, T>;
|
||||
|
||||
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> = {
|
||||
/**
|
||||
* If you know how to test something, `.not` lets you test its opposite.
|
||||
*/
|
||||
not: MakeMatchers<R, T>;
|
||||
/**
|
||||
* 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>>;
|
||||
/**
|
||||
* 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>>;
|
||||
|
||||
export type Expect = {
|
||||
<T = unknown>(actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers<void, T>;
|
||||
|
@ -5119,8 +5124,6 @@ export type Expect = {
|
|||
not: Omit<AsymmetricMatchers, 'any' | 'anything'>;
|
||||
} & AsymmetricMatchers;
|
||||
|
||||
type Awaited<T> = T extends PromiseLike<infer U> ? U : T;
|
||||
|
||||
// --- BEGINGLOBAL ---
|
||||
declare global {
|
||||
export namespace PlaywrightTest {
|
||||
|
|
|
@ -294,44 +294,79 @@ test('should propose only the relevant matchers when custom expect matcher class
|
|||
'a.spec.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('custom matchers', async ({ page }) => {
|
||||
// Page-specific assertions apply to Page.
|
||||
await test.expect(page).toHaveURL('https://example.com');
|
||||
await test.expect(page).not.toHaveURL('https://example.com');
|
||||
await test.expect(page).toBe(true);
|
||||
// Some generic assertions also apply to Page.
|
||||
test.expect(page).toBe(true);
|
||||
test.expect(page).toBeDefined();
|
||||
test.expect(page).toBeFalsy();
|
||||
test.expect(page).toBeNull();
|
||||
test.expect(page).toBeTruthy();
|
||||
test.expect(page).toBeUndefined();
|
||||
|
||||
// Locator-specific and most generic assertions do not apply to Page.
|
||||
// @ts-expect-error
|
||||
await test.expect(page).toBeEnabled();
|
||||
// @ts-expect-error
|
||||
await test.expect(page).not.toBeEnabled();
|
||||
// @ts-expect-error
|
||||
test.expect(page).toEqual();
|
||||
|
||||
// Locator-specific assertions apply to Locator.
|
||||
await test.expect(page.locator('foo')).toBeEnabled();
|
||||
await test.expect(page.locator('foo')).toBeEnabled({ enabled: false });
|
||||
await test.expect(page.locator('foo')).not.toBeEnabled({ enabled: true });
|
||||
await test.expect(page.locator('foo')).toBeChecked();
|
||||
await test.expect(page.locator('foo')).not.toBeChecked({ checked: true });
|
||||
await test.expect(page.locator('foo')).not.toBeEditable();
|
||||
await test.expect(page.locator('foo')).toBeEditable({ editable: false });
|
||||
await test.expect(page.locator('foo')).toBeVisible();
|
||||
await test.expect(page.locator('foo')).not.toBeVisible({ visible: false });
|
||||
// Some generic assertions also apply to Locator.
|
||||
test.expect(page.locator('foo')).toBe(true);
|
||||
|
||||
// Page-specific and most generic assertions do not apply to Locator.
|
||||
// @ts-expect-error
|
||||
await test.expect(page.locator('foo')).toHaveURL('https://example.com');
|
||||
// @ts-expect-error
|
||||
await test.expect(page.locator('foo')).toHaveLength(1);
|
||||
|
||||
// Wrong arguments for assertions do not compile.
|
||||
// @ts-expect-error
|
||||
await test.expect(page.locator('foo')).toBeEnabled({ unknown: false });
|
||||
// @ts-expect-error
|
||||
await test.expect(page.locator('foo')).toBeEnabled({ enabled: 'foo' });
|
||||
|
||||
await test.expect(page.locator('foo')).toBe(true);
|
||||
// @ts-expect-error
|
||||
await test.expect(page.locator('foo')).toHaveURL('https://example.com');
|
||||
// Generic assertions work.
|
||||
test.expect([123]).toHaveLength(1);
|
||||
test.expect('123').toMatchSnapshot('name');
|
||||
test.expect(await page.screenshot()).toMatchSnapshot('screenshot.png');
|
||||
|
||||
// All possible assertions apply to "any" type.
|
||||
const x: any = 123;
|
||||
test.expect(x).toHaveLength(1);
|
||||
await test.expect(x).toHaveURL('url');
|
||||
await test.expect(x).toBeEnabled();
|
||||
test.expect(x).toMatchSnapshot('snapshot name');
|
||||
|
||||
// APIResponse-specific assertions apply to APIResponse.
|
||||
const res = await page.request.get('http://i-do-definitely-not-exist.com');
|
||||
await test.expect(res).toBeOK();
|
||||
await test.expect(res).toBe(true);
|
||||
// Some generic assertions also apply to APIResponse.
|
||||
test.expect(res).toBe(true);
|
||||
// Page-specific and most generic assertions do not apply to APIResponse.
|
||||
// @ts-expect-error
|
||||
await test.expect(res).toHaveURL('https://example.com');
|
||||
// @ts-expect-error
|
||||
test.expect(res).toEqual(123);
|
||||
|
||||
// Explicitly casting to "any" supports all assertions.
|
||||
await test.expect(res as any).toHaveURL('https://example.com');
|
||||
|
||||
// Playwright-specific assertions do not apply to generic values.
|
||||
// @ts-expect-error
|
||||
await test.expect(123).toHaveURL('https://example.com');
|
||||
|
||||
await test.expect(page.locator('foo')).toBeChecked();
|
||||
await test.expect(page.locator('foo')).not.toBeChecked({ checked: true });
|
||||
|
||||
await test.expect(page.locator('foo')).not.toBeEditable();
|
||||
await test.expect(page.locator('foo')).toBeEditable({ editable: false });
|
||||
|
||||
await test.expect(page.locator('foo')).toBeVisible();
|
||||
await test.expect(page.locator('foo')).not.toBeVisible({ visible: false });
|
||||
});
|
||||
`
|
||||
});
|
||||
|
|
|
@ -294,9 +294,6 @@ interface AsymmetricMatchers {
|
|||
stringMatching(sample: string | RegExp): AsymmetricMatcher;
|
||||
}
|
||||
|
||||
type IfAny<T, Y, N> = 0 extends (1 & T) ? Y : N;
|
||||
type ExtraMatchers<T, Type, Matchers> = T extends Type ? Matchers : IfAny<T, Matchers, {}>;
|
||||
|
||||
interface GenericAssertions<R> {
|
||||
not: GenericAssertions<R>;
|
||||
toBe(expected: unknown): R;
|
||||
|
@ -325,33 +322,41 @@ interface GenericAssertions<R> {
|
|||
toThrowError(error?: unknown): R;
|
||||
}
|
||||
|
||||
type BaseMatchers<R, T> = GenericAssertions<R> & PlaywrightTest.Matchers<R, T>;
|
||||
type FunctionAssertions = {
|
||||
/**
|
||||
* Retries the callback until it passes.
|
||||
*/
|
||||
toPass(options?: { timeout?: number, intervals?: number[] }): Promise<void>;
|
||||
};
|
||||
|
||||
type MakeMatchers<R, T> = BaseMatchers<R, T> & {
|
||||
/**
|
||||
* If you know how to test something, `.not` lets you test its opposite.
|
||||
*/
|
||||
not: MakeMatchers<R, T>;
|
||||
/**
|
||||
* 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>>;
|
||||
/**
|
||||
* 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>, Awaited<T>>;
|
||||
} & SnapshotAssertions &
|
||||
ExtraMatchers<T, Page, PageAssertions> &
|
||||
ExtraMatchers<T, Locator, LocatorAssertions> &
|
||||
ExtraMatchers<T, APIResponse, APIResponseAssertions> &
|
||||
ExtraMatchers<T, Function, {
|
||||
/**
|
||||
* Retries the callback until it passes.
|
||||
*/
|
||||
toPass(options?: { timeout?: number, intervals?: number[] }): Promise<void>;
|
||||
}>;
|
||||
type BaseMatchers<R, T> = GenericAssertions<R> & PlaywrightTest.Matchers<R, T> & SnapshotAssertions;
|
||||
type AllowedGenericMatchers<R> = Pick<GenericAssertions<R>, 'toBe' | 'toBeDefined' | 'toBeFalsy' | 'toBeNull' | 'toBeTruthy' | 'toBeUndefined'>;
|
||||
|
||||
type SpecificMatchers<R, T> =
|
||||
T extends Page ? PageAssertions & AllowedGenericMatchers<R> :
|
||||
T extends Locator ? LocatorAssertions & AllowedGenericMatchers<R> :
|
||||
T extends APIResponse ? APIResponseAssertions & AllowedGenericMatchers<R> :
|
||||
BaseMatchers<R, T> & (T extends Function ? FunctionAssertions : {});
|
||||
type AllMatchers<R, T> = PageAssertions & LocatorAssertions & APIResponseAssertions & FunctionAssertions & BaseMatchers<R, T>;
|
||||
|
||||
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> = {
|
||||
/**
|
||||
* If you know how to test something, `.not` lets you test its opposite.
|
||||
*/
|
||||
not: MakeMatchers<R, T>;
|
||||
/**
|
||||
* 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>>;
|
||||
/**
|
||||
* 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>>;
|
||||
|
||||
export type Expect = {
|
||||
<T = unknown>(actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers<void, T>;
|
||||
|
@ -377,8 +382,6 @@ export type Expect = {
|
|||
not: Omit<AsymmetricMatchers, 'any' | 'anything'>;
|
||||
} & AsymmetricMatchers;
|
||||
|
||||
type Awaited<T> = T extends PromiseLike<infer U> ? U : T;
|
||||
|
||||
// --- BEGINGLOBAL ---
|
||||
declare global {
|
||||
export namespace PlaywrightTest {
|
||||
|
|
Загрузка…
Ссылка в новой задаче