diff --git a/packages/playwright-core/types/structs.d.ts b/packages/playwright-core/types/structs.d.ts index 11f87a2ccebf4..3b671e99ac17b 100644 --- a/packages/playwright-core/types/structs.d.ts +++ b/packages/playwright-core/types/structs.d.ts @@ -25,12 +25,17 @@ export type Serializable = any; */ export type EvaluationArgument = {}; -type CallbackResult = T extends PromiseLike ? Promise> : Promise>; -export type NoHandles = Arg extends JSHandle ? never : (Arg extends Function ? never : (Arg extends object ? { [Key in keyof Arg]: NoHandles } : Arg)); +export type NoHandles = + Arg extends JSHandle ? never : + Arg extends (...args: infer T) => PromiseLike ? (...args: T) => Promise> : + Arg extends (...args: infer T) => infer R ? (...args: T) => NoHandles : + Arg extends object ? { [Key in keyof Arg]: NoHandles } : + Arg; export type Unboxed = Arg extends ElementHandle ? T : Arg extends JSHandle ? T : - Arg extends (...args: infer T) => infer R ? (...args: T) => CallbackResult : + Arg extends (...args: infer T) => PromiseLike ? (...args: T) => Promise> : + Arg extends (...args: infer T) => infer R ? (...args: T) => Unboxed : Arg extends NoHandles ? Arg : Arg extends [infer A0] ? [Unboxed] : Arg extends [infer A0, infer A1] ? [Unboxed, Unboxed] : diff --git a/utils/generate_types/test/test.ts b/utils/generate_types/test/test.ts index 3e4d407f1c0c7..88d4d00665ea8 100644 --- a/utils/generate_types/test/test.ts +++ b/utils/generate_types/test/test.ts @@ -428,10 +428,12 @@ playwright.chromium.launch().then(async browser => { { await locator.evaluateAll((sel: HTMLSelectElement[]) => {}) } + // Handles in callback results are unboxed, but the callback keeps its authored + // sync/async signature, even though the runtime always exposes it as async on the page side. { await locator.evaluate((e, cb) => { const value = cb(2); - const assertion: AssertType, typeof value> = true; + const assertion: AssertType = true; }, (x: number) => 2 * x); } { @@ -456,6 +458,54 @@ playwright.chromium.launch().then(async browser => { const assertion: AssertType, typeof value> = true; }, { cb: func }); } + { + // Promises nested in the argument are not awaited, only callback results are. + const result = await page.evaluate(arg => { + const assertion: AssertType, typeof arg.a> = true; + return arg; + }, { a: new Promise(() => {}), b: 42 }); + const assertion: AssertType<{ a: Promise, b: number }, typeof result> = true; + } + await browser.close(); +})(); + +// branded primitives in evaluate arguments — https://github.com/microsoft/playwright/issues/42000 +declare const __brand: unique symbol; +type Branded = T & { [__brand]: B }; +type IsoDate = Branded; +declare function takesIsoDate(date: IsoDate): void; + +(async () => { + const browser = await playwright.chromium.launch(); + const page = await browser.newPage(); + const date = '2026-01-15' as IsoDate; + { + const result = await page.evaluate((d: IsoDate) => d, date); + const assertion: AssertType = true; + } + { + await page.evaluate(d => takesIsoDate(d), date); + } + { + await page.evaluate(({ d }) => takesIsoDate(d), { d: date }); + } + { + const count = 42 as Branded; + const result = await page.evaluate(c => c, count); + const assertion: AssertType, typeof result> = true; + } + { + const result = await page.evaluate((arg: { d?: IsoDate }) => arg.d, { d: date } as { d?: IsoDate }); + const assertion: AssertType = true; + } + { + const result = await page.evaluate((dates: readonly IsoDate[]) => dates[0], [date] as readonly IsoDate[]); + const assertion: AssertType = true; + } + { + const result = await page.evaluate((pair: [IsoDate, number]) => pair[0], [date, 42] as [IsoDate, number]); + const assertion: AssertType = true; + } await browser.close(); })();