feat(size): emulate window.screen size (#5967)

This commit is contained in:
Pavel Feldman 2021-03-30 05:10:58 +08:00 committed by GitHub
parent 8c6822bd32
commit f1c0d09765
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 391 additions and 43 deletions

View File

@ -216,7 +216,18 @@ Toggles bypassing page's Content-Security-Policy.
- `width` <[int]> page width in pixels. - `width` <[int]> page width in pixels.
- `height` <[int]> page height in pixels. - `height` <[int]> page height in pixels.
Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. `null` disables the default viewport. Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. `null` disables the default viewport.
## context-option-screen
* langs:
- alias-java: screenSize
- alias-csharp: screenSize
- `screen` <[Object]>
- `width` <[int]> page width in pixels.
- `height` <[int]> page height in pixels.
Emulates consistent window screen size available inside web page via `window.screen`. Is only used when the
[`option: viewport`] is set.
## evaluate-expression ## evaluate-expression
- `expression` <[string]> - `expression` <[string]>
@ -546,6 +557,7 @@ is considered matching if all specified properties match.
- %%-context-option-bypasscsp-%% - %%-context-option-bypasscsp-%%
- %%-context-option-viewport-%% - %%-context-option-viewport-%%
- %%-python-context-option-viewport-%% - %%-python-context-option-viewport-%%
- %%-context-option-screen-%%
- %%-python-context-option-no-viewport-%% - %%-python-context-option-no-viewport-%%
- %%-context-option-useragent-%% - %%-context-option-useragent-%%
- %%-context-option-devicescalefactor-%% - %%-context-option-devicescalefactor-%%

View File

@ -166,6 +166,10 @@ export type PlaywrightInitializer = {
width: number, width: number,
height: number, height: number,
}, },
screen?: {
width: number,
height: number,
},
deviceScaleFactor: number, deviceScaleFactor: number,
isMobile: boolean, isMobile: boolean,
hasTouch: boolean, hasTouch: boolean,
@ -294,6 +298,10 @@ export type BrowserTypeLaunchPersistentContextParams = {
width: number, width: number,
height: number, height: number,
}, },
screen?: {
width: number,
height: number,
},
ignoreHTTPSErrors?: boolean, ignoreHTTPSErrors?: boolean,
javaScriptEnabled?: boolean, javaScriptEnabled?: boolean,
bypassCSP?: boolean, bypassCSP?: boolean,
@ -359,6 +367,10 @@ export type BrowserTypeLaunchPersistentContextOptions = {
width: number, width: number,
height: number, height: number,
}, },
screen?: {
width: number,
height: number,
},
ignoreHTTPSErrors?: boolean, ignoreHTTPSErrors?: boolean,
javaScriptEnabled?: boolean, javaScriptEnabled?: boolean,
bypassCSP?: boolean, bypassCSP?: boolean,
@ -439,6 +451,10 @@ export type BrowserNewContextParams = {
width: number, width: number,
height: number, height: number,
}, },
screen?: {
width: number,
height: number,
},
ignoreHTTPSErrors?: boolean, ignoreHTTPSErrors?: boolean,
javaScriptEnabled?: boolean, javaScriptEnabled?: boolean,
bypassCSP?: boolean, bypassCSP?: boolean,
@ -492,6 +508,10 @@ export type BrowserNewContextOptions = {
width: number, width: number,
height: number, height: number,
}, },
screen?: {
width: number,
height: number,
},
ignoreHTTPSErrors?: boolean, ignoreHTTPSErrors?: boolean,
javaScriptEnabled?: boolean, javaScriptEnabled?: boolean,
bypassCSP?: boolean, bypassCSP?: boolean,

View File

@ -275,6 +275,11 @@ ContextOptions:
properties: properties:
width: number width: number
height: number height: number
screen:
type: object?
properties:
width: number
height: number
ignoreHTTPSErrors: boolean? ignoreHTTPSErrors: boolean?
javaScriptEnabled: boolean? javaScriptEnabled: boolean?
bypassCSP: boolean? bypassCSP: boolean?
@ -351,6 +356,11 @@ Playwright:
properties: properties:
width: number width: number
height: number height: number
screen:
type: object?
properties:
width: number
height: number
deviceScaleFactor: number deviceScaleFactor: number
isMobile: boolean isMobile: boolean
hasTouch: boolean hasTouch: boolean

View File

@ -204,6 +204,10 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
width: tNumber, width: tNumber,
height: tNumber, height: tNumber,
})), })),
screen: tOptional(tObject({
width: tNumber,
height: tNumber,
})),
ignoreHTTPSErrors: tOptional(tBoolean), ignoreHTTPSErrors: tOptional(tBoolean),
javaScriptEnabled: tOptional(tBoolean), javaScriptEnabled: tOptional(tBoolean),
bypassCSP: tOptional(tBoolean), bypassCSP: tOptional(tBoolean),
@ -257,6 +261,10 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
width: tNumber, width: tNumber,
height: tNumber, height: tNumber,
})), })),
screen: tOptional(tObject({
width: tNumber,
height: tNumber,
})),
ignoreHTTPSErrors: tOptional(tBoolean), ignoreHTTPSErrors: tOptional(tBoolean),
javaScriptEnabled: tOptional(tBoolean), javaScriptEnabled: tOptional(tBoolean),
bypassCSP: tOptional(tBoolean), bypassCSP: tOptional(tBoolean),

View File

@ -87,7 +87,7 @@ export class CRPage implements PageDelegate {
const features = opener._nextWindowOpenPopupFeatures.shift() || []; const features = opener._nextWindowOpenPopupFeatures.shift() || [];
const viewportSize = helper.getViewportSizeFromWindowFeatures(features); const viewportSize = helper.getViewportSizeFromWindowFeatures(features);
if (viewportSize) if (viewportSize)
this._page._state.viewportSize = viewportSize; this._page._state.emulatedSize = { viewport: viewportSize, screen: viewportSize };
} }
this._pagePromise = this._mainFrameSession._initialize(hasUIWindow).then(() => this._initializedPage = this._page).catch(e => e); this._pagePromise = this._mainFrameSession._initialize(hasUIWindow).then(() => this._initializedPage = this._page).catch(e => e);
} }
@ -162,8 +162,8 @@ export class CRPage implements PageDelegate {
await this._forAllFrameSessions(frame => frame._updateHttpCredentials(false)); await this._forAllFrameSessions(frame => frame._updateHttpCredentials(false));
} }
async setViewportSize(viewportSize: types.Size): Promise<void> { async setEmulatedSize(emulatedSize: types.EmulatedSize): Promise<void> {
assert(this._page._state.viewportSize === viewportSize); assert(this._page._state.emulatedSize === emulatedSize);
await this._mainFrameSession._updateViewport(); await this._mainFrameSession._updateViewport();
} }
@ -899,17 +899,19 @@ class FrameSession {
return; return;
assert(this._isMainFrame()); assert(this._isMainFrame());
const options = this._crPage._browserContext._options; const options = this._crPage._browserContext._options;
const viewportSize = this._page._state.viewportSize; const emulatedSize = this._page._state.emulatedSize;
if (viewportSize === null) if (emulatedSize === null)
return; return;
const viewportSize = emulatedSize.viewport;
const screenSize = emulatedSize.screen;
const isLandscape = viewportSize.width > viewportSize.height; const isLandscape = viewportSize.width > viewportSize.height;
const promises = [ const promises = [
this._client.send('Emulation.setDeviceMetricsOverride', { this._client.send('Emulation.setDeviceMetricsOverride', {
mobile: !!options.isMobile, mobile: !!options.isMobile,
width: viewportSize.width, width: viewportSize.width,
height: viewportSize.height, height: viewportSize.height,
screenWidth: viewportSize.width, screenWidth: screenSize.width,
screenHeight: viewportSize.height, screenHeight: screenSize.height,
deviceScaleFactor: options.deviceScaleFactor || 1, deviceScaleFactor: options.deviceScaleFactor || 1,
screenOrientation: isLandscape ? { angle: 90, type: 'landscapePrimary' } : { angle: 0, type: 'portraitPrimary' }, screenOrientation: isLandscape ? { angle: 90, type: 'landscapePrimary' } : { angle: 0, type: 'portraitPrimary' },
}), }),

View File

@ -439,10 +439,14 @@ module.exports = {
}, },
'iPhone 11': { 'iPhone 11': {
'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1', 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1',
'viewport': { 'screen': {
'width': 414, 'width': 414,
'height': 896 'height': 896
}, },
'viewport': {
'width': 414,
'height': 715
},
'deviceScaleFactor': 2, 'deviceScaleFactor': 2,
'isMobile': true, 'isMobile': true,
'hasTouch': true, 'hasTouch': true,
@ -450,9 +454,13 @@ module.exports = {
}, },
'iPhone 11 landscape': { 'iPhone 11 landscape': {
'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1', 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1',
'screen': {
'width': 414,
'height': 896
},
'viewport': { 'viewport': {
'width': 896, 'width': 800,
'height': 414 'height': 364
}, },
'deviceScaleFactor': 2, 'deviceScaleFactor': 2,
'isMobile': true, 'isMobile': true,
@ -461,10 +469,14 @@ module.exports = {
}, },
'iPhone 11 Pro': { 'iPhone 11 Pro': {
'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1', 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1',
'viewport': { 'screen': {
'width': 375, 'width': 375,
'height': 812 'height': 812
}, },
'viewport': {
'width': 375,
'height': 635
},
'deviceScaleFactor': 3, 'deviceScaleFactor': 3,
'isMobile': true, 'isMobile': true,
'hasTouch': true, 'hasTouch': true,
@ -472,9 +484,13 @@ module.exports = {
}, },
'iPhone 11 Pro landscape': { 'iPhone 11 Pro landscape': {
'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1', 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1',
'screen': {
'width': 375,
'height': 812
},
'viewport': { 'viewport': {
'width': 812, 'width': 724,
'height': 375 'height': 325
}, },
'deviceScaleFactor': 3, 'deviceScaleFactor': 3,
'isMobile': true, 'isMobile': true,
@ -483,10 +499,14 @@ module.exports = {
}, },
'iPhone 11 Pro Max': { 'iPhone 11 Pro Max': {
'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1', 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1',
'viewport': { 'screen': {
'width': 414, 'width': 414,
'height': 896 'height': 896
}, },
'viewport': {
'width': 414,
'height': 715
},
'deviceScaleFactor': 3, 'deviceScaleFactor': 3,
'isMobile': true, 'isMobile': true,
'hasTouch': true, 'hasTouch': true,
@ -494,9 +514,103 @@ module.exports = {
}, },
'iPhone 11 Pro Max landscape': { 'iPhone 11 Pro Max landscape': {
'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1', 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1',
'screen': {
'width': 414,
'height': 896
},
'viewport': { 'viewport': {
'width': 896, 'width': 808,
'height': 414 'height': 364
},
'deviceScaleFactor': 3,
'isMobile': true,
'hasTouch': true,
'defaultBrowserType': 'webkit'
},
'iPhone 12': {
'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1',
'screen': {
'width': 390,
'height': 844
},
'viewport': {
'width': 390,
'height': 664
},
'deviceScaleFactor': 3,
'isMobile': true,
'hasTouch': true,
'defaultBrowserType': 'webkit'
},
'iPhone 12 landscape': {
'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1',
'screen': {
'width': 390,
'height': 844
},
'viewport': {
'width': 750,
'height': 340
},
'deviceScaleFactor': 3,
'isMobile': true,
'hasTouch': true,
'defaultBrowserType': 'webkit'
},
'iPhone 12 Pro': {
'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1',
'screen': {
'width': 390,
'height': 844
},
'viewport': {
'width': 390,
'height': 664
},
'deviceScaleFactor': 3,
'isMobile': true,
'hasTouch': true,
'defaultBrowserType': 'webkit'
},
'iPhone 12 Pro landscape': {
'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1',
'screen': {
'width': 390,
'height': 844
},
'viewport': {
'width': 750,
'height': 340
},
'deviceScaleFactor': 3,
'isMobile': true,
'hasTouch': true,
'defaultBrowserType': 'webkit'
},
'iPhone 12 Pro Max': {
'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1',
'screen': {
'width': 428,
'height': 926
},
'viewport': {
'width': 428,
'height': 746
},
'deviceScaleFactor': 3,
'isMobile': true,
'hasTouch': true,
'defaultBrowserType': 'webkit'
},
'iPhone 12 Pro Max landscape': {
'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1',
'screen': {
'width': 428,
'height': 926
},
'viewport': {
'width': 832,
'height': 378
}, },
'deviceScaleFactor': 3, 'deviceScaleFactor': 3,
'isMobile': true, 'isMobile': true,
@ -854,5 +968,65 @@ module.exports = {
'isMobile': true, 'isMobile': true,
'hasTouch': true, 'hasTouch': true,
'defaultBrowserType': 'chromium' 'defaultBrowserType': 'chromium'
} },
'Pixel 4a (5G)': {
'userAgent': 'Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.105 Mobile Safari/537.36',
'screen': {
'width': 412,
'height': 892
},
'viewport': {
'width': 412,
'height': 765
},
'deviceScaleFactor': 2.63,
'isMobile': true,
'hasTouch': true,
'defaultBrowserType': 'chromium'
},
'Pixel 4a (5G) landscape': {
'userAgent': 'Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.105 Mobile Safari/537.36',
'screen': {
'height': 892,
'width': 412
},
'viewport': {
'width': 840,
'height': 312
},
'deviceScaleFactor': 2.63,
'isMobile': true,
'hasTouch': true,
'defaultBrowserType': 'chromium'
},
'Pixel 5': {
'userAgent': 'Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.105 Mobile Safari/537.36',
'screen': {
'width': 393,
'height': 851
},
'viewport': {
'width': 393,
'height': 727
},
'deviceScaleFactor': 2.75,
'isMobile': true,
'hasTouch': true,
'defaultBrowserType': 'chromium'
},
'Pixel 5 landscape': {
'userAgent': 'Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.105 Mobile Safari/537.36',
'screen': {
'width': 851,
'height': 393
},
'viewport': {
'width': 802,
'height': 293
},
'deviceScaleFactor': 2.75,
'isMobile': true,
'hasTouch': true,
'defaultBrowserType': 'chromium'
},
}; };

View File

@ -315,12 +315,12 @@ export class FFPage implements PageDelegate {
await this._session.send('Network.setExtraHTTPHeaders', { headers: this._page._state.extraHTTPHeaders || [] }); await this._session.send('Network.setExtraHTTPHeaders', { headers: this._page._state.extraHTTPHeaders || [] });
} }
async setViewportSize(viewportSize: types.Size): Promise<void> { async setEmulatedSize(emulatedSize: types.EmulatedSize): Promise<void> {
assert(this._page._state.viewportSize === viewportSize); assert(this._page._state.emulatedSize === emulatedSize);
await this._session.send('Page.setViewportSize', { await this._session.send('Page.setViewportSize', {
viewportSize: { viewportSize: {
width: viewportSize.width, width: emulatedSize.viewport.width,
height: viewportSize.height, height: emulatedSize.viewport.height,
}, },
}); });
} }

View File

@ -52,7 +52,7 @@ export interface PageDelegate {
navigateFrame(frame: frames.Frame, url: string, referrer: string | undefined): Promise<frames.GotoResult>; navigateFrame(frame: frames.Frame, url: string, referrer: string | undefined): Promise<frames.GotoResult>;
updateExtraHTTPHeaders(): Promise<void>; updateExtraHTTPHeaders(): Promise<void>;
setViewportSize(viewportSize: types.Size): Promise<void>; setEmulatedSize(emulatedSize: types.EmulatedSize): Promise<void>;
updateEmulateMedia(): Promise<void>; updateEmulateMedia(): Promise<void>;
updateRequestInterception(): Promise<void>; updateRequestInterception(): Promise<void>;
setFileChooserIntercepted(enabled: boolean): Promise<void>; setFileChooserIntercepted(enabled: boolean): Promise<void>;
@ -86,7 +86,7 @@ export interface PageDelegate {
} }
type PageState = { type PageState = {
viewportSize: types.Size | null; emulatedSize: { screen: types.Size, viewport: types.Size } | null;
mediaType: types.MediaType | null; mediaType: types.MediaType | null;
colorScheme: types.ColorScheme | null; colorScheme: types.ColorScheme | null;
extraHTTPHeaders: types.HeadersArray | null; extraHTTPHeaders: types.HeadersArray | null;
@ -163,7 +163,7 @@ export class Page extends SdkObject {
this._crashedPromise = new Promise(f => this._crashedCallback = f); this._crashedPromise = new Promise(f => this._crashedCallback = f);
this._browserContext = browserContext; this._browserContext = browserContext;
this._state = { this._state = {
viewportSize: browserContext._options.viewport || null, emulatedSize: browserContext._options.viewport ? { viewport: browserContext._options.viewport, screen: browserContext._options.screen || browserContext._options.viewport } : null,
mediaType: null, mediaType: null,
colorScheme: null, colorScheme: null,
extraHTTPHeaders: null, extraHTTPHeaders: null,
@ -369,13 +369,13 @@ export class Page extends SdkObject {
} }
async setViewportSize(viewportSize: types.Size) { async setViewportSize(viewportSize: types.Size) {
this._state.viewportSize = { ...viewportSize }; this._state.emulatedSize = { viewport: { ...viewportSize }, screen: { ...viewportSize } };
await this._delegate.setViewportSize(this._state.viewportSize); await this._delegate.setEmulatedSize(this._state.emulatedSize);
await this._doSlowMo(); await this._doSlowMo();
} }
viewportSize(): types.Size | null { viewportSize(): types.Size | null {
return this._state.viewportSize; return this._state.emulatedSize?.viewport || null;
} }
async bringToFront(): Promise<void> { async bringToFront(): Promise<void> {

View File

@ -214,9 +214,12 @@ export type SetNetworkCookieParam = {
sameSite?: 'Strict' | 'Lax' | 'None' sameSite?: 'Strict' | 'Lax' | 'None'
}; };
export type EmulatedSize = { viewport: Size, screen: Size };
export type BrowserContextOptions = { export type BrowserContextOptions = {
sdkLanguage: string, sdkLanguage: string,
viewport?: Size, viewport?: Size,
screen?: Size,
noDefaultViewport?: boolean, noDefaultViewport?: boolean,
ignoreHTTPSErrors?: boolean, ignoreHTTPSErrors?: boolean,
javaScriptEnabled?: boolean, javaScriptEnabled?: boolean,

View File

@ -97,7 +97,7 @@ export class WKPage implements PageDelegate {
const viewportSize = helper.getViewportSizeFromWindowFeatures(opener._nextWindowOpenPopupFeatures); const viewportSize = helper.getViewportSizeFromWindowFeatures(opener._nextWindowOpenPopupFeatures);
opener._nextWindowOpenPopupFeatures = undefined; opener._nextWindowOpenPopupFeatures = undefined;
if (viewportSize) if (viewportSize)
this._page._state.viewportSize = viewportSize; this._page._state.emulatedSize = { viewport: viewportSize, screen: viewportSize };
} }
} }
@ -181,10 +181,10 @@ export class WKPage implements PageDelegate {
this._page.frames().map(frame => frame.evaluateExpression(bootstrapScript, false, undefined, 'main').catch(e => {})); this._page.frames().map(frame => frame.evaluateExpression(bootstrapScript, false, undefined, 'main').catch(e => {}));
if (contextOptions.bypassCSP) if (contextOptions.bypassCSP)
promises.push(session.send('Page.setBypassCSP', { enabled: true })); promises.push(session.send('Page.setBypassCSP', { enabled: true }));
if (this._page._state.viewportSize) { if (this._page._state.emulatedSize) {
promises.push(session.send('Page.setScreenSizeOverride', { promises.push(session.send('Page.setScreenSizeOverride', {
width: this._page._state.viewportSize.width, width: this._page._state.emulatedSize.screen.width,
height: this._page._state.viewportSize.height, height: this._page._state.emulatedSize.screen.height,
})); }));
} }
promises.push(this.updateEmulateMedia()); promises.push(this.updateEmulateMedia());
@ -603,8 +603,8 @@ export class WKPage implements PageDelegate {
await this._forAllSessions(session => WKPage._setEmulateMedia(session, this._page._state.mediaType, colorScheme)); await this._forAllSessions(session => WKPage._setEmulateMedia(session, this._page._state.mediaType, colorScheme));
} }
async setViewportSize(viewportSize: types.Size): Promise<void> { async setEmulatedSize(emulatedSize: types.EmulatedSize): Promise<void> {
assert(this._page._state.viewportSize === viewportSize); assert(this._page._state.emulatedSize === emulatedSize);
await this._updateViewport(); await this._updateViewport();
} }
@ -616,9 +616,11 @@ export class WKPage implements PageDelegate {
async _updateViewport(): Promise<void> { async _updateViewport(): Promise<void> {
const options = this._browserContext._options; const options = this._browserContext._options;
const viewportSize = this._page._state.viewportSize; const deviceSize = this._page._state.emulatedSize;
if (viewportSize === null) if (deviceSize === null)
return; return;
const viewportSize = deviceSize.viewport;
const screenSize = deviceSize.screen;
const promises: Promise<any>[] = [ const promises: Promise<any>[] = [
this._pageProxySession.send('Emulation.setDeviceMetricsOverride', { this._pageProxySession.send('Emulation.setDeviceMetricsOverride', {
width: viewportSize.width, width: viewportSize.width,
@ -627,8 +629,8 @@ export class WKPage implements PageDelegate {
deviceScaleFactor: options.deviceScaleFactor || 1 deviceScaleFactor: options.deviceScaleFactor || 1
}), }),
this._session.send('Page.setScreenSizeOverride', { this._session.send('Page.setScreenSizeOverride', {
width: viewportSize.width, width: screenSize.width,
height: viewportSize.height, height: screenSize.height,
}), }),
]; ];
if (options.isMobile) { if (options.isMobile) {

View File

@ -114,4 +114,47 @@ describe('device', (suite, { browserName }) => {
await context.close(); await context.close();
}); });
it('should emulate viewport and screen size', async ({server, contextFactory, playwright, contextOptions}) => {
const device = playwright.devices['iPhone 12'];
const context = await contextFactory({
...contextOptions,
...device,
});
const page = await context.newPage();
await page.setContent(`<meta name="viewport" content="width=device-width, user-scalable=no" />`);
expect(await page.evaluate(() => ({
width: window.screen.width,
height: window.screen.height
}))).toEqual({ width: 390, height: 844 });
expect(await page.evaluate(() => ({
width: window.innerWidth,
height: window.innerHeight
}))).toEqual({ width: 390, height: 664 });
await context.close();
});
it('should emulate viewport without screen size', async ({server, contextFactory, playwright, contextOptions}) => {
const device = playwright.devices['iPhone 6'];
const context = await contextFactory({
...contextOptions,
...device,
});
const page = await context.newPage();
await page.setContent(`<meta name="viewport" content="width=device-width, user-scalable=no" />`);
expect(await page.evaluate(() => ({
width: window.screen.width,
height: window.screen.height
}))).toEqual({ width: 375, height: 667 });
expect(await page.evaluate(() => ({
width: window.innerWidth,
height: window.innerHeight
}))).toEqual({ width: 375, height: 667 });
await context.close();
});
}); });

View File

@ -69,7 +69,7 @@ it('should print the correct context options when using a device and additional
const expectedResult = `BrowserContext context = browser.newContext(new Browser.NewContextOptions() const expectedResult = `BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setColorScheme(ColorScheme.LIGHT) .setColorScheme(ColorScheme.LIGHT)
.setUserAgent("Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1") .setUserAgent("Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1")
.setViewportSize(414, 896) .setViewportSize(414, 715)
.setDeviceScaleFactor(2) .setDeviceScaleFactor(2)
.setIsMobile(true) .setIsMobile(true)
.setHasTouch(true));`; .setHasTouch(true));`;

82
types/types.d.ts vendored
View File

@ -6576,6 +6576,22 @@ export interface BrowserType<Browser> {
}; };
}; };
/**
* Emulates consistent window screen size available inside web page via `window.screen`. Is only used when the `viewport`
* is set.
*/
screen?: {
/**
* page width in pixels.
*/
width: number;
/**
* page height in pixels.
*/
height: number;
};
/** /**
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on. * Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
* Defaults to 0. * Defaults to 0.
@ -6623,7 +6639,7 @@ export interface BrowserType<Browser> {
videosPath?: string; videosPath?: string;
/** /**
* Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. `null` disables the default viewport. * Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. `null` disables the default viewport.
*/ */
viewport?: null|{ viewport?: null|{
/** /**
@ -7666,6 +7682,22 @@ export interface AndroidDevice {
}; };
}; };
/**
* Emulates consistent window screen size available inside web page via `window.screen`. Is only used when the `viewport`
* is set.
*/
screen?: {
/**
* page width in pixels.
*/
width: number;
/**
* page height in pixels.
*/
height: number;
};
/** /**
* Changes the timezone of the context. See * Changes the timezone of the context. See
* [ICU's metaZones.txt](https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1) * [ICU's metaZones.txt](https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1)
@ -7701,7 +7733,7 @@ export interface AndroidDevice {
videosPath?: string; videosPath?: string;
/** /**
* Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. `null` disables the default viewport. * Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. `null` disables the default viewport.
*/ */
viewport?: null|{ viewport?: null|{
/** /**
@ -8418,6 +8450,22 @@ export interface Browser extends EventEmitter {
}; };
}; };
/**
* Emulates consistent window screen size available inside web page via `window.screen`. Is only used when the `viewport`
* is set.
*/
screen?: {
/**
* page width in pixels.
*/
width: number;
/**
* page height in pixels.
*/
height: number;
};
/** /**
* Populates context with given storage state. This option can be used to initialize context with logged-in information * Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via * obtained via
@ -8518,7 +8566,7 @@ export interface Browser extends EventEmitter {
videosPath?: string; videosPath?: string;
/** /**
* Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. `null` disables the default viewport. * Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. `null` disables the default viewport.
*/ */
viewport?: null|{ viewport?: null|{
/** /**
@ -10500,6 +10548,22 @@ export interface BrowserContextOptions {
}; };
}; };
/**
* Emulates consistent window screen size available inside web page via `window.screen`. Is only used when the `viewport`
* is set.
*/
screen?: {
/**
* page width in pixels.
*/
width: number;
/**
* page height in pixels.
*/
height: number;
};
/** /**
* Populates context with given storage state. This option can be used to initialize context with logged-in information * Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via * obtained via
@ -10600,7 +10664,7 @@ export interface BrowserContextOptions {
videosPath?: string; videosPath?: string;
/** /**
* Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. `null` disables the default viewport. * Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. `null` disables the default viewport.
*/ */
viewport?: null|ViewportSize; viewport?: null|ViewportSize;
} }
@ -10907,6 +10971,12 @@ type Devices = {
"iPhone 11 Pro landscape": DeviceDescriptor; "iPhone 11 Pro landscape": DeviceDescriptor;
"iPhone 11 Pro Max": DeviceDescriptor; "iPhone 11 Pro Max": DeviceDescriptor;
"iPhone 11 Pro Max landscape": DeviceDescriptor; "iPhone 11 Pro Max landscape": DeviceDescriptor;
"iPhone 12": DeviceDescriptor;
"iPhone 12 landscape": DeviceDescriptor;
"iPhone 12 Pro": DeviceDescriptor;
"iPhone 12 Pro landscape": DeviceDescriptor;
"iPhone 12 Pro Max": DeviceDescriptor;
"iPhone 12 Pro Max landscape": DeviceDescriptor;
"JioPhone 2": DeviceDescriptor; "JioPhone 2": DeviceDescriptor;
"JioPhone 2 landscape": DeviceDescriptor; "JioPhone 2 landscape": DeviceDescriptor;
"Kindle Fire HDX": DeviceDescriptor; "Kindle Fire HDX": DeviceDescriptor;
@ -10939,5 +11009,9 @@ type Devices = {
"Pixel 2 landscape": DeviceDescriptor; "Pixel 2 landscape": DeviceDescriptor;
"Pixel 2 XL": DeviceDescriptor; "Pixel 2 XL": DeviceDescriptor;
"Pixel 2 XL landscape": DeviceDescriptor; "Pixel 2 XL landscape": DeviceDescriptor;
"Pixel 4a (5G)": DeviceDescriptor;
"Pixel 4a (5G) landscape": DeviceDescriptor;
"Pixel 5": DeviceDescriptor;
"Pixel 5 landscape": DeviceDescriptor;
[key: string]: DeviceDescriptor; [key: string]: DeviceDescriptor;
} }