From 56e57f2c91ca6aade9fe1fe48ca92445d4b614e8 Mon Sep 17 00:00:00 2001 From: soreavis <263610811+soreavis@users.noreply.github.com> Date: Mon, 20 Jul 2026 22:20:30 +0200 Subject: [PATCH 1/3] docs(cdp): fix CDPSession event example to use method (#41866) The Usage example for `CDPSession.event` destructures `name`, but #39570 renamed the payload field to `method`, so the example as written logs `CDP event: undefined`. --- docs/src/api/class-cdpsession.md | 4 ++-- packages/playwright-client/types/types.d.ts | 12 ++++++------ packages/playwright-core/types/types.d.ts | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/src/api/class-cdpsession.md b/docs/src/api/class-cdpsession.md index d14b51a86299e..1d581ffe1a6df 100644 --- a/docs/src/api/class-cdpsession.md +++ b/docs/src/api/class-cdpsession.md @@ -86,8 +86,8 @@ their names ahead of time. **Usage** ```js -session.on('event', ({ name, params }) => { - console.log(`CDP event: ${name}`, params); +session.on('event', ({ method, params }) => { + console.log(`CDP event: ${method}`, params); }); ``` diff --git a/packages/playwright-client/types/types.d.ts b/packages/playwright-client/types/types.d.ts index 0b3086a126811..0b3c58906b5b1 100644 --- a/packages/playwright-client/types/types.d.ts +++ b/packages/playwright-client/types/types.d.ts @@ -17750,8 +17750,8 @@ export interface CDPSession { * **Usage** * * ```js - * session.on('event', ({ name, params }) => { - * console.log(`CDP event: ${name}`, params); + * session.on('event', ({ method, params }) => { + * console.log(`CDP event: ${method}`, params); * }); * ``` * @@ -17800,8 +17800,8 @@ export interface CDPSession { * **Usage** * * ```js - * session.on('event', ({ name, params }) => { - * console.log(`CDP event: ${name}`, params); + * session.on('event', ({ method, params }) => { + * console.log(`CDP event: ${method}`, params); * }); * ``` * @@ -17870,8 +17870,8 @@ export interface CDPSession { * **Usage** * * ```js - * session.on('event', ({ name, params }) => { - * console.log(`CDP event: ${name}`, params); + * session.on('event', ({ method, params }) => { + * console.log(`CDP event: ${method}`, params); * }); * ``` * diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 0b3086a126811..0b3c58906b5b1 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -17750,8 +17750,8 @@ export interface CDPSession { * **Usage** * * ```js - * session.on('event', ({ name, params }) => { - * console.log(`CDP event: ${name}`, params); + * session.on('event', ({ method, params }) => { + * console.log(`CDP event: ${method}`, params); * }); * ``` * @@ -17800,8 +17800,8 @@ export interface CDPSession { * **Usage** * * ```js - * session.on('event', ({ name, params }) => { - * console.log(`CDP event: ${name}`, params); + * session.on('event', ({ method, params }) => { + * console.log(`CDP event: ${method}`, params); * }); * ``` * @@ -17870,8 +17870,8 @@ export interface CDPSession { * **Usage** * * ```js - * session.on('event', ({ name, params }) => { - * console.log(`CDP event: ${name}`, params); + * session.on('event', ({ method, params }) => { + * console.log(`CDP event: ${method}`, params); * }); * ``` * From 540ead4ecbde5806078c2837ecd2f259fcc9efeb Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 20 Jul 2026 23:00:01 +0100 Subject: [PATCH 2/3] chore: infer correct types for evalutes with exposeFunctions (#41876) --- packages/playwright-core/types/structs.d.ts | 4 ++- tests/page/page-evaluate-callback.spec.ts | 24 ++++++++++++++++-- utils/generate_types/test/test.ts | 28 +++++++++++++++++++++ 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/packages/playwright-core/types/structs.d.ts b/packages/playwright-core/types/structs.d.ts index 9c621cff35fb8..11f87a2ccebf4 100644 --- a/packages/playwright-core/types/structs.d.ts +++ b/packages/playwright-core/types/structs.d.ts @@ -25,10 +25,12 @@ export type Serializable = any; */ export type EvaluationArgument = {}; -export type NoHandles = Arg extends JSHandle ? never : (Arg extends object ? { [Key in keyof Arg]: NoHandles } : Arg); +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 Unboxed = Arg extends ElementHandle ? T : Arg extends JSHandle ? T : + Arg extends (...args: infer T) => infer R ? (...args: T) => CallbackResult : Arg extends NoHandles ? Arg : Arg extends [infer A0] ? [Unboxed] : Arg extends [infer A0, infer A1] ? [Unboxed, Unboxed] : diff --git a/tests/page/page-evaluate-callback.spec.ts b/tests/page/page-evaluate-callback.spec.ts index 6b58dfbc508ee..d7157f62cd1e9 100644 --- a/tests/page/page-evaluate-callback.spec.ts +++ b/tests/page/page-evaluate-callback.spec.ts @@ -18,7 +18,7 @@ import { attachFrame } from '../config/utils'; import { test as it, expect } from './pageTest'; it('should throw without the exposeFunctions option', async ({ page }) => { - await expect(page.evaluate(({ cb }) => (cb as any)(), { cb: () => {} })) + await expect(page.evaluate(({ cb }) => cb(), { cb: () => {} })) .rejects.toThrow(/Attempting to serialize unexpected value at position "cb": \(\) => {}/); }); @@ -27,7 +27,7 @@ it('should call a function passed as an argument', async ({ page }) => { await page.evaluate(async ({ cb }) => { await cb(1); await cb(2); - }, { cb: async (n: number) => { received.push(n); } }, { exposeFunctions: true }); + }, { cb: (n: number) => { received.push(n); } }, { exposeFunctions: true }); expect(received).toEqual([1, 2]); }); @@ -56,6 +56,26 @@ it('should return the callback result to the page', async ({ page }) => { expect(doubled).toBe(42); }); +it('should support handle as a callback result', async ({ page }) => { + const result = await page.evaluate(async cb => { + const value = await cb(42); + return value + 17; + }, (n: number) => page.evaluateHandle(x => 2 * x, n), { exposeFunctions: true }); + expect(result).toBe(101); +}); + +it('should support nested handles in the callback result', async ({ page }) => { + const result = await page.evaluate(async cb => { + const res = await cb(42); + return res.mul[0] + res.mul[1] + res.add; + }, async (n: number) => { + const double = await page.evaluateHandle(x => 2 * x, n); + const triple = await page.evaluateHandle(x => 3 * x, n); + return { mul: [double, triple] as const, add: 17 }; + }, { exposeFunctions: true }); + expect(result).toBe(227); +}); + it('should await an async callback result', async ({ page }) => { const value = await page.evaluate(async ({ cb }) => await cb(20), { cb: async (n: number) => { await new Promise(f => setTimeout(f, 10)); return n + 1; }, diff --git a/utils/generate_types/test/test.ts b/utils/generate_types/test/test.ts index ff7b28a5200a2..3e4d407f1c0c7 100644 --- a/utils/generate_types/test/test.ts +++ b/utils/generate_types/test/test.ts @@ -428,6 +428,34 @@ playwright.chromium.launch().then(async browser => { { await locator.evaluateAll((sel: HTMLSelectElement[]) => {}) } + { + await locator.evaluate((e, cb) => { + const value = cb(2); + const assertion: AssertType, typeof value> = true; + }, (x: number) => 2 * x); + } + { + await locator.evaluate((e, cb) => { + const value = cb(2); + const assertion: AssertType, typeof value> = true; + }, async (x: number) => 2 * x); + } + { + await locator.evaluate((e, { cb }) => { + const value = cb(2); + const assertion: AssertType, typeof value> = true; + }, { cb: (x: number) => page.evaluateHandle(y => 2 * y, x) }); + } + { + const func = async (x: number) => { + const double = await page.evaluateHandle(y => 2 * y, x); + return { double, add: 17 }; + }; + await locator.evaluate((e, { cb }) => { + const value = cb(2); + const assertion: AssertType, typeof value> = true; + }, { cb: func }); + } await browser.close(); })(); From 8cd58bad73241e15c6e5e435614520afe785ede8 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Mon, 20 Jul 2026 15:42:11 -0700 Subject: [PATCH 3/3] fix(firefox): do not override Accept-Language header set by the page (#41886) --- .../src/server/firefox/ffBrowser.ts | 5 +-- tests/library/browsercontext-locale.spec.ts | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/packages/playwright-core/src/server/firefox/ffBrowser.ts b/packages/playwright-core/src/server/firefox/ffBrowser.ts index 120e448cd419f..73230d70eee20 100644 --- a/packages/playwright-core/src/server/firefox/ffBrowser.ts +++ b/packages/playwright-core/src/server/firefox/ffBrowser.ts @@ -305,10 +305,7 @@ export class FFBrowserContext extends BrowserContext { } async doUpdateExtraHTTPHeaders(): Promise { - let allHeaders = this._options.extraHTTPHeaders || []; - if (this._options.locale) - allHeaders = network.mergeHeaders([allHeaders, network.singleHeader('Accept-Language', this._options.locale)]); - await this._browser.session.send('Browser.setExtraHTTPHeaders', { browserContextId: this._browserContextId, headers: allHeaders }); + await this._browser.session.send('Browser.setExtraHTTPHeaders', { browserContextId: this._browserContextId, headers: this._options.extraHTTPHeaders || [] }); } async setUserAgent(userAgent: string | undefined): Promise { diff --git a/tests/library/browsercontext-locale.spec.ts b/tests/library/browsercontext-locale.spec.ts index 6bfb5aff80bd1..6272f71ed4c5e 100644 --- a/tests/library/browsercontext-locale.spec.ts +++ b/tests/library/browsercontext-locale.spec.ts @@ -189,3 +189,38 @@ it('should affect Intl.DateTimeFormat().resolvedOptions().locale', async ({ brow expect(await page.evaluate(() => (new Intl.DateTimeFormat()).resolvedOptions().locale)).toBe('en-GB'); await context.close(); }); + +it('should send user Accept-Language header', { + annotation: [{ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/23732' }], +}, async ({ browser, server, browserName }) => { + it.fixme(browserName === 'webkit', 'Implementation set Accept-Language header as extra HTTP header'); + const context = await browser.newContext({ locale: 'en-GB' }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + { + const reqPromise = server.waitForRequest('/empty.html'); + await page.evaluate(async url => { + await fetch(url, { + headers: { + 'Content-Type': 'application/json', + 'Accept-Language': 'de' + }, + }); + }, server.EMPTY_PAGE); + const req = await reqPromise; + expect(req.headers['accept-language']).toBe('de'); + } + { + const reqPromise = server.waitForRequest('/empty.html'); + await page.evaluate(async url => { + await fetch(url, { + headers: { + 'Content-Type': 'application/json' + }, + }); + }, server.EMPTY_PAGE); + const req = await reqPromise; + expect(req.headers['accept-language']).toContain('en-GB'); + } + await context.close(); +});