diff --git a/docs/src/api/class-browsertype.md b/docs/src/api/class-browsertype.md index 9f94ff4beb68c..af59fbac3679d 100644 --- a/docs/src/api/class-browsertype.md +++ b/docs/src/api/class-browsertype.md @@ -322,6 +322,14 @@ describes some differences for Linux users. ### option: BrowserType.launch.ignoreAllDefaultArgs = %%-csharp-java-browser-option-ignorealldefaultargs-%% * since: v1.9 +### option: BrowserType.launch.createPagesInBackground +* since: v1.62 +- `createPagesInBackground` <[boolean]> + +When set to `true`, new pages are created in the background, so that the headed browser window does not activate and +steal the OS focus from the user. Pages still behave as focused ones. Use [`method: Page.bringToFront`] to activate +the browser window when needed. Currently only implemented for headed Chromium. Defaults to `false`. + ## async method: BrowserType.launchPersistentContext * since: v1.8 - returns: <[BrowserContext]> diff --git a/packages/playwright-client/types/types.d.ts b/packages/playwright-client/types/types.d.ts index 0bff9d367c0b3..95c45e0804693 100644 --- a/packages/playwright-client/types/types.d.ts +++ b/packages/playwright-client/types/types.d.ts @@ -23283,6 +23283,14 @@ export interface LaunchOptions { */ chromiumSandbox?: boolean; + /** + * When set to `true`, new pages are created in the background, so that the headed browser window does not activate + * and steal the OS focus from the user. Pages still behave as focused ones. Use + * [page.bringToFront()](https://playwright.dev/docs/api/class-page#page-bring-to-front) to activate the browser + * window when needed. Currently only implemented for headed Chromium. Defaults to `false`. + */ + createPagesInBackground?: boolean; + /** * If specified, accepted downloads are downloaded into this directory. Otherwise, temporary directory is created and * is deleted when browser is closed. In either case, the downloads are deleted when the browser context they were diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 4a9b428e56fc3..f3045939b35dc 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -982,6 +982,7 @@ scheme.BrowserTypeLaunchParams = tObject({ tracesDir: tOptional(tString), artifactsDir: tOptional(tString), chromiumSandbox: tOptional(tBoolean), + createPagesInBackground: tOptional(tBoolean), firefoxUserPrefs: tOptional(tAny), cdpPort: tOptional(tInt), slowMo: tOptional(tFloat), @@ -1011,6 +1012,7 @@ scheme.BrowserTypeLaunchPersistentContextParams = tObject({ tracesDir: tOptional(tString), artifactsDir: tOptional(tString), chromiumSandbox: tOptional(tBoolean), + createPagesInBackground: tOptional(tBoolean), firefoxUserPrefs: tOptional(tAny), cdpPort: tOptional(tInt), noDefaultViewport: tOptional(tBoolean), diff --git a/packages/playwright-core/src/server/chromium/crBrowser.ts b/packages/playwright-core/src/server/chromium/crBrowser.ts index 27a3409fb5c28..12c2955552e17 100644 --- a/packages/playwright-core/src/server/chromium/crBrowser.ts +++ b/packages/playwright-core/src/server/chromium/crBrowser.ts @@ -370,7 +370,9 @@ export class CRBrowserContext extends BrowserContext { } override async doCreateNewPage(): Promise { - const { targetId } = await this._browser._session.send('Target.createTarget', { url: 'about:blank', browserContextId: this._browserContextId }); + // The `background` parameter is not supported by the headless shell or Android. + const background = this._browser.options.originalLaunchOptions?.createPagesInBackground && this._browser.options.headful && this._browser.options.name !== 'clank' ? true : undefined; + const { targetId } = await this._browser._session.send('Target.createTarget', { url: 'about:blank', browserContextId: this._browserContextId, background }); return this._browser._crPages.get(targetId)!._page; } diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 0bff9d367c0b3..95c45e0804693 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -23283,6 +23283,14 @@ export interface LaunchOptions { */ chromiumSandbox?: boolean; + /** + * When set to `true`, new pages are created in the background, so that the headed browser window does not activate + * and steal the OS focus from the user. Pages still behave as focused ones. Use + * [page.bringToFront()](https://playwright.dev/docs/api/class-page#page-bring-to-front) to activate the browser + * window when needed. Currently only implemented for headed Chromium. Defaults to `false`. + */ + createPagesInBackground?: boolean; + /** * If specified, accepted downloads are downloaded into this directory. Otherwise, temporary directory is created and * is deleted when browser is closed. In either case, the downloads are deleted when the browser context they were diff --git a/packages/protocol/spec/mixins.yml b/packages/protocol/spec/mixins.yml index c950b7f2b68ac..8acc43f9bdb75 100644 --- a/packages/protocol/spec/mixins.yml +++ b/packages/protocol/spec/mixins.yml @@ -92,6 +92,7 @@ LaunchOptions: tracesDir: string? artifactsDir: string? chromiumSandbox: boolean? + createPagesInBackground: boolean? firefoxUserPrefs: json? cdpPort: int? diff --git a/packages/protocol/src/channels.d.ts b/packages/protocol/src/channels.d.ts index ddec4af4c34a1..d716810ab5c2b 100644 --- a/packages/protocol/src/channels.d.ts +++ b/packages/protocol/src/channels.d.ts @@ -1838,6 +1838,7 @@ export type BrowserTypeLaunchParams = { tracesDir?: string, artifactsDir?: string, chromiumSandbox?: boolean, + createPagesInBackground?: boolean, firefoxUserPrefs?: any, cdpPort?: number, slowMo?: number, @@ -1863,6 +1864,7 @@ export type BrowserTypeLaunchOptions = { tracesDir?: string, artifactsDir?: string, chromiumSandbox?: boolean, + createPagesInBackground?: boolean, firefoxUserPrefs?: any, cdpPort?: number, slowMo?: number, @@ -1892,6 +1894,7 @@ export type BrowserTypeLaunchPersistentContextParams = { tracesDir?: string, artifactsDir?: string, chromiumSandbox?: boolean, + createPagesInBackground?: boolean, firefoxUserPrefs?: any, cdpPort?: number, noDefaultViewport?: boolean, @@ -1980,6 +1983,7 @@ export type BrowserTypeLaunchPersistentContextOptions = { tracesDir?: string, artifactsDir?: string, chromiumSandbox?: boolean, + createPagesInBackground?: boolean, firefoxUserPrefs?: any, cdpPort?: number, noDefaultViewport?: boolean, diff --git a/tests/library/headful.spec.ts b/tests/library/headful.spec.ts index bc9707ba69a06..1efffe7732599 100644 --- a/tests/library/headful.spec.ts +++ b/tests/library/headful.spec.ts @@ -232,6 +232,30 @@ it('Page.bringToFront should work', async ({ browser }) => { await page2.close(); }); +it('pages created in background should be focused and interactable', { + annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/4822' }, +}, async ({ browserType }) => { + // With createPagesInBackground, pages do not activate the browser window; + // emulated focus should make them behave as foreground. + const browser = await browserType.launch({ createPagesInBackground: true }); + const page1 = await browser.newPage(); + await page1.setContent(''); + const page2 = await browser.newPage(); + await page2.setContent(''); + + expect(await page1.evaluate('document.visibilityState')).toBe('visible'); + expect(await page2.evaluate('document.visibilityState')).toBe('visible'); + expect(await page1.evaluate('document.hasFocus()')).toBe(true); + expect(await page2.evaluate('document.hasFocus()')).toBe(true); + + await page1.fill('#i', 'page1'); + await page2.fill('#i', 'page2'); + expect(await page1.inputValue('#i')).toBe('page1'); + expect(await page2.inputValue('#i')).toBe('page2'); + + await browser.close(); +}); + it('should click in OOPIF', async ({ browserName, launchPersistent, server }) => { server.setRoute('/empty.html', (req, res) => { res.writeHead(200, { 'Content-Type': 'text/html' });