diff --git a/docs/src/api/params.md b/docs/src/api/params.md index 02bd0483dc413..9f9b46d8cd235 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -355,7 +355,24 @@ When using [`method: Page.goto`], [`method: Page.route`], [`method: Page.waitFor * baseURL: `http://localhost:3000/foo` (without trailing slash) and navigating to `./bar.html` results in `http://localhost:3000/bar.html` ## context-option-viewport -* langs: js, java +* langs: js +- `viewport` <[null]|"maximized"|[Object]> + * alias: ViewportSize + - `width` <[int]> page width in pixels. + - `height` <[int]> page height in pixels. + +Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. +Use `null` to disable the consistent viewport emulation. Learn more about [viewport emulation](../emulation#viewport). +Use `"maximized"` to disable viewport emulation and maximize each native browser window. This is only supported in headed mode and does not enter fullscreen mode or trigger the web Fullscreen API. + +:::note +The `null` and `"maximized"` values opt out from the default presets and make viewport depend on the +host window size defined by the operating system. It makes the execution of the +tests non-deterministic. +::: + +## java-context-option-viewport +* langs: java - alias-java: viewportSize - `viewport` <[null]|[Object]> * alias: ViewportSize @@ -1090,6 +1107,7 @@ between the same pixel in compared images, between zero (strict) and one (lax), - %%-context-option-bypasscsp-%% - %%-context-option-baseURL-%% - %%-context-option-viewport-%% +- %%-java-context-option-viewport-%% - %%-csharp-context-option-viewport-%% - %%-python-context-option-viewport-%% - %%-context-option-screen-%% diff --git a/packages/isomorphic/codegen/java.ts b/packages/isomorphic/codegen/java.ts index 2c36a649d68b0..1a762752672ce 100644 --- a/packages/isomorphic/codegen/java.ts +++ b/packages/isomorphic/codegen/java.ts @@ -284,7 +284,7 @@ function formatContextOptions(contextOptions: BrowserContextOptions, deviceName: lines.push(` .setTimezoneId(${quote(options.timezoneId)})`); if (options.userAgent) lines.push(` .setUserAgent(${quote(options.userAgent)})`); - if (options.viewport) + if (options.viewport && options.viewport !== 'maximized') lines.push(` .setViewportSize(${options.viewport.width}, ${options.viewport.height})`); return lines.join('\n'); } diff --git a/packages/playwright-client/types/types.d.ts b/packages/playwright-client/types/types.d.ts index 2acee94c525e7..f685272bc4ea4 100644 --- a/packages/playwright-client/types/types.d.ts +++ b/packages/playwright-client/types/types.d.ts @@ -11706,13 +11706,15 @@ export interface Browser { /** * Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. Use `null` to disable the consistent - * viewport emulation. Learn more about [viewport emulation](https://playwright.dev/docs/emulation#viewport). + * viewport emulation. Learn more about [viewport emulation](https://playwright.dev/docs/emulation#viewport). Use `"maximized"` to disable + * viewport emulation and maximize each native browser window. This is only supported in headed mode and does not + * enter fullscreen mode or trigger the web Fullscreen API. * - * **NOTE** The `null` value opts out from the default presets, makes viewport depend on the host window size defined - * by the operating system. It makes the execution of the tests non-deterministic. + * **NOTE** The `null` and `"maximized"` values opt out from the default presets and make viewport depend on the host + * window size defined by the operating system. It makes the execution of the tests non-deterministic. * */ - viewport?: null|{ + viewport?: null|"maximized"|{ /** * page width in pixels. */ @@ -18015,13 +18017,15 @@ export interface BrowserType { /** * Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. Use `null` to disable the consistent - * viewport emulation. Learn more about [viewport emulation](https://playwright.dev/docs/emulation#viewport). + * viewport emulation. Learn more about [viewport emulation](https://playwright.dev/docs/emulation#viewport). Use `"maximized"` to disable + * viewport emulation and maximize each native browser window. This is only supported in headed mode and does not + * enter fullscreen mode or trigger the web Fullscreen API. * - * **NOTE** The `null` value opts out from the default presets, makes viewport depend on the host window size defined - * by the operating system. It makes the execution of the tests non-deterministic. + * **NOTE** The `null` and `"maximized"` values opt out from the default presets and make viewport depend on the host + * window size defined by the operating system. It makes the execution of the tests non-deterministic. * */ - viewport?: null|{ + viewport?: null|"maximized"|{ /** * page width in pixels. */ @@ -24875,13 +24879,15 @@ export interface AndroidDevice { /** * Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. Use `null` to disable the consistent - * viewport emulation. Learn more about [viewport emulation](https://playwright.dev/docs/emulation#viewport). + * viewport emulation. Learn more about [viewport emulation](https://playwright.dev/docs/emulation#viewport). Use `"maximized"` to disable + * viewport emulation and maximize each native browser window. This is only supported in headed mode and does not + * enter fullscreen mode or trigger the web Fullscreen API. * - * **NOTE** The `null` value opts out from the default presets, makes viewport depend on the host window size defined - * by the operating system. It makes the execution of the tests non-deterministic. + * **NOTE** The `null` and `"maximized"` values opt out from the default presets and make viewport depend on the host + * window size defined by the operating system. It makes the execution of the tests non-deterministic. * */ - viewport?: null|{ + viewport?: null|"maximized"|{ /** * page width in pixels. */ @@ -26142,13 +26148,15 @@ export interface BrowserContextOptions { /** * Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. Use `null` to disable the consistent - * viewport emulation. Learn more about [viewport emulation](https://playwright.dev/docs/emulation#viewport). + * viewport emulation. Learn more about [viewport emulation](https://playwright.dev/docs/emulation#viewport). Use `"maximized"` to disable + * viewport emulation and maximize each native browser window. This is only supported in headed mode and does not + * enter fullscreen mode or trigger the web Fullscreen API. * - * **NOTE** The `null` value opts out from the default presets, makes viewport depend on the host window size defined - * by the operating system. It makes the execution of the tests non-deterministic. + * **NOTE** The `null` and `"maximized"` values opt out from the default presets and make viewport depend on the host + * window size defined by the operating system. It makes the execution of the tests non-deterministic. * */ - viewport?: null|ViewportSize; + viewport?: null|"maximized"|ViewportSize; } export interface ViewportSize { diff --git a/packages/playwright-core/src/client/browserContext.ts b/packages/playwright-core/src/client/browserContext.ts index 3d3614a00dd8a..1034c81fc7227 100644 --- a/packages/playwright-core/src/client/browserContext.ts +++ b/packages/playwright-core/src/client/browserContext.ts @@ -574,8 +574,9 @@ export async function prepareBrowserContextParams(options: BrowserContextOptions network.validateHeaders(options.extraHTTPHeaders); const contextParams: channels.BrowserNewContextParams = { ...options, - viewport: options.viewport === null ? undefined : options.viewport, - noDefaultViewport: options.viewport === null, + viewport: options.viewport === null || options.viewport === 'maximized' ? undefined : options.viewport, + viewportMaximized: options.viewport === 'maximized', + noDefaultViewport: options.viewport === null || options.viewport === 'maximized', extraHTTPHeaders: options.extraHTTPHeaders ? headersObjectToArray(options.extraHTTPHeaders) : undefined, storageState: options.storageState ? await prepareStorageState(options.storageState) : undefined, serviceWorkers: options.serviceWorkers, diff --git a/packages/playwright-core/src/client/channels.d.ts b/packages/playwright-core/src/client/channels.d.ts index aecf4c95c1c23..a666b6dce0103 100644 --- a/packages/playwright-core/src/client/channels.d.ts +++ b/packages/playwright-core/src/client/channels.d.ts @@ -379,6 +379,7 @@ export type AndroidDeviceLaunchBrowserParams = { width: number, height: number, }, + viewportMaximized?: boolean, screen?: { width: number, height: number, @@ -446,6 +447,7 @@ export type AndroidDeviceLaunchBrowserOptions = { width: number, height: number, }, + viewportMaximized?: boolean, screen?: { width: number, height: number, @@ -828,6 +830,7 @@ export type BrowserNewContextParams = { width: number, height: number, }, + viewportMaximized?: boolean, screen?: { width: number, height: number, @@ -898,6 +901,7 @@ export type BrowserNewContextOptions = { width: number, height: number, }, + viewportMaximized?: boolean, screen?: { width: number, height: number, @@ -971,6 +975,7 @@ export type BrowserNewContextForReuseParams = { width: number, height: number, }, + viewportMaximized?: boolean, screen?: { width: number, height: number, @@ -1041,6 +1046,7 @@ export type BrowserNewContextForReuseOptions = { width: number, height: number, }, + viewportMaximized?: boolean, screen?: { width: number, height: number, @@ -1153,6 +1159,7 @@ export type BrowserContextInitializer = { width: number, height: number, }, + viewportMaximized?: boolean, screen?: { width: number, height: number, @@ -1798,6 +1805,7 @@ export type BrowserTypeLaunchPersistentContextParams = { width: number, height: number, }, + viewportMaximized?: boolean, screen?: { width: number, height: number, @@ -1881,6 +1889,7 @@ export type BrowserTypeLaunchPersistentContextOptions = { width: number, height: number, }, + viewportMaximized?: boolean, screen?: { width: number, height: number, diff --git a/packages/playwright-core/src/client/types.ts b/packages/playwright-core/src/client/types.ts index 89a610732afa5..ac296775db0fb 100644 --- a/packages/playwright-core/src/client/types.ts +++ b/packages/playwright-core/src/client/types.ts @@ -64,8 +64,8 @@ export type ClientCertificate = { passphrase?: string; }; -export type BrowserContextOptions = Omit & { - viewport?: Size | null; +export type BrowserContextOptions = Omit & { + viewport?: Size | 'maximized' | null; extraHTTPHeaders?: Headers; httpCredentials?: HttpCredentials | HttpCredentials[]; logger?: Logger; diff --git a/packages/playwright-core/src/server/browserContext.ts b/packages/playwright-core/src/server/browserContext.ts index 33b5331e64ffc..70810511194f9 100644 --- a/packages/playwright-core/src/server/browserContext.ts +++ b/packages/playwright-core/src/server/browserContext.ts @@ -760,10 +760,15 @@ export abstract class BrowserContext extends Sdk } export function validateBrowserContextOptions(options: types.BrowserContextOptions, browserOptions: BrowserOptions) { + const noDefaultViewportDescription = options.viewportMaximized ? '"maximized" viewport' : 'null "viewport"'; if (options.noDefaultViewport && options.deviceScaleFactor !== undefined) - throw new Error(`"deviceScaleFactor" option is not supported with null "viewport"`); + throw new Error(`"deviceScaleFactor" option is not supported with ${noDefaultViewportDescription}`); if (options.noDefaultViewport && !!options.isMobile) - throw new Error(`"isMobile" option is not supported with null "viewport"`); + throw new Error(`"isMobile" option is not supported with ${noDefaultViewportDescription}`); + if (options.viewportMaximized && !browserOptions.headful) + throw new Error(`"maximized" viewport is not supported in headless mode`); + if (options.viewportMaximized && !supportsMaximizedViewport(browserOptions)) + throw new Error(`"maximized" viewport is not supported for this browser`); if (options.acceptDownloads === undefined && browserOptions.name !== 'electron') options.acceptDownloads = 'accept'; // Electron requires explicit acceptDownloads: true since we wait for @@ -778,6 +783,14 @@ export function validateBrowserContextOptions(options: types.BrowserContextOptio verifyGeolocation(options.geolocation); } +function supportsMaximizedViewport(browserOptions: BrowserOptions) { + if (browserOptions.name === 'chromium') + return !browserOptions.channel?.startsWith('bidi-'); + if (browserOptions.name === 'firefox') + return !browserOptions.channel?.startsWith('moz-'); + return browserOptions.name === 'webkit'; +} + export function findMatchingHttpCredentials(credentials: HttpCredentials[] | undefined, url: string): HttpCredentials | undefined { const origin = new URL(url).origin.toLowerCase(); return credentials?.find(c => !c.origin || c.origin.toLowerCase() === origin); @@ -849,6 +862,7 @@ const paramsThatAllowContextReuse: (keyof channels.BrowserNewContextForReusePara const defaultNewContextParamValues: channels.BrowserNewContextForReuseParams = { noDefaultViewport: false, + viewportMaximized: false, ignoreHTTPSErrors: false, javaScriptEnabled: true, bypassCSP: false, diff --git a/packages/playwright-core/src/server/channels.d.ts b/packages/playwright-core/src/server/channels.d.ts index 1119ef3267bb9..0b57653efaafd 100644 --- a/packages/playwright-core/src/server/channels.d.ts +++ b/packages/playwright-core/src/server/channels.d.ts @@ -380,6 +380,7 @@ export type AndroidDeviceLaunchBrowserParams = { width: number, height: number, }, + viewportMaximized?: boolean, screen?: { width: number, height: number, @@ -447,6 +448,7 @@ export type AndroidDeviceLaunchBrowserOptions = { width: number, height: number, }, + viewportMaximized?: boolean, screen?: { width: number, height: number, @@ -829,6 +831,7 @@ export type BrowserNewContextParams = { width: number, height: number, }, + viewportMaximized?: boolean, screen?: { width: number, height: number, @@ -899,6 +902,7 @@ export type BrowserNewContextOptions = { width: number, height: number, }, + viewportMaximized?: boolean, screen?: { width: number, height: number, @@ -972,6 +976,7 @@ export type BrowserNewContextForReuseParams = { width: number, height: number, }, + viewportMaximized?: boolean, screen?: { width: number, height: number, @@ -1042,6 +1047,7 @@ export type BrowserNewContextForReuseOptions = { width: number, height: number, }, + viewportMaximized?: boolean, screen?: { width: number, height: number, @@ -1154,6 +1160,7 @@ export type BrowserContextInitializer = { width: number, height: number, }, + viewportMaximized?: boolean, screen?: { width: number, height: number, @@ -1799,6 +1806,7 @@ export type BrowserTypeLaunchPersistentContextParams = { width: number, height: number, }, + viewportMaximized?: boolean, screen?: { width: number, height: number, @@ -1882,6 +1890,7 @@ export type BrowserTypeLaunchPersistentContextOptions = { width: number, height: number, }, + viewportMaximized?: boolean, screen?: { width: number, height: number, diff --git a/packages/playwright-core/src/server/chromium/crPage.ts b/packages/playwright-core/src/server/chromium/crPage.ts index 0c2bcc08b08b0..ea9612c8e90f0 100644 --- a/packages/playwright-core/src/server/chromium/crPage.ts +++ b/packages/playwright-core/src/server/chromium/crPage.ts @@ -47,7 +47,7 @@ import type * as types from '../types'; import type * as channels from '../channels'; -export type WindowBounds = { top?: number, left?: number, width?: number, height?: number }; +export type WindowBounds = { top?: number, left?: number, width?: number, height?: number, windowState?: Protocol.Browser.WindowState }; // Browsers disallow these WebUI hosts in off-the-record profiles and redirect them to the original // profile, which crashes when the profile was created over CDP. Edge allows most of them in InPrivate. @@ -465,9 +465,10 @@ class FrameSession { async _initialize(hasUIWindow: boolean) { const browserOptions = this._crPage._browserContext._browser.options; + const options = this._crPage._browserContext._options; if (!this._page.isStorageStatePage && hasUIWindow && !this._crPage._browserContext._browser.isClank() && - !this._crPage._browserContext._options.noDefaultViewport) { + (!options.noDefaultViewport || options.viewportMaximized)) { try { const { windowId } = await this._client.send('Browser.getWindowForTarget'); this._windowId = windowId; @@ -546,13 +547,14 @@ class FrameSession { promises.push(this.exposePlaywrightBinding()); if (this._isMainFrame() && !skipDefaultOverrides) promises.push(this._client.send('Emulation.setFocusEmulationEnabled', { enabled: true })); - const options = this._crPage._browserContext._options; if (options.bypassCSP) promises.push(this._client.send('Page.setBypassCSP', { enabled: true })); if (options.ignoreHTTPSErrors || options.internalIgnoreHTTPSErrors) promises.push(this._client.send('Security.setIgnoreCertificateErrors', { ignore: true })); if (this._isMainFrame()) promises.push(this._updateViewport()); + if (this._windowId && options.viewportMaximized) + promises.push(this.setWindowBounds({ windowState: 'maximized' })); if (options.hasTouch) promises.push(this._client.send('Emulation.setTouchEmulationEnabled', { enabled: true })); if (options.javaScriptEnabled === false) @@ -985,7 +987,7 @@ class FrameSession { if (JSON.stringify(this._metricsOverride) === JSON.stringify(metricsOverride)) return; const promises = []; - if (!preserveWindowBoundaries && this._windowId) { + if (!preserveWindowBoundaries && this._windowId && !options.viewportMaximized) { let insets = { width: 0, height: 0 }; if (this._crPage._browserContext._browser.options.headful) { // TODO: popup windows have their own insets. diff --git a/packages/playwright-core/src/server/firefox/ffBrowser.ts b/packages/playwright-core/src/server/firefox/ffBrowser.ts index cf2f84efb89c9..120580ac905eb 100644 --- a/packages/playwright-core/src/server/firefox/ffBrowser.ts +++ b/packages/playwright-core/src/server/firefox/ffBrowser.ts @@ -185,6 +185,11 @@ export class FFBrowserContext extends BrowserContext { }, })); } + if (this._options.viewportMaximized) { + promises.push(this._browser.session.send('Browser.setWindowMaximized', { + browserContextId, + })); + } promises.push(this.doUpdateDefaultViewport()); if (this._options.hasTouch) promises.push(this._browser.session.send('Browser.setTouchOverride', { browserContextId, hasTouch: true })); diff --git a/packages/playwright-core/src/server/firefox/protocol.d.ts b/packages/playwright-core/src/server/firefox/protocol.d.ts index 18da296d70272..46a1e7f0fa690 100644 --- a/packages/playwright-core/src/server/firefox/protocol.d.ts +++ b/packages/playwright-core/src/server/firefox/protocol.d.ts @@ -222,6 +222,10 @@ export namespace Protocol { }|null; }; export type setDefaultViewportReturnValue = void; + export type setWindowMaximizedParameters = { + browserContextId?: string; + }; + export type setWindowMaximizedReturnValue = void; export type setInitScriptsParameters = { browserContextId?: string; scripts: { @@ -1066,6 +1070,7 @@ export namespace Protocol { "Browser.setDownloadOptions": Browser.setDownloadOptionsParameters; "Browser.setTouchOverride": Browser.setTouchOverrideParameters; "Browser.setDefaultViewport": Browser.setDefaultViewportParameters; + "Browser.setWindowMaximized": Browser.setWindowMaximizedParameters; "Browser.setInitScripts": Browser.setInitScriptsParameters; "Browser.addBinding": Browser.addBindingParameters; "Browser.grantPermissions": Browser.grantPermissionsParameters; @@ -1148,6 +1153,7 @@ export namespace Protocol { "Browser.setDownloadOptions": Browser.setDownloadOptionsReturnValue; "Browser.setTouchOverride": Browser.setTouchOverrideReturnValue; "Browser.setDefaultViewport": Browser.setDefaultViewportReturnValue; + "Browser.setWindowMaximized": Browser.setWindowMaximizedReturnValue; "Browser.setInitScripts": Browser.setInitScriptsReturnValue; "Browser.addBinding": Browser.addBindingReturnValue; "Browser.grantPermissions": Browser.grantPermissionsReturnValue; diff --git a/packages/playwright-core/src/server/webkit/protocol.d.ts b/packages/playwright-core/src/server/webkit/protocol.d.ts index a1ef30279396e..d243d9718e946 100644 --- a/packages/playwright-core/src/server/webkit/protocol.d.ts +++ b/packages/playwright-core/src/server/webkit/protocol.d.ts @@ -7430,6 +7430,17 @@ the top of the viewport and Y increases as it proceeds towards the bottom of the */ pageProxyId: PageProxyID; } + /** + * Maximizes the native browser window. + */ + export type maximizeWindowParameters = { + /** + * Unique identifier of the page proxy. + */ + pageProxyId: PageProxyID; + } + export type maximizeWindowReturnValue = { + } /** * Navigates current page to the given URL. */ @@ -9692,6 +9703,7 @@ the top of the viewport and Y increases as it proceeds towards the bottom of the "Playwright.createContext": Playwright.createContextParameters; "Playwright.deleteContext": Playwright.deleteContextParameters; "Playwright.createPage": Playwright.createPageParameters; + "Playwright.maximizeWindow": Playwright.maximizeWindowParameters; "Playwright.navigate": Playwright.navigateParameters; "Playwright.grantFileReadAccess": Playwright.grantFileReadAccessParameters; "Playwright.takePageScreenshot": Playwright.takePageScreenshotParameters; @@ -10002,6 +10014,7 @@ the top of the viewport and Y increases as it proceeds towards the bottom of the "Playwright.createContext": Playwright.createContextReturnValue; "Playwright.deleteContext": Playwright.deleteContextReturnValue; "Playwright.createPage": Playwright.createPageReturnValue; + "Playwright.maximizeWindow": Playwright.maximizeWindowReturnValue; "Playwright.navigate": Playwright.navigateReturnValue; "Playwright.grantFileReadAccess": Playwright.grantFileReadAccessReturnValue; "Playwright.takePageScreenshot": Playwright.takePageScreenshotReturnValue; diff --git a/packages/playwright-core/src/server/webkit/wkPage.ts b/packages/playwright-core/src/server/webkit/wkPage.ts index 66373d187e02a..e87dbd10f61cc 100644 --- a/packages/playwright-core/src/server/webkit/wkPage.ts +++ b/packages/playwright-core/src/server/webkit/wkPage.ts @@ -135,6 +135,11 @@ export class WKPage implements PageDelegate { } startAutomaticVideoRecording(this._page); await Promise.all(promises); + if (contextOptions.viewportMaximized) { + await this._browserContext._browser._browserSession.send('Playwright.maximizeWindow', { + pageProxyId: this._pageProxySession.sessionId, + }); + } } private _setSession(session: WKSession) { diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 2acee94c525e7..f685272bc4ea4 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -11706,13 +11706,15 @@ export interface Browser { /** * Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. Use `null` to disable the consistent - * viewport emulation. Learn more about [viewport emulation](https://playwright.dev/docs/emulation#viewport). + * viewport emulation. Learn more about [viewport emulation](https://playwright.dev/docs/emulation#viewport). Use `"maximized"` to disable + * viewport emulation and maximize each native browser window. This is only supported in headed mode and does not + * enter fullscreen mode or trigger the web Fullscreen API. * - * **NOTE** The `null` value opts out from the default presets, makes viewport depend on the host window size defined - * by the operating system. It makes the execution of the tests non-deterministic. + * **NOTE** The `null` and `"maximized"` values opt out from the default presets and make viewport depend on the host + * window size defined by the operating system. It makes the execution of the tests non-deterministic. * */ - viewport?: null|{ + viewport?: null|"maximized"|{ /** * page width in pixels. */ @@ -18015,13 +18017,15 @@ export interface BrowserType { /** * Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. Use `null` to disable the consistent - * viewport emulation. Learn more about [viewport emulation](https://playwright.dev/docs/emulation#viewport). + * viewport emulation. Learn more about [viewport emulation](https://playwright.dev/docs/emulation#viewport). Use `"maximized"` to disable + * viewport emulation and maximize each native browser window. This is only supported in headed mode and does not + * enter fullscreen mode or trigger the web Fullscreen API. * - * **NOTE** The `null` value opts out from the default presets, makes viewport depend on the host window size defined - * by the operating system. It makes the execution of the tests non-deterministic. + * **NOTE** The `null` and `"maximized"` values opt out from the default presets and make viewport depend on the host + * window size defined by the operating system. It makes the execution of the tests non-deterministic. * */ - viewport?: null|{ + viewport?: null|"maximized"|{ /** * page width in pixels. */ @@ -24875,13 +24879,15 @@ export interface AndroidDevice { /** * Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. Use `null` to disable the consistent - * viewport emulation. Learn more about [viewport emulation](https://playwright.dev/docs/emulation#viewport). + * viewport emulation. Learn more about [viewport emulation](https://playwright.dev/docs/emulation#viewport). Use `"maximized"` to disable + * viewport emulation and maximize each native browser window. This is only supported in headed mode and does not + * enter fullscreen mode or trigger the web Fullscreen API. * - * **NOTE** The `null` value opts out from the default presets, makes viewport depend on the host window size defined - * by the operating system. It makes the execution of the tests non-deterministic. + * **NOTE** The `null` and `"maximized"` values opt out from the default presets and make viewport depend on the host + * window size defined by the operating system. It makes the execution of the tests non-deterministic. * */ - viewport?: null|{ + viewport?: null|"maximized"|{ /** * page width in pixels. */ @@ -26142,13 +26148,15 @@ export interface BrowserContextOptions { /** * Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. Use `null` to disable the consistent - * viewport emulation. Learn more about [viewport emulation](https://playwright.dev/docs/emulation#viewport). + * viewport emulation. Learn more about [viewport emulation](https://playwright.dev/docs/emulation#viewport). Use `"maximized"` to disable + * viewport emulation and maximize each native browser window. This is only supported in headed mode and does not + * enter fullscreen mode or trigger the web Fullscreen API. * - * **NOTE** The `null` value opts out from the default presets, makes viewport depend on the host window size defined - * by the operating system. It makes the execution of the tests non-deterministic. + * **NOTE** The `null` and `"maximized"` values opt out from the default presets and make viewport depend on the host + * window size defined by the operating system. It makes the execution of the tests non-deterministic. * */ - viewport?: null|ViewportSize; + viewport?: null|"maximized"|ViewportSize; } export interface ViewportSize { diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 040450342836f..ee86a1f97fa2f 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -7750,10 +7750,12 @@ export interface PlaywrightTestOptions { userAgent: string | undefined; /** * Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. Use `null` to disable the consistent - * viewport emulation. Learn more about [viewport emulation](https://playwright.dev/docs/emulation#viewport). + * viewport emulation. Learn more about [viewport emulation](https://playwright.dev/docs/emulation#viewport). Use `"maximized"` to disable + * viewport emulation and maximize each native browser window. This is only supported in headed mode and does not + * enter fullscreen mode or trigger the web Fullscreen API. * - * **NOTE** The `null` value opts out from the default presets, makes viewport depend on the host window size defined - * by the operating system. It makes the execution of the tests non-deterministic. + * **NOTE** The `null` and `"maximized"` values opt out from the default presets and make viewport depend on the host + * window size defined by the operating system. It makes the execution of the tests non-deterministic. * * **Usage** * @@ -7769,7 +7771,7 @@ export interface PlaywrightTestOptions { * ``` * */ - viewport: ViewportSize | null; + viewport: ViewportSize | 'maximized' | null; /** * When using [page.goto(url[, options])](https://playwright.dev/docs/api/class-page#page-goto), * [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route), diff --git a/packages/protocol/spec/mixins.yml b/packages/protocol/spec/mixins.yml index 80dcdfe8f47fb..b6a191644bd48 100644 --- a/packages/protocol/spec/mixins.yml +++ b/packages/protocol/spec/mixins.yml @@ -115,6 +115,7 @@ ContextOptions: properties: width: int height: int + viewportMaximized: boolean? screen: type: object? properties: diff --git a/packages/protocol/src/validator.ts b/packages/protocol/src/validator.ts index 565a6a18cf956..88ade6fca6264 100644 --- a/packages/protocol/src/validator.ts +++ b/packages/protocol/src/validator.ts @@ -147,6 +147,7 @@ scheme.AndroidDeviceLaunchBrowserParams = tObject({ width: tInt, height: tInt, })), + viewportMaximized: tOptional(tBoolean), screen: tOptional(tObject({ width: tInt, height: tInt, @@ -429,6 +430,7 @@ scheme.BrowserNewContextParams = tObject({ width: tInt, height: tInt, })), + viewportMaximized: tOptional(tBoolean), screen: tOptional(tObject({ width: tInt, height: tInt, @@ -502,6 +504,7 @@ scheme.BrowserNewContextForReuseParams = tObject({ width: tInt, height: tInt, })), + viewportMaximized: tOptional(tBoolean), screen: tOptional(tObject({ width: tInt, height: tInt, @@ -597,6 +600,7 @@ scheme.BrowserContextInitializer = tObject({ width: tInt, height: tInt, })), + viewportMaximized: tOptional(tBoolean), screen: tOptional(tObject({ width: tInt, height: tInt, @@ -994,6 +998,7 @@ scheme.BrowserTypeLaunchPersistentContextParams = tObject({ width: tInt, height: tInt, })), + viewportMaximized: tOptional(tBoolean), screen: tOptional(tObject({ width: tInt, height: tInt, diff --git a/tests/library/browsercontext-basic.spec.ts b/tests/library/browsercontext-basic.spec.ts index 662919a375026..8b7ee1a07575e 100644 --- a/tests/library/browsercontext-basic.spec.ts +++ b/tests/library/browsercontext-basic.spec.ts @@ -193,6 +193,21 @@ it('should not allow isMobile with null viewport', async ({ browser }) => { expect(error.message).toContain('"isMobile" option is not supported with null "viewport"'); }); +it('should not allow deviceScaleFactor with maximized viewport', async ({ browser }) => { + const error = await browser.newContext({ viewport: 'maximized', deviceScaleFactor: 1 }).catch(e => e); + expect(error.message).toContain('"deviceScaleFactor" option is not supported with "maximized" viewport'); +}); + +it('should not allow isMobile with maximized viewport', async ({ browser }) => { + const error = await browser.newContext({ viewport: 'maximized', isMobile: true }).catch(e => e); + expect(error.message).toContain('"isMobile" option is not supported with "maximized" viewport'); +}); + +it('should not allow maximized viewport in headless mode', async ({ browser }) => { + const error = await browser.newContext({ viewport: 'maximized' }).catch(e => e); + expect(error.message).toContain('"maximized" viewport is not supported in headless mode'); +}); + it('close() should work for empty context', async ({ browser }) => { const context = await browser.newContext(); await context.close(); diff --git a/tests/library/headful.spec.ts b/tests/library/headful.spec.ts index bb437b33e6e54..24bb947691e1d 100644 --- a/tests/library/headful.spec.ts +++ b/tests/library/headful.spec.ts @@ -18,6 +18,8 @@ import { utils } from '../../packages/playwright-core/lib/coreBundle'; import { PNG } from 'playwright-core/lib/utilsBundle'; import { expect, playwrightTest as it } from '../config/browserTest'; +import type { BrowserContext, Page } from 'playwright-core'; + const { compare } = utils; it.skip(({ headless }) => headless, 'avoid popping windows in headless mode'); @@ -212,6 +214,69 @@ it('should not override viewport size when passed null', async function({ browse await context.close(); }); +it.describe('maximized viewport', () => { + it.skip(({ isBidi }) => isBidi, 'maximized viewport is not supported by BiDi'); + + async function windowIsMaximized(context: BrowserContext, page: Page, browserName: string) { + if (browserName === 'chromium') { + const client = await context.newCDPSession(page); + const { windowId } = await client.send('Browser.getWindowForTarget'); + const { bounds } = await client.send('Browser.getWindowBounds', { windowId }); + await client.detach(); + return bounds.windowState === 'maximized'; + } + return await page.evaluate(() => Math.abs(outerWidth - screen.availWidth) <= 20 && Math.abs(outerHeight - screen.availHeight) <= 20); + } + + it('should create context in maximized window without entering fullscreen', async ({ browser, browserName }) => { + const context = await browser.newContext({ viewport: 'maximized' }); + const page = await context.newPage(); + await expect.poll(() => windowIsMaximized(context, page, browserName)).toBe(true); + expect(await page.evaluate(() => document.fullscreenElement)).toBeNull(); + await context.close(); + }); + + it('should open shift-clicked links in maximized windows', async ({ browser, browserName, server }) => { + const context = await browser.newContext({ viewport: 'maximized' }); + const page = await context.newPage(); + await page.setContent(`link`); + const [popup] = await Promise.all([ + context.waitForEvent('page'), + page.click('a', { modifiers: ['Shift'] }), + ]); + await expect.poll(() => windowIsMaximized(context, popup, browserName)).toBe(true); + await context.close(); + }); + + it('should override explicitly sized popup geometry', async ({ browser, browserName, server }) => { + const context = await browser.newContext({ viewport: 'maximized' }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => window.open('', '_blank', 'left=100,top=100,width=320,height=320')), + ]); + await expect.poll(() => windowIsMaximized(context, popup, browserName)).toBe(true); + await context.close(); + }); + + it('should keep native window maximized when resizing viewport', async ({ browser, browserName }) => { + const context = await browser.newContext({ viewport: 'maximized' }); + const page = await context.newPage(); + const outerSize = await page.evaluate(() => ({ width: outerWidth, height: outerHeight })); + await page.setViewportSize({ width: 567, height: 345 }); + expect(await page.evaluate(() => ({ width: window.innerWidth, height: window.innerHeight }))).toEqual({ width: 567, height: 345 }); + expect(await page.evaluate(() => ({ width: outerWidth, height: outerHeight }))).toEqual(outerSize); + await expect.poll(() => windowIsMaximized(context, page, browserName)).toBe(true); + await context.close(); + }); + + it('should launch persistent context in maximized window', async ({ browserName, launchPersistent }) => { + const { context, page } = await launchPersistent({ viewport: 'maximized' }); + await expect.poll(() => windowIsMaximized(context, page, browserName)).toBe(true); + }); +}); + it('Page.bringToFront should work', async ({ browser }) => { const page1 = await browser.newPage(); await page1.setContent('Page1'); diff --git a/utils/generate_types/overrides-test.d.ts b/utils/generate_types/overrides-test.d.ts index e7de7655c4c52..9ceb4568b3c4d 100644 --- a/utils/generate_types/overrides-test.d.ts +++ b/utils/generate_types/overrides-test.d.ts @@ -302,7 +302,7 @@ export interface PlaywrightTestOptions { storageState: StorageState | undefined; timezoneId: string | undefined; userAgent: string | undefined; - viewport: ViewportSize | null; + viewport: ViewportSize | 'maximized' | null; baseURL: string | undefined; contextOptions: BrowserContextOptions; actionTimeout: number;