From 32e928b18f69e4cd7bf6fbfb2dbd2b10f57178a4 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 2 Oct 2025 16:03:17 -0700 Subject: [PATCH 1/3] chore(bidi): use firefox browser type with moz-firefox- channel --- packages/playwright-client/types/types.d.ts | 1 - .../playwright-core/src/browserServerImpl.ts | 4 ++-- .../playwright-core/src/client/playwright.ts | 5 +---- .../playwright-core/src/inProcessFactory.ts | 1 - .../playwright-core/src/protocol/validator.ts | 1 - .../src/server/bidi/bidiFirefox.ts | 2 +- .../playwright-core/src/server/browserType.ts | 6 ++--- .../dispatchers/playwrightDispatcher.ts | 2 -- .../src/server/firefox/firefox.ts | 22 +++++++++++++++++-- .../playwright-core/src/server/playwright.ts | 4 +--- .../src/server/registry/index.ts | 8 +++---- packages/playwright-core/types/types.d.ts | 1 - packages/playwright/src/index.ts | 4 ++-- packages/protocol/src/channels.d.ts | 1 - packages/protocol/src/protocol.yml | 1 - tests/bidi/playwright.config.ts | 6 ++--- tests/config/browserTest.ts | 6 ++--- tests/config/utils.ts | 2 +- .../browsercontext-network-event.spec.ts | 4 ++-- .../browsercontext-timezone-id.spec.ts | 6 ++--- tests/library/favicon.spec.ts | 4 ++-- tests/library/har.spec.ts | 4 ++-- tests/library/page-close.spec.ts | 4 ++-- tests/page/page-basic.spec.ts | 4 ++-- tests/page/page-event-console.spec.ts | 4 ++-- tests/page/page-event-pageerror.spec.ts | 4 ++-- tests/page/page-goto.spec.ts | 4 ++-- tests/page/page-route.spec.ts | 10 ++++----- tests/page/pageTestApi.ts | 2 +- utils/generate_types/overrides.d.ts | 1 - 30 files changed, 65 insertions(+), 63 deletions(-) diff --git a/packages/playwright-client/types/types.d.ts b/packages/playwright-client/types/types.d.ts index d4e4f52c74f85..86af0171bf007 100644 --- a/packages/playwright-client/types/types.d.ts +++ b/packages/playwright-client/types/types.d.ts @@ -16291,7 +16291,6 @@ export type AndroidKey = export const _electron: Electron; export const _android: Android; export const _bidiChromium: BrowserType; -export const _bidiFirefox: BrowserType; // This is required to not export everything by default. See https://github.com/Microsoft/TypeScript/issues/19545#issuecomment-340490459 export {}; diff --git a/packages/playwright-core/src/browserServerImpl.ts b/packages/playwright-core/src/browserServerImpl.ts index 0057d26ac94bf..d0e3229d782e7 100644 --- a/packages/playwright-core/src/browserServerImpl.ts +++ b/packages/playwright-core/src/browserServerImpl.ts @@ -32,9 +32,9 @@ import type { WebSocketEventEmitter } from './utilsBundle'; import type { Browser } from './server/browser'; export class BrowserServerLauncherImpl implements BrowserServerLauncher { - private _browserName: 'chromium' | 'firefox' | 'webkit' | '_bidiFirefox' | '_bidiChromium'; + private _browserName: 'chromium' | 'firefox' | 'webkit' | '_bidiChromium'; - constructor(browserName: 'chromium' | 'firefox' | 'webkit' | '_bidiFirefox' | '_bidiChromium') { + constructor(browserName: 'chromium' | 'firefox' | 'webkit' | '_bidiChromium') { this._browserName = browserName; } diff --git a/packages/playwright-core/src/client/playwright.ts b/packages/playwright-core/src/client/playwright.ts index 48c6349080f15..5b0ed7dcc246d 100644 --- a/packages/playwright-core/src/client/playwright.ts +++ b/packages/playwright-core/src/client/playwright.ts @@ -30,7 +30,6 @@ export class Playwright extends ChannelOwner { readonly _android: Android; readonly _electron: Electron; readonly _bidiChromium: BrowserType; - readonly _bidiFirefox: BrowserType; readonly chromium: BrowserType; readonly firefox: BrowserType; readonly webkit: BrowserType; @@ -60,8 +59,6 @@ export class Playwright extends ChannelOwner { this._electron._playwright = this; this._bidiChromium = BrowserType.from(initializer._bidiChromium); this._bidiChromium._playwright = this; - this._bidiFirefox = BrowserType.from(initializer._bidiFirefox); - this._bidiFirefox._playwright = this; this.devices = this._connection.localUtils()?.devices ?? {}; this.selectors = new Selectors(this._connection._platform); this.errors = { TimeoutError }; @@ -72,7 +69,7 @@ export class Playwright extends ChannelOwner { } private _browserTypes(): BrowserType[] { - return [this.chromium, this.firefox, this.webkit, this._bidiChromium, this._bidiFirefox]; + return [this.chromium, this.firefox, this.webkit, this._bidiChromium]; } _preLaunchedBrowser(): Browser { diff --git a/packages/playwright-core/src/inProcessFactory.ts b/packages/playwright-core/src/inProcessFactory.ts index 8a77643c8e8ac..3c621133e7946 100644 --- a/packages/playwright-core/src/inProcessFactory.ts +++ b/packages/playwright-core/src/inProcessFactory.ts @@ -43,7 +43,6 @@ export function createInProcessPlaywright(): PlaywrightAPI { playwrightAPI.webkit._serverLauncher = new BrowserServerLauncherImpl('webkit'); playwrightAPI._android._serverLauncher = new AndroidServerLauncherImpl(); playwrightAPI._bidiChromium._serverLauncher = new BrowserServerLauncherImpl('_bidiChromium'); - playwrightAPI._bidiFirefox._serverLauncher = new BrowserServerLauncherImpl('_bidiFirefox'); // Switch to async dispatch after we got Playwright object. dispatcherConnection.onmessage = message => setImmediate(() => clientConnection.dispatch(message)); diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 3913c0545ae97..d9d3153dcda89 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -378,7 +378,6 @@ scheme.PlaywrightInitializer = tObject({ firefox: tChannel(['BrowserType']), webkit: tChannel(['BrowserType']), _bidiChromium: tChannel(['BrowserType']), - _bidiFirefox: tChannel(['BrowserType']), android: tChannel(['Android']), electron: tChannel(['Electron']), utils: tOptional(tChannel(['LocalUtils'])), diff --git a/packages/playwright-core/src/server/bidi/bidiFirefox.ts b/packages/playwright-core/src/server/bidi/bidiFirefox.ts index 9201a5ffc56af..09406299cb6b2 100644 --- a/packages/playwright-core/src/server/bidi/bidiFirefox.ts +++ b/packages/playwright-core/src/server/bidi/bidiFirefox.ts @@ -34,7 +34,7 @@ import type { RecentLogsCollector } from '../utils/debugLogger'; export class BidiFirefox extends BrowserType { constructor(parent: SdkObject) { - super(parent, '_bidiFirefox'); + super(parent, 'firefox'); } override executablePath(): string { diff --git a/packages/playwright-core/src/server/browserType.ts b/packages/playwright-core/src/server/browserType.ts index f8b86e40ca6c3..e370491788b1b 100644 --- a/packages/playwright-core/src/server/browserType.ts +++ b/packages/playwright-core/src/server/browserType.ts @@ -92,7 +92,7 @@ export abstract class BrowserType extends SdkObject { } } - async _innerLaunchWithRetries(progress: Progress, options: types.LaunchOptions, persistent: types.BrowserContextOptions | undefined, protocolLogger: types.ProtocolLogger, userDataDir?: string): Promise { + private async _innerLaunchWithRetries(progress: Progress, options: types.LaunchOptions, persistent: types.BrowserContextOptions | undefined, protocolLogger: types.ProtocolLogger, userDataDir?: string): Promise { try { return await this._innerLaunch(progress, options, persistent, protocolLogger, userDataDir); } catch (error) { @@ -106,7 +106,7 @@ export abstract class BrowserType extends SdkObject { } } - async _innerLaunch(progress: Progress, options: types.LaunchOptions, persistent: types.BrowserContextOptions | undefined, protocolLogger: types.ProtocolLogger, maybeUserDataDir?: string): Promise { + private async _innerLaunch(progress: Progress, options: types.LaunchOptions, persistent: types.BrowserContextOptions | undefined, protocolLogger: types.ProtocolLogger, maybeUserDataDir?: string): Promise { options.proxy = options.proxy ? normalizeProxySettings(options.proxy) : undefined; const browserLogsCollector = new RecentLogsCollector(); const { browserProcess, userDataDir, artifactsDir, transport } = await this._launchProcess(progress, options, !!persistent, browserLogsCollector, maybeUserDataDir); @@ -317,7 +317,7 @@ export abstract class BrowserType extends SdkObject { } } - _rewriteStartupLog(error: Error): Error { + private _rewriteStartupLog(error: Error): Error { if (!isProtocolError(error)) return error; return this.doRewriteStartupLog(error); diff --git a/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts b/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts index d26042339bbbf..32d35913358c9 100644 --- a/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts @@ -54,14 +54,12 @@ export class PlaywrightDispatcher extends Dispatcher { + if (options.channel && options.channel.startsWith('moz-')) + return this._bidiFirefox.launch(progress, options, protocolLogger); + return super.launch(progress, options, protocolLogger); + } + + override async launchPersistentContext(progress: Progress, userDataDir: string, options: channels.BrowserTypeLaunchPersistentContextOptions & { cdpPort?: number, internalIgnoreHTTPSErrors?: boolean, socksProxyPort?: number }): Promise { + if (options.channel?.startsWith('moz-')) + return this._bidiFirefox.launchPersistentContext(progress, userDataDir, options); + return super.launchPersistentContext(progress, userDataDir, options); } override connectToTransport(transport: ConnectionTransport, options: BrowserOptions): Promise { diff --git a/packages/playwright-core/src/server/playwright.ts b/packages/playwright-core/src/server/playwright.ts index bdd98bd8af02c..c8d33d47818c5 100644 --- a/packages/playwright-core/src/server/playwright.ts +++ b/packages/playwright-core/src/server/playwright.ts @@ -43,7 +43,6 @@ export class Playwright extends SdkObject { readonly firefox: BrowserType; readonly webkit: BrowserType; readonly _bidiChromium: BrowserType; - readonly _bidiFirefox: BrowserType; readonly options: PlaywrightOptions; readonly debugController: DebugController; private _allPages = new Set(); @@ -61,8 +60,7 @@ export class Playwright extends SdkObject { }, null); this.chromium = new Chromium(this); this._bidiChromium = new BidiChromium(this); - this._bidiFirefox = new BidiFirefox(this); - this.firefox = new Firefox(this); + this.firefox = new Firefox(this, new BidiFirefox(this)); this.webkit = new WebKit(this); this.electron = new Electron(this); this.android = new Android(this, new AdbBackend()); diff --git a/packages/playwright-core/src/server/registry/index.ts b/packages/playwright-core/src/server/registry/index.ts index 700cfdd9b1fa3..25f3ecffd4765 100644 --- a/packages/playwright-core/src/server/registry/index.ts +++ b/packages/playwright-core/src/server/registry/index.ts @@ -404,8 +404,6 @@ const DOWNLOAD_PATHS: Record = { 'win64': 'builds/android/%s/android.zip', }, // TODO(bidi): implement downloads. - '_bidiFirefox': { - } as DownloadPaths, '_bidiChromium': { } as DownloadPaths, }; @@ -502,7 +500,7 @@ function readDescriptors(browsersJSON: BrowsersJSON): BrowsersJSONDescriptor[] { }); } -export type BrowserName = 'chromium' | 'firefox' | 'webkit' | '_bidiFirefox' | '_bidiChromium'; +export type BrowserName = 'chromium' | 'firefox' | 'webkit' | '_bidiChromium'; type InternalTool = 'ffmpeg' | 'winldd' | 'firefox-beta' | 'chromium-tip-of-tree' | 'chromium-headless-shell' | 'chromium-tip-of-tree-headless-shell' | 'android'; type BidiChannel = 'moz-firefox' | 'moz-firefox-beta' | 'moz-firefox-nightly' | 'bidi-chrome-canary' | 'bidi-chrome-stable' | 'bidi-chromium'; type ChromiumChannel = 'chrome' | 'chrome-beta' | 'chrome-dev' | 'chrome-canary' | 'msedge' | 'msedge-beta' | 'msedge-dev' | 'msedge-canary'; @@ -958,13 +956,13 @@ export class Registry { return executablePath; } if (shouldThrow) - throw new Error(`Cannot find Firefox installation for channel '${name}' at the standard system paths.`); + throw new Error(`Cannot find Firefox installation for channel '${name}' at the standard system paths. ${`Tried paths:\n ${prefixes.map(p => path.join(p, suffix)).join('\n ')}`}`); return undefined; }; return { type: 'channel', name, - browserName: '_bidiFirefox', + browserName: 'firefox', directory: undefined, executablePath: (sdkLanguage: string) => executablePath(sdkLanguage, false), executablePathOrDie: (sdkLanguage: string) => executablePath(sdkLanguage, true)!, diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index d4e4f52c74f85..86af0171bf007 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -16291,7 +16291,6 @@ export type AndroidKey = export const _electron: Electron; export const _android: Android; export const _bidiChromium: BrowserType; -export const _bidiFirefox: BrowserType; // This is required to not export everything by default. See https://github.com/Microsoft/TypeScript/issues/19545#issuecomment-340490459 export {}; diff --git a/packages/playwright/src/index.ts b/packages/playwright/src/index.ts index 6c412c0d98bbd..c9ffdd81773a1 100644 --- a/packages/playwright/src/index.ts +++ b/packages/playwright/src/index.ts @@ -100,8 +100,8 @@ const playwrightFixtures: Fixtures = ({ playwright._defaultLaunchOptions = undefined; }, { scope: 'worker', auto: true, box: true }], - browser: [async ({ playwright, browserName, _browserOptions, connectOptions }, use, testInfo) => { - if (!['chromium', 'firefox', 'webkit', '_bidiChromium', '_bidiFirefox'].includes(browserName)) + browser: [async ({ playwright, browserName, _browserOptions, connectOptions }, use) => { + if (!['chromium', 'firefox', 'webkit', '_bidiChromium'].includes(browserName)) throw new Error(`Unexpected browserName "${browserName}", must be one of "chromium", "firefox" or "webkit"`); if (connectOptions) { diff --git a/packages/protocol/src/channels.d.ts b/packages/protocol/src/channels.d.ts index a3bcb262fa6ea..f166090f90f8f 100644 --- a/packages/protocol/src/channels.d.ts +++ b/packages/protocol/src/channels.d.ts @@ -627,7 +627,6 @@ export type PlaywrightInitializer = { firefox: BrowserTypeChannel, webkit: BrowserTypeChannel, _bidiChromium: BrowserTypeChannel, - _bidiFirefox: BrowserTypeChannel, android: AndroidChannel, electron: ElectronChannel, utils?: LocalUtilsChannel, diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index d0b79ba72a219..9ee2ae7f86be9 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -795,7 +795,6 @@ Playwright: firefox: BrowserType webkit: BrowserType _bidiChromium: BrowserType - _bidiFirefox: BrowserType android: Android electron: Electron utils: LocalUtils? diff --git a/tests/bidi/playwright.config.ts b/tests/bidi/playwright.config.ts index e4dc010728d1b..0f489eeeabb17 100644 --- a/tests/bidi/playwright.config.ts +++ b/tests/bidi/playwright.config.ts @@ -71,18 +71,18 @@ const config: Config { if (browserName === '_bidiChromium') return process.env.BIDI_CRPATH; - if (browserName === '_bidiFirefox') + if (browserName === 'firefox') return process.env.BIDI_FFPATH; }; const browserToChannels = { '_bidiChromium': ['bidi-chromium', 'bidi-chrome-canary', 'bidi-chrome-stable'], - '_bidiFirefox': ['moz-firefox', 'moz-firefox-beta', 'moz-firefox-nightly'], + 'firefox': ['moz-firefox', 'moz-firefox-beta', 'moz-firefox-nightly'], }; for (const [key, channels] of Object.entries(browserToChannels)) { diff --git a/tests/config/browserTest.ts b/tests/config/browserTest.ts index 2872e37579e70..f2a10ab9dd9af 100644 --- a/tests/config/browserTest.ts +++ b/tests/config/browserTest.ts @@ -66,15 +66,15 @@ const test = baseTest.extend await run(playwright[browserName]); }, { scope: 'worker' }], - allowsThirdParty: [async ({ browserName }, run) => { - if (browserName === 'firefox' || browserName as any === '_bidiFirefox') + allowsThirdParty: [async ({ browserName, channel }, run) => { + if (browserName === 'firefox' || channel.startsWith('moz-firefox')) await run(true); else await run(false); }, { scope: 'worker' }], defaultSameSiteCookieValue: [async ({ browserName, platform, channel }, run) => { - if (browserName === 'chromium' || browserName as any === '_bidiChromium' || browserName as any === '_bidiFirefox') + if (browserName === 'chromium' || browserName as any === '_bidiChromium' || channel.startsWith('moz-firefox')) await run('Lax'); else if (browserName === 'webkit' && (platform === 'linux' || channel === 'webkit-wsl')) await run('Lax'); diff --git a/tests/config/utils.ts b/tests/config/utils.ts index 6fb3fc6a03e26..b2b47cb3e8170 100644 --- a/tests/config/utils.ts +++ b/tests/config/utils.ts @@ -64,7 +64,7 @@ export function expectedSSLError(browserName: string, platform: string, channel: else return /Unacceptable TLS certificate|Operation was cancelled/; } - if (browserName === '_bidiFirefox') + if (channel.startsWith('moz-firefox')) return /MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT/; return /SSL_ERROR_UNKNOWN/; } diff --git a/tests/library/browsercontext-network-event.spec.ts b/tests/library/browsercontext-network-event.spec.ts index 6eaf9e624b04a..74a773388f9b6 100644 --- a/tests/library/browsercontext-network-event.spec.ts +++ b/tests/library/browsercontext-network-event.spec.ts @@ -104,8 +104,8 @@ it('should fire events in proper order', async ({ context, server }) => { ]); }); -it('should not fire events for favicon or favicon redirects', async ({ context, page, server, browserName, headless }) => { - it.skip(headless && browserName !== 'firefox' && browserName as any !== '_bidiFirefox', 'headless browsers, except firefox, do not request favicons'); +it('should not fire events for favicon or favicon redirects', async ({ context, page, server, browserName, headless, channel }) => { + it.skip(headless && browserName !== 'firefox' && !channel.startsWith('moz-firefox'), 'headless browsers, except firefox, do not request favicons'); it.skip(!headless && browserName === 'webkit', 'headed webkit does not have a favicon feature'); const favicon = `/no-cache/favicon.ico`; const hashedFaviconUrl = `/favicon-hashed.ico`; diff --git a/tests/library/browsercontext-timezone-id.spec.ts b/tests/library/browsercontext-timezone-id.spec.ts index 91b274fc6c00d..786652416a7f9 100644 --- a/tests/library/browsercontext-timezone-id.spec.ts +++ b/tests/library/browsercontext-timezone-id.spec.ts @@ -45,13 +45,13 @@ it('should work @smoke', async ({ browser, browserName }) => { } }); -it('should throw for invalid timezone IDs when creating pages', async ({ browser, browserName }) => { +it('should throw for invalid timezone IDs when creating pages', async ({ browser, browserName, channel }) => { for (const timezoneId of ['Foo/Bar', 'Baz/Qux']) { - if (browserName as any === '_bidiChromium' || browserName as any === '_bidiFirefox') { + if (browserName as any === '_bidiChromium' || channel.startsWith('moz-firefox')) { const error = await browser.newContext({ timezoneId }).catch(e => e); if (browserName as any === '_bidiChromium') expect(error.message).toContain(`Invalid timezone "${timezoneId}"`); - else if (browserName as any === '_bidiFirefox') + else if (channel.startsWith('moz-firefox')) expect(error.message).toContain(`Expected "timezone" to be a valid timezone ID (e.g., "Europe/Berlin") or a valid timezone offset (e.g., "+01:00"), got ${timezoneId}`); } else { let error = null; diff --git a/tests/library/favicon.spec.ts b/tests/library/favicon.spec.ts index e8c5d56c76292..de13bdee6838c 100644 --- a/tests/library/favicon.spec.ts +++ b/tests/library/favicon.spec.ts @@ -17,8 +17,8 @@ import { contextTest as it } from '../config/browserTest'; -it('should load svg favicon with prefer-color-scheme', async ({ page, server, browserName, headless, asset }) => { - it.skip(headless && browserName !== 'firefox' && browserName as any !== '_bidiFirefox', 'headless browsers, except firefox, do not request favicons'); +it('should load svg favicon with prefer-color-scheme', async ({ page, server, browserName, headless, asset, channel }) => { + it.skip(headless && browserName !== 'firefox' && !channel.startsWith('moz-firefox'), 'headless browsers, except firefox, do not request favicons'); it.skip(!headless && browserName === 'webkit', 'headed webkit does not have a favicon feature'); // Browsers aggressively cache favicons, so force bust with the diff --git a/tests/library/har.spec.ts b/tests/library/har.spec.ts index d85ba463c82bf..b70f05a90e90e 100644 --- a/tests/library/har.spec.ts +++ b/tests/library/har.spec.ts @@ -698,8 +698,8 @@ it('should contain http2 for http2 requests', async ({ contextFactory }, testInf server.close(); }); -it('should filter favicon and favicon redirects', async ({ server, browserName, headless, asset, contextFactory }, testInfo) => { - it.skip(headless && browserName !== 'firefox' && browserName as any !== '_bidiFirefox', 'headless browsers, except firefox, do not request favicons'); +it('should filter favicon and favicon redirects', async ({ server, browserName, headless, asset, contextFactory, channel }, testInfo) => { + it.skip(headless && browserName !== 'firefox' && !channel.startsWith('moz-firefox'), 'headless browsers, except firefox, do not request favicons'); it.skip(!headless && browserName === 'webkit', 'headed webkit does not have a favicon feature'); const { page, getLog } = await pageWithHar(contextFactory, testInfo); diff --git a/tests/library/page-close.spec.ts b/tests/library/page-close.spec.ts index e06dfabe98aad..d0056e51ffa21 100644 --- a/tests/library/page-close.spec.ts +++ b/tests/library/page-close.spec.ts @@ -140,7 +140,7 @@ test('should not throw UnhandledPromiseRejection when page closes', async ({ pag ]).catch(e => {}); }); -test('interrupt request.response() and request.allHeaders() on page.close', async ({ page, server, browserName }) => { +test('interrupt request.response() and request.allHeaders() on page.close', async ({ page, server, browserName, channel }) => { test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/27227' }); server.setRoute('/one-style.css', (req, res) => { res.setHeader('Content-Type', 'text/css'); @@ -153,7 +153,7 @@ test('interrupt request.response() and request.allHeaders() on page.close', asyn await page.close(); expect((await respPromise).message).toContain(kTargetClosedErrorMessage); // All headers are the same as "provisional" headers in Firefox. - if (browserName === 'firefox' || browserName as any === '_bidiFirefox') + if (browserName === 'firefox' || channel.startsWith('moz-firefox')) expect((await headersPromise)['user-agent']).toBeTruthy(); else expect((await headersPromise).message).toContain(kTargetClosedErrorMessage); diff --git a/tests/page/page-basic.spec.ts b/tests/page/page-basic.spec.ts index 715c03b54ca32..b94e3ed0c890a 100644 --- a/tests/page/page-basic.spec.ts +++ b/tests/page/page-basic.spec.ts @@ -108,7 +108,7 @@ it('page.frame should respect url', async function({ page, server }) { expect(page.frame({ url: /empty/ }).url()).toBe(server.EMPTY_PAGE); }); -it('should have sane user agent', async ({ page, browserName, isElectron, isAndroid }) => { +it('should have sane user agent', async ({ page, browserName, isElectron, isAndroid, channel }) => { it.skip(isAndroid); it.skip(isElectron); @@ -125,7 +125,7 @@ it('should have sane user agent', async ({ page, browserName, isElectron, isAndr // Second part in parenthesis is platform - ignore it. // Third part for Firefox is the last one and encodes engine and browser versions. - if (browserName === 'firefox' || browserName as any === '_bidiFirefox') { + if (browserName === 'firefox' || channel.startsWith('moz-firefox')) { const [engine, browser] = part3.split(' '); expect(engine.startsWith('Gecko')).toBe(true); expect(browser.startsWith('Firefox')).toBe(true); diff --git a/tests/page/page-event-console.spec.ts b/tests/page/page-event-console.spec.ts index 081f743937e91..9766e16b151d8 100644 --- a/tests/page/page-event-console.spec.ts +++ b/tests/page/page-event-console.spec.ts @@ -191,7 +191,7 @@ it('should use object previews for arrays and objects', async ({ page, browserNa expect(text).toEqual('Array JSHandle@object JSHandle@object'); }); -it('should use object previews for errors', async ({ page, browserName }) => { +it('should use object previews for errors', async ({ page, browserName, channel }) => { let text: string; page.on('console', message => { text = message.text(); @@ -201,7 +201,7 @@ it('should use object previews for errors', async ({ page, browserName }) => { expect(text).toContain('.evaluate'); if (browserName as any === '_bidiChromium') expect(text).toEqual('error'); - if (browserName === 'webkit' || browserName as any === '_bidiFirefox') + if (browserName === 'webkit' || channel.startsWith('moz-firefox')) expect(text).toEqual('Error: Exception'); if (browserName === 'firefox') expect(text).toEqual('Error'); diff --git a/tests/page/page-event-pageerror.spec.ts b/tests/page/page-event-pageerror.spec.ts index 310f4917f0bb9..9612fb5302579 100644 --- a/tests/page/page-event-pageerror.spec.ts +++ b/tests/page/page-event-pageerror.spec.ts @@ -17,7 +17,7 @@ import { test as it, expect } from './pageTest'; -it('should fire', async ({ page, server, browserName }) => { +it('should fire', async ({ page, server, browserName, channel }) => { const url = server.PREFIX + '/error.html'; const [error] = await Promise.all([ page.waitForEvent('pageerror'), @@ -25,7 +25,7 @@ it('should fire', async ({ page, server, browserName }) => { ]); expect(error.name).toBe('Error'); expect(error.message).toBe('Fancy error!'); - if (browserName === 'chromium' || browserName === '_bidiChromium' || browserName === '_bidiFirefox') { + if (browserName === 'chromium' || browserName === '_bidiChromium' || channel.startsWith('moz-firefox')) { expect(error.stack).toBe(`Error: Fancy error! at c (myscript.js:14:11) at b (myscript.js:10:5) diff --git a/tests/page/page-goto.spec.ts b/tests/page/page-goto.spec.ts index 954228de06f55..a1c863b2c7d51 100644 --- a/tests/page/page-goto.spec.ts +++ b/tests/page/page-goto.spec.ts @@ -292,12 +292,12 @@ it('should work when page calls history API in beforeunload', async ({ page, ser expect(response.status()).toBe(200); }); -it('should fail when navigating to bad url', async ({ mode, page, browserName }) => { +it('should fail when navigating to bad url', async ({ page, browserName, channel }) => { let error = null; await page.goto('asdfasdf').catch(e => error = e); if (browserName === 'chromium' || browserName === 'webkit') expect(error.message).toContain('Cannot navigate to invalid URL'); - else if (browserName === '_bidiFirefox') + else if (channel.startsWith('moz-firefox')) expect(error.message).toContain('NS_ERROR_MALFORMED_URI'); else expect(error.message).toContain('Invalid url'); diff --git a/tests/page/page-route.spec.ts b/tests/page/page-route.spec.ts index 00b9754bd9de7..bc8f95ad8f0e7 100644 --- a/tests/page/page-route.spec.ts +++ b/tests/page/page-route.spec.ts @@ -344,17 +344,17 @@ it('should send referer', async ({ page, server }) => { expect(request.headers['referer']).toBe('http://google.com/'); }); -it('should fail navigation when aborting main resource', async ({ page, server, browserName, isMac, macVersion }) => { +it('should fail navigation when aborting main resource', async ({ page, server, browserName, isMac, macVersion, channel }) => { await page.route('**/*', route => route.abort()); let error = null; await page.goto(server.EMPTY_PAGE).catch(e => error = e); expect(error).toBeTruthy(); if (browserName === 'webkit') expect(error.message).toContain(isMac && macVersion < 11 ? 'Request intercepted' : 'Blocked by Web Inspector'); + else if (channel.startsWith('moz-firefox')) + expect(error.message).toContain('NS_ERROR_ABORT'); else if (browserName === 'firefox') expect(error.message).toContain('NS_ERROR_FAILURE'); - else if (browserName === '_bidiFirefox') - expect(error.message).toContain('NS_ERROR_ABORT'); else expect(error.message).toContain('net::ERR_FAILED'); }); @@ -609,7 +609,7 @@ it('should not fulfill with redirect status', async ({ page, server, browserName } }); -it('should support cors with GET', async ({ page, server, browserName }) => { +it('should support cors with GET', async ({ page, server, browserName, channel }) => { await page.goto(server.EMPTY_PAGE); await page.route('**/cars*', async (route, request) => { const headers = { 'access-control-allow-origin': request.url().endsWith('allow') ? '*' : 'none' }; @@ -638,7 +638,7 @@ it('should support cors with GET', async ({ page, server, browserName }) => { expect(error.message).toContain('Failed'); if (browserName === 'webkit') expect(error.message).toContain('TypeError'); - if (browserName === 'firefox' || browserName === '_bidiFirefox') + if (browserName === 'firefox' || channel.startsWith('moz-firefox')) expect(error.message).toContain('NetworkError'); } }); diff --git a/tests/page/pageTestApi.ts b/tests/page/pageTestApi.ts index f29af2c92f680..e5e4625c5d8aa 100644 --- a/tests/page/pageTestApi.ts +++ b/tests/page/pageTestApi.ts @@ -29,7 +29,7 @@ export type PageWorkerFixtures = { screenshot: ScreenshotMode | { mode: ScreenshotMode } & Pick; trace: 'off' | 'on' | 'retain-on-failure' | 'on-first-retry' | 'retain-on-first-failure' | 'on-all-retries' | /** deprecated */ 'retry-with-trace'; video: VideoMode | { mode: VideoMode, size: ViewportSize }; - browserName: 'chromium' | 'firefox' | 'webkit' | '_bidiFirefox' | '_bidiChromium'; + browserName: 'chromium' | 'firefox' | 'webkit' | '_bidiChromium'; browserVersion: string; browserMajorVersion: number; electronMajorVersion: number; diff --git a/utils/generate_types/overrides.d.ts b/utils/generate_types/overrides.d.ts index 8c7863a3c8edf..84b556b9ee791 100644 --- a/utils/generate_types/overrides.d.ts +++ b/utils/generate_types/overrides.d.ts @@ -398,7 +398,6 @@ export type AndroidKey = export const _electron: Electron; export const _android: Android; export const _bidiChromium: BrowserType; -export const _bidiFirefox: BrowserType; // This is required to not export everything by default. See https://github.com/Microsoft/TypeScript/issues/19545#issuecomment-340490459 export {}; From 2b90eda3cabd26ae5dd3801e980badc602248e49 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 2 Oct 2025 16:55:10 -0700 Subject: [PATCH 2/3] channel is optional --- tests/config/browserTest.ts | 4 ++-- tests/config/utils.ts | 4 ++-- tests/library/browsercontext-timezone-id.spec.ts | 4 ++-- tests/library/favicon.spec.ts | 2 +- tests/library/har.spec.ts | 2 +- tests/library/page-close.spec.ts | 2 +- tests/page/page-basic.spec.ts | 2 +- tests/page/page-event-console.spec.ts | 2 +- tests/page/page-event-pageerror.spec.ts | 2 +- tests/page/page-goto.spec.ts | 2 +- tests/page/page-route.spec.ts | 4 ++-- tests/page/pageTestApi.ts | 2 +- 12 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/config/browserTest.ts b/tests/config/browserTest.ts index f2a10ab9dd9af..0fdc040f09d99 100644 --- a/tests/config/browserTest.ts +++ b/tests/config/browserTest.ts @@ -67,14 +67,14 @@ const test = baseTest.extend }, { scope: 'worker' }], allowsThirdParty: [async ({ browserName, channel }, run) => { - if (browserName === 'firefox' || channel.startsWith('moz-firefox')) + if (browserName === 'firefox' || channel?.startsWith('moz-firefox')) await run(true); else await run(false); }, { scope: 'worker' }], defaultSameSiteCookieValue: [async ({ browserName, platform, channel }, run) => { - if (browserName === 'chromium' || browserName as any === '_bidiChromium' || channel.startsWith('moz-firefox')) + if (browserName === 'chromium' || browserName as any === '_bidiChromium' || channel?.startsWith('moz-firefox')) await run('Lax'); else if (browserName === 'webkit' && (platform === 'linux' || channel === 'webkit-wsl')) await run('Lax'); diff --git a/tests/config/utils.ts b/tests/config/utils.ts index b2b47cb3e8170..41571186a3c45 100644 --- a/tests/config/utils.ts +++ b/tests/config/utils.ts @@ -53,7 +53,7 @@ export async function verifyViewport(page: Page, width: number, height: number) expect(await page.evaluate('window.innerHeight')).toBe(height); } -export function expectedSSLError(browserName: string, platform: string, channel: string): RegExp { +export function expectedSSLError(browserName: string, platform: string, channel: string | undefined): RegExp { if (browserName === 'chromium') return /net::(ERR_CERT_AUTHORITY_INVALID|ERR_CERT_INVALID)/; if (browserName === 'webkit') { @@ -64,7 +64,7 @@ export function expectedSSLError(browserName: string, platform: string, channel: else return /Unacceptable TLS certificate|Operation was cancelled/; } - if (channel.startsWith('moz-firefox')) + if (channel?.startsWith('moz-firefox')) return /MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT/; return /SSL_ERROR_UNKNOWN/; } diff --git a/tests/library/browsercontext-timezone-id.spec.ts b/tests/library/browsercontext-timezone-id.spec.ts index 786652416a7f9..567da9e231361 100644 --- a/tests/library/browsercontext-timezone-id.spec.ts +++ b/tests/library/browsercontext-timezone-id.spec.ts @@ -47,11 +47,11 @@ it('should work @smoke', async ({ browser, browserName }) => { it('should throw for invalid timezone IDs when creating pages', async ({ browser, browserName, channel }) => { for (const timezoneId of ['Foo/Bar', 'Baz/Qux']) { - if (browserName as any === '_bidiChromium' || channel.startsWith('moz-firefox')) { + if (browserName as any === '_bidiChromium' || channel?.startsWith('moz-firefox')) { const error = await browser.newContext({ timezoneId }).catch(e => e); if (browserName as any === '_bidiChromium') expect(error.message).toContain(`Invalid timezone "${timezoneId}"`); - else if (channel.startsWith('moz-firefox')) + else if (channel?.startsWith('moz-firefox')) expect(error.message).toContain(`Expected "timezone" to be a valid timezone ID (e.g., "Europe/Berlin") or a valid timezone offset (e.g., "+01:00"), got ${timezoneId}`); } else { let error = null; diff --git a/tests/library/favicon.spec.ts b/tests/library/favicon.spec.ts index de13bdee6838c..9d8f1aadc7706 100644 --- a/tests/library/favicon.spec.ts +++ b/tests/library/favicon.spec.ts @@ -18,7 +18,7 @@ import { contextTest as it } from '../config/browserTest'; it('should load svg favicon with prefer-color-scheme', async ({ page, server, browserName, headless, asset, channel }) => { - it.skip(headless && browserName !== 'firefox' && !channel.startsWith('moz-firefox'), 'headless browsers, except firefox, do not request favicons'); + it.skip(headless && browserName !== 'firefox' && !channel?.startsWith('moz-firefox'), 'headless browsers, except firefox, do not request favicons'); it.skip(!headless && browserName === 'webkit', 'headed webkit does not have a favicon feature'); // Browsers aggressively cache favicons, so force bust with the diff --git a/tests/library/har.spec.ts b/tests/library/har.spec.ts index b70f05a90e90e..227589697f7a0 100644 --- a/tests/library/har.spec.ts +++ b/tests/library/har.spec.ts @@ -699,7 +699,7 @@ it('should contain http2 for http2 requests', async ({ contextFactory }, testInf }); it('should filter favicon and favicon redirects', async ({ server, browserName, headless, asset, contextFactory, channel }, testInfo) => { - it.skip(headless && browserName !== 'firefox' && !channel.startsWith('moz-firefox'), 'headless browsers, except firefox, do not request favicons'); + it.skip(headless && browserName !== 'firefox' && !channel?.startsWith('moz-firefox'), 'headless browsers, except firefox, do not request favicons'); it.skip(!headless && browserName === 'webkit', 'headed webkit does not have a favicon feature'); const { page, getLog } = await pageWithHar(contextFactory, testInfo); diff --git a/tests/library/page-close.spec.ts b/tests/library/page-close.spec.ts index d0056e51ffa21..4b23924f0bf2c 100644 --- a/tests/library/page-close.spec.ts +++ b/tests/library/page-close.spec.ts @@ -153,7 +153,7 @@ test('interrupt request.response() and request.allHeaders() on page.close', asyn await page.close(); expect((await respPromise).message).toContain(kTargetClosedErrorMessage); // All headers are the same as "provisional" headers in Firefox. - if (browserName === 'firefox' || channel.startsWith('moz-firefox')) + if (browserName === 'firefox' || channel?.startsWith('moz-firefox')) expect((await headersPromise)['user-agent']).toBeTruthy(); else expect((await headersPromise).message).toContain(kTargetClosedErrorMessage); diff --git a/tests/page/page-basic.spec.ts b/tests/page/page-basic.spec.ts index b94e3ed0c890a..8cdd65ac1c331 100644 --- a/tests/page/page-basic.spec.ts +++ b/tests/page/page-basic.spec.ts @@ -125,7 +125,7 @@ it('should have sane user agent', async ({ page, browserName, isElectron, isAndr // Second part in parenthesis is platform - ignore it. // Third part for Firefox is the last one and encodes engine and browser versions. - if (browserName === 'firefox' || channel.startsWith('moz-firefox')) { + if (browserName === 'firefox' || channel?.startsWith('moz-firefox')) { const [engine, browser] = part3.split(' '); expect(engine.startsWith('Gecko')).toBe(true); expect(browser.startsWith('Firefox')).toBe(true); diff --git a/tests/page/page-event-console.spec.ts b/tests/page/page-event-console.spec.ts index 9766e16b151d8..387e4e5709368 100644 --- a/tests/page/page-event-console.spec.ts +++ b/tests/page/page-event-console.spec.ts @@ -201,7 +201,7 @@ it('should use object previews for errors', async ({ page, browserName, channel expect(text).toContain('.evaluate'); if (browserName as any === '_bidiChromium') expect(text).toEqual('error'); - if (browserName === 'webkit' || channel.startsWith('moz-firefox')) + if (browserName === 'webkit' || channel?.startsWith('moz-firefox')) expect(text).toEqual('Error: Exception'); if (browserName === 'firefox') expect(text).toEqual('Error'); diff --git a/tests/page/page-event-pageerror.spec.ts b/tests/page/page-event-pageerror.spec.ts index 9612fb5302579..ea1bf0524f8da 100644 --- a/tests/page/page-event-pageerror.spec.ts +++ b/tests/page/page-event-pageerror.spec.ts @@ -25,7 +25,7 @@ it('should fire', async ({ page, server, browserName, channel }) => { ]); expect(error.name).toBe('Error'); expect(error.message).toBe('Fancy error!'); - if (browserName === 'chromium' || browserName === '_bidiChromium' || channel.startsWith('moz-firefox')) { + if (browserName === 'chromium' || browserName === '_bidiChromium' || channel?.startsWith('moz-firefox')) { expect(error.stack).toBe(`Error: Fancy error! at c (myscript.js:14:11) at b (myscript.js:10:5) diff --git a/tests/page/page-goto.spec.ts b/tests/page/page-goto.spec.ts index a1c863b2c7d51..354c970052587 100644 --- a/tests/page/page-goto.spec.ts +++ b/tests/page/page-goto.spec.ts @@ -297,7 +297,7 @@ it('should fail when navigating to bad url', async ({ page, browserName, channel await page.goto('asdfasdf').catch(e => error = e); if (browserName === 'chromium' || browserName === 'webkit') expect(error.message).toContain('Cannot navigate to invalid URL'); - else if (channel.startsWith('moz-firefox')) + else if (channel?.startsWith('moz-firefox')) expect(error.message).toContain('NS_ERROR_MALFORMED_URI'); else expect(error.message).toContain('Invalid url'); diff --git a/tests/page/page-route.spec.ts b/tests/page/page-route.spec.ts index bc8f95ad8f0e7..8dc56af5d645d 100644 --- a/tests/page/page-route.spec.ts +++ b/tests/page/page-route.spec.ts @@ -351,7 +351,7 @@ it('should fail navigation when aborting main resource', async ({ page, server, expect(error).toBeTruthy(); if (browserName === 'webkit') expect(error.message).toContain(isMac && macVersion < 11 ? 'Request intercepted' : 'Blocked by Web Inspector'); - else if (channel.startsWith('moz-firefox')) + else if (channel?.startsWith('moz-firefox')) expect(error.message).toContain('NS_ERROR_ABORT'); else if (browserName === 'firefox') expect(error.message).toContain('NS_ERROR_FAILURE'); @@ -638,7 +638,7 @@ it('should support cors with GET', async ({ page, server, browserName, channel } expect(error.message).toContain('Failed'); if (browserName === 'webkit') expect(error.message).toContain('TypeError'); - if (browserName === 'firefox' || channel.startsWith('moz-firefox')) + if (browserName === 'firefox' || channel?.startsWith('moz-firefox')) expect(error.message).toContain('NetworkError'); } }); diff --git a/tests/page/pageTestApi.ts b/tests/page/pageTestApi.ts index e5e4625c5d8aa..bde24e623597f 100644 --- a/tests/page/pageTestApi.ts +++ b/tests/page/pageTestApi.ts @@ -25,7 +25,7 @@ export type PageTestFixtures = { export type PageWorkerFixtures = { headless: boolean; - channel: string; + channel: string | undefined; screenshot: ScreenshotMode | { mode: ScreenshotMode } & Pick; trace: 'off' | 'on' | 'retain-on-failure' | 'on-first-retry' | 'retain-on-first-failure' | 'on-all-retries' | /** deprecated */ 'retry-with-trace'; video: VideoMode | { mode: VideoMode, size: ViewportSize }; From 54239bf88d308a2eff3c1b5961ea48c04556faf6 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 2 Oct 2025 17:58:06 -0700 Subject: [PATCH 3/3] fix tests --- .../playwright-core/src/server/firefox/firefox.ts | 2 +- tests/library/browsercontext-network-event.spec.ts | 2 +- tests/library/channels.spec.ts | 11 ----------- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/packages/playwright-core/src/server/firefox/firefox.ts b/packages/playwright-core/src/server/firefox/firefox.ts index db15784b44e75..1c0daef0fb6b7 100644 --- a/packages/playwright-core/src/server/firefox/firefox.ts +++ b/packages/playwright-core/src/server/firefox/firefox.ts @@ -43,7 +43,7 @@ export class Firefox extends BrowserType { } override launch(progress: Progress, options: types.LaunchOptions, protocolLogger?: types.ProtocolLogger): Promise { - if (options.channel && options.channel.startsWith('moz-')) + if (options.channel?.startsWith('moz-')) return this._bidiFirefox.launch(progress, options, protocolLogger); return super.launch(progress, options, protocolLogger); } diff --git a/tests/library/browsercontext-network-event.spec.ts b/tests/library/browsercontext-network-event.spec.ts index 74a773388f9b6..0265d1caa3c4d 100644 --- a/tests/library/browsercontext-network-event.spec.ts +++ b/tests/library/browsercontext-network-event.spec.ts @@ -105,7 +105,7 @@ it('should fire events in proper order', async ({ context, server }) => { }); it('should not fire events for favicon or favicon redirects', async ({ context, page, server, browserName, headless, channel }) => { - it.skip(headless && browserName !== 'firefox' && !channel.startsWith('moz-firefox'), 'headless browsers, except firefox, do not request favicons'); + it.skip(headless && browserName !== 'firefox' && !channel?.startsWith('moz-firefox'), 'headless browsers, except firefox, do not request favicons'); it.skip(!headless && browserName === 'webkit', 'headed webkit does not have a favicon feature'); const favicon = `/no-cache/favicon.ico`; const hashedFaviconUrl = `/favicon-hashed.ico`; diff --git a/tests/library/channels.spec.ts b/tests/library/channels.spec.ts index 827f9fe0b9ec2..13ec20d3f0363 100644 --- a/tests/library/channels.spec.ts +++ b/tests/library/channels.spec.ts @@ -46,7 +46,6 @@ it('should scope context handles', async ({ browserType, server, expectScopeStat { _guid: 'browser-type', objects: [] }, { _guid: 'browser-type', objects: [] }, { _guid: 'browser-type', objects: [] }, - { _guid: 'browser-type', objects: [] }, { _guid: 'browser-type', objects: [ { _guid: 'browser', objects: [] } ] }, @@ -69,7 +68,6 @@ it('should scope context handles', async ({ browserType, server, expectScopeStat { _guid: 'browser-type', objects: [] }, { _guid: 'browser-type', objects: [] }, { _guid: 'browser-type', objects: [] }, - { _guid: 'browser-type', objects: [] }, { _guid: 'browser-type', objects: [ { _guid: 'browser', objects: [ { _guid: 'browser-context', objects: [ @@ -106,7 +104,6 @@ it('should scope CDPSession handles', async ({ browserType, browserName, expectS { _guid: 'browser-type', objects: [] }, { _guid: 'browser-type', objects: [] }, { _guid: 'browser-type', objects: [] }, - { _guid: 'browser-type', objects: [] }, { _guid: 'browser-type', objects: [ { _guid: 'browser', objects: [] } ] }, @@ -125,7 +122,6 @@ it('should scope CDPSession handles', async ({ browserType, browserName, expectS { _guid: 'browser-type', objects: [] }, { _guid: 'browser-type', objects: [] }, { _guid: 'browser-type', objects: [] }, - { _guid: 'browser-type', objects: [] }, { _guid: 'browser-type', objects: [ { _guid: 'browser', objects: [ { _guid: 'cdp-session', objects: [] }, @@ -152,7 +148,6 @@ it('should scope browser handles', async ({ browserType, expectScopeState }) => { _guid: 'browser-type', objects: [] }, { _guid: 'browser-type', objects: [] }, { _guid: 'browser-type', objects: [] }, - { _guid: 'browser-type', objects: [] }, { _guid: 'electron', objects: [] }, { _guid: 'localUtils', objects: [] }, { _guid: 'Playwright', objects: [] }, @@ -169,7 +164,6 @@ it('should scope browser handles', async ({ browserType, expectScopeState }) => { _guid: 'browser-type', objects: [] }, { _guid: 'browser-type', objects: [] }, { _guid: 'browser-type', objects: [] }, - { _guid: 'browser-type', objects: [] }, { _guid: 'browser-type', objects: [ { _guid: 'browser', objects: [ @@ -206,7 +200,6 @@ it('should not generate dispatchers for subresources w/o listeners', async ({ pa { _guid: 'browser-type', objects: [] }, { _guid: 'browser-type', objects: [] }, { _guid: 'browser-type', objects: [] }, - { _guid: 'browser-type', objects: [] }, { _guid: 'browser-type', objects: [ { _guid: 'browser', objects: [ @@ -289,10 +282,6 @@ it('exposeFunction should not leak', async ({ page, expectScopeState, server }) '_guid': 'browser-type', 'objects': [], }, - { - '_guid': 'browser-type', - 'objects': [], - }, { '_guid': 'browser-type', 'objects': [