From c1447eb8c13529f549cbd4d587f746abc9e92962 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Wed, 27 Nov 2024 15:22:51 +0000 Subject: [PATCH] fix(types): update types for test.extend - Replace `KeyValue` with `{}` to avoid inferring `KeyValue` when not intended and turning all properties into `any`. - Employ `as Exclude` technique to make sure overridden fixtures inherit from the base test type, and are not looked up in the extended fixtures list where they are not present. This should fix errors with TypeScript 5.8. --- packages/playwright/types/test.d.ts | 19 +++--- .../ct-react17/tests/render.spec.tsx | 1 + tests/playwright-test/types.spec.ts | 65 +++++++++++++++++++ utils/generate_types/overrides-test.d.ts | 19 +++--- 4 files changed, 84 insertions(+), 20 deletions(-) diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 8d05bdafef..4f396a96a9 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -1891,7 +1891,7 @@ type ConditionBody = (args: TestArgs) => boolean; * ``` * */ -export interface TestType { +export interface TestType { /** * Declares a test. * - `test(title, body)` @@ -5632,7 +5632,7 @@ export interface TestType(fixtures: Fixtures): TestType; + extend(fixtures: Fixtures): TestType; /** * Returns information about the currently running test. This method can only be called during the test execution, * otherwise it throws. @@ -5653,19 +5653,18 @@ export interface TestType = (args: Args, use: (r: R) => Promise, testInfo: TestInfo) => any; -export type WorkerFixture = (args: Args, use: (r: R) => Promise, workerInfo: WorkerInfo) => any; -type TestFixtureValue = Exclude | TestFixture; -type WorkerFixtureValue = Exclude | WorkerFixture; -export type Fixtures = { +export type TestFixture = (args: Args, use: (r: R) => Promise, testInfo: TestInfo) => any; +export type WorkerFixture = (args: Args, use: (r: R) => Promise, workerInfo: WorkerInfo) => any; +type TestFixtureValue = Exclude | TestFixture; +type WorkerFixtureValue = Exclude | WorkerFixture; +export type Fixtures = { [K in keyof PW]?: WorkerFixtureValue | [WorkerFixtureValue, { scope: 'worker', timeout?: number | undefined, title?: string, box?: boolean }]; } & { [K in keyof PT]?: TestFixtureValue | [TestFixtureValue, { scope: 'test', timeout?: number | undefined, title?: string, box?: boolean }]; } & { - [K in keyof W]?: [WorkerFixtureValue, { scope: 'worker', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean }]; + [K in Exclude]?: [WorkerFixtureValue, { scope: 'worker', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean }]; } & { - [K in keyof T]?: TestFixtureValue | [TestFixtureValue, { scope?: 'test', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean }]; + [K in Exclude]?: TestFixtureValue | [TestFixtureValue, { scope?: 'test', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean }]; }; type BrowserName = 'chromium' | 'firefox' | 'webkit'; diff --git a/tests/components/ct-react17/tests/render.spec.tsx b/tests/components/ct-react17/tests/render.spec.tsx index ec9405c82f..46e3ea9abf 100644 --- a/tests/components/ct-react17/tests/render.spec.tsx +++ b/tests/components/ct-react17/tests/render.spec.tsx @@ -31,6 +31,7 @@ test('render an empty component', async ({ mount, page }) => { const testWithServer = test.extend(serverFixtures); testWithServer( 'components routing should go through context', + // @ts-ignore "serverFixtures" are imported from the impl without any types async ({ mount, context, server }) => { server.setRoute('/hello', (req: any, res: any) => { res.write('served via server'); diff --git a/tests/playwright-test/types.spec.ts b/tests/playwright-test/types.spec.ts index c34c586f7a..e9fea813f0 100644 --- a/tests/playwright-test/types.spec.ts +++ b/tests/playwright-test/types.spec.ts @@ -115,6 +115,71 @@ test('should check types of fixtures', async ({ runTSC }) => { await use(x); }, }); + + base.extend({ + page: async ({ page }) => { + type IsPage = (typeof page) extends Page ? true : never; + const isPage: IsPage = true; + }, + }); + + base.extend<{ myFixture: (arg: number) => void }>({ + page: async ({ page }) => { + type IsPage = (typeof page) extends Page ? true : never; + const isPage: IsPage = true; + }, + }); + + base.extend({ + // @ts-expect-error + myFixture: async ({ page }) => { + type IsPage = (typeof page) extends Page ? true : never; + const isPage: IsPage = true; + } + }); + + base.extend<{ myFixture: (arg: number) => void }>({ + myFixture: async ({ page }) => { + type IsPage = (typeof page) extends Page ? true : never; + const isPage: IsPage = true; + } + }); + + base.extend({ + page: async ({ page }) => { + type IsPage = (typeof page) extends Page ? true : never; + const isPage: IsPage = true; + }, + // @ts-expect-error + myFixture: async ({ page }) => { + type IsPage = (typeof page) extends Page ? true : never; + const isPage: IsPage = true; + } + }); + + base.extend<{ myFixture: (arg: number) => void }>({ + page: async ({ page }) => { + type IsPage = (typeof page) extends Page ? true : never; + const isPage: IsPage = true; + }, + myFixture: async ({ page }) => { + type IsPage = (typeof page) extends Page ? true : never; + const isPage: IsPage = true; + } + }); + + base.extend<{ myFixture: (arg: number) => void }>({ + // @ts-expect-error + myFixture: (arg: number) => {}, + }); + + base.extend<{ myFixture: (arg: number) => void }>({ + myFixture: async (_, use) => { + use((arg: number) => {}); + // @ts-expect-error + use((arg: string) => {}); + } + }); `, 'playwright.config.ts': ` import { MyOptions } from './helper'; diff --git a/utils/generate_types/overrides-test.d.ts b/utils/generate_types/overrides-test.d.ts index 5775917382..78df18f014 100644 --- a/utils/generate_types/overrides-test.d.ts +++ b/utils/generate_types/overrides-test.d.ts @@ -78,7 +78,7 @@ export type TestDetails = { type TestBody = (args: TestArgs, testInfo: TestInfo) => Promise | void; type ConditionBody = (args: TestArgs) => boolean; -export interface TestType { +export interface TestType { (title: string, body: TestBody): void; (title: string, details: TestDetails, body: TestBody): void; @@ -164,23 +164,22 @@ export interface TestType): void; step(title: string, body: () => T | Promise, options?: { box?: boolean, location?: Location, timeout?: number }): Promise; expect: Expect<{}>; - extend(fixtures: Fixtures): TestType; + extend(fixtures: Fixtures): TestType; info(): TestInfo; } -type KeyValue = { [key: string]: any }; -export type TestFixture = (args: Args, use: (r: R) => Promise, testInfo: TestInfo) => any; -export type WorkerFixture = (args: Args, use: (r: R) => Promise, workerInfo: WorkerInfo) => any; -type TestFixtureValue = Exclude | TestFixture; -type WorkerFixtureValue = Exclude | WorkerFixture; -export type Fixtures = { +export type TestFixture = (args: Args, use: (r: R) => Promise, testInfo: TestInfo) => any; +export type WorkerFixture = (args: Args, use: (r: R) => Promise, workerInfo: WorkerInfo) => any; +type TestFixtureValue = Exclude | TestFixture; +type WorkerFixtureValue = Exclude | WorkerFixture; +export type Fixtures = { [K in keyof PW]?: WorkerFixtureValue | [WorkerFixtureValue, { scope: 'worker', timeout?: number | undefined, title?: string, box?: boolean }]; } & { [K in keyof PT]?: TestFixtureValue | [TestFixtureValue, { scope: 'test', timeout?: number | undefined, title?: string, box?: boolean }]; } & { - [K in keyof W]?: [WorkerFixtureValue, { scope: 'worker', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean }]; + [K in Exclude]?: [WorkerFixtureValue, { scope: 'worker', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean }]; } & { - [K in keyof T]?: TestFixtureValue | [TestFixtureValue, { scope?: 'test', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean }]; + [K in Exclude]?: TestFixtureValue | [TestFixtureValue, { scope?: 'test', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean }]; }; type BrowserName = 'chromium' | 'firefox' | 'webkit';