2020-08-03 13:41:48 -07:00
/ * *
* Copyright 2018 Google Inc . All rights reserved .
* Modifications copyright ( c ) Microsoft Corporation .
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
2020-08-18 23:00:45 -07:00
2024-08-19 10:29:23 -07:00
import os from 'os' ;
2022-03-25 15:05:50 -08:00
import { browserTest as it , expect } from '../config/browserTest' ;
2021-04-02 21:07:45 -07:00
it . describe ( 'mobile viewport' , ( ) = > {
2021-04-30 13:26:13 -07:00
it . skip ( ( { browserName } ) = > browserName === 'firefox' ) ;
2020-08-03 13:41:48 -07:00
2021-09-27 18:58:08 +02:00
it ( 'should support mobile emulation' , async ( { playwright , browser , server } ) = > {
2020-08-28 13:53:47 -07:00
const iPhone = playwright . devices [ 'iPhone 6' ] ;
const context = await browser . newContext ( { . . . iPhone } ) ;
const page = await context . newPage ( ) ;
await page . goto ( server . PREFIX + '/mobile.html' ) ;
expect ( await page . evaluate ( ( ) = > window . innerWidth ) ) . toBe ( 375 ) ;
2021-09-27 18:58:08 +02:00
await page . setViewportSize ( { width : 400 , height : 300 } ) ;
2020-08-28 13:53:47 -07:00
expect ( await page . evaluate ( ( ) = > window . innerWidth ) ) . toBe ( 400 ) ;
await context . close ( ) ;
} ) ;
2020-08-03 13:41:48 -07:00
2021-09-27 18:58:08 +02:00
it ( 'should support touch emulation' , async ( { playwright , browser , server } ) = > {
2020-08-28 13:53:47 -07:00
const iPhone = playwright . devices [ 'iPhone 6' ] ;
const context = await browser . newContext ( { . . . iPhone } ) ;
const page = await context . newPage ( ) ;
await page . goto ( server . PREFIX + '/mobile.html' ) ;
expect ( await page . evaluate ( ( ) = > 'ontouchstart' in window ) ) . toBe ( true ) ;
expect ( await page . evaluate ( dispatchTouch ) ) . toBe ( 'Received touch' ) ;
await context . close ( ) ;
2020-08-03 13:41:48 -07:00
2020-08-28 13:53:47 -07:00
function dispatchTouch() {
2023-10-23 09:31:30 -07:00
let fulfill ! : ( s : string ) = > void ;
2020-08-28 13:53:47 -07:00
const promise = new Promise ( x = > fulfill = x ) ;
window . ontouchstart = function ( e ) {
fulfill ( 'Received touch' ) ;
} ;
window . dispatchEvent ( new Event ( 'touchstart' ) ) ;
2020-08-03 13:41:48 -07:00
2020-08-28 13:53:47 -07:00
fulfill ( 'Did not receive touch' ) ;
2020-08-03 13:41:48 -07:00
2020-08-28 13:53:47 -07:00
return promise ;
}
} ) ;
2020-08-03 13:41:48 -07:00
2024-08-19 10:29:23 -07:00
it ( 'should be detectable by Modernizr' , async ( { playwright , browser , server , browserName , platform } ) = > {
it . skip ( browserName === 'webkit' && platform === 'darwin' && parseInt ( os . release ( ) , 10 ) === 22 , 'detect-touch.html uses Modernizr which uses WebGL. WebGL is not available in macOS-13 - https://bugs.webkit.org/show_bug.cgi?id=278277' ) ;
2020-08-28 13:53:47 -07:00
const iPhone = playwright . devices [ 'iPhone 6' ] ;
const context = await browser . newContext ( { . . . iPhone } ) ;
const page = await context . newPage ( ) ;
await page . goto ( server . PREFIX + '/detect-touch.html' ) ;
2023-10-23 09:31:30 -07:00
expect ( await page . evaluate ( ( ) = > document . body . textContent ! . trim ( ) ) ) . toBe ( 'YES' ) ;
2020-08-28 13:53:47 -07:00
await context . close ( ) ;
} ) ;
2020-08-03 13:41:48 -07:00
2024-08-19 10:29:23 -07:00
it ( 'should detect touch when applying viewport with touches' , async ( { browser , server , browserName , platform } ) = > {
it . skip ( browserName === 'webkit' && platform === 'darwin' && parseInt ( os . release ( ) , 10 ) === 22 , 'Modernizr uses WebGL. WebGL is not available in macOS-13 - https://bugs.webkit.org/show_bug.cgi?id=278277' ) ;
2020-08-28 13:53:47 -07:00
const context = await browser . newContext ( { viewport : { width : 800 , height : 600 } , hasTouch : true } ) ;
const page = await context . newPage ( ) ;
await page . goto ( server . EMPTY_PAGE ) ;
2021-09-27 18:58:08 +02:00
await page . addScriptTag ( { url : server.PREFIX + '/modernizr.js' } ) ;
2023-10-23 09:31:30 -07:00
expect ( await page . evaluate ( ( ) = > ( window as any ) [ 'Modernizr' ] . touchevents ) ) . toBe ( true ) ;
2020-08-28 13:53:47 -07:00
await context . close ( ) ;
} ) ;
2020-08-03 13:41:48 -07:00
2021-09-27 18:58:08 +02:00
it ( 'should support landscape emulation' , async ( { playwright , browser , server } ) = > {
2020-08-28 13:53:47 -07:00
const iPhone = playwright . devices [ 'iPhone 6' ] ;
const iPhoneLandscape = playwright . devices [ 'iPhone 6 landscape' ] ;
const context1 = await browser . newContext ( { . . . iPhone } ) ;
const page1 = await context1 . newPage ( ) ;
await page1 . goto ( server . PREFIX + '/mobile.html' ) ;
expect ( await page1 . evaluate ( ( ) = > matchMedia ( '(orientation: landscape)' ) . matches ) ) . toBe ( false ) ;
const context2 = await browser . newContext ( { . . . iPhoneLandscape } ) ;
const page2 = await context2 . newPage ( ) ;
expect ( await page2 . evaluate ( ( ) = > matchMedia ( '(orientation: landscape)' ) . matches ) ) . toBe ( true ) ;
await context1 . close ( ) ;
await context2 . close ( ) ;
} ) ;
2020-08-03 13:41:48 -07:00
2021-09-27 18:58:08 +02:00
it ( 'should support window.orientation emulation' , async ( { browser , server } ) = > {
2020-08-28 13:53:47 -07:00
const context = await browser . newContext ( { viewport : { width : 300 , height : 400 } , isMobile : true } ) ;
const page = await context . newPage ( ) ;
await page . goto ( server . PREFIX + '/mobile.html' ) ;
expect ( await page . evaluate ( ( ) = > window . orientation ) ) . toBe ( 0 ) ;
2021-09-27 18:58:08 +02:00
await page . setViewportSize ( { width : 400 , height : 300 } ) ;
2020-08-28 13:53:47 -07:00
expect ( await page . evaluate ( ( ) = > window . orientation ) ) . toBe ( 90 ) ;
await context . close ( ) ;
2020-08-03 13:41:48 -07:00
} ) ;
2021-09-27 18:58:08 +02:00
it ( 'should fire orientationchange event' , async ( { browser , server } ) = > {
2020-08-28 13:53:47 -07:00
const context = await browser . newContext ( { viewport : { width : 300 , height : 400 } , isMobile : true } ) ;
const page = await context . newPage ( ) ;
await page . goto ( server . PREFIX + '/mobile.html' ) ;
await page . evaluate ( ( ) = > {
let counter = 0 ;
window . addEventListener ( 'orientationchange' , ( ) = > console . log ( ++ counter ) ) ;
} ) ;
2020-08-03 13:41:48 -07:00
2020-08-28 13:53:47 -07:00
const event1 = page . waitForEvent ( 'console' ) ;
2021-09-27 18:58:08 +02:00
await page . setViewportSize ( { width : 400 , height : 300 } ) ;
2020-08-28 13:53:47 -07:00
expect ( ( await event1 ) . text ( ) ) . toBe ( '1' ) ;
2020-08-03 13:41:48 -07:00
2020-08-28 13:53:47 -07:00
const event2 = page . waitForEvent ( 'console' ) ;
2021-09-27 18:58:08 +02:00
await page . setViewportSize ( { width : 300 , height : 400 } ) ;
2020-08-28 13:53:47 -07:00
expect ( ( await event2 ) . text ( ) ) . toBe ( '2' ) ;
await context . close ( ) ;
} ) ;
2021-09-27 18:58:08 +02:00
it ( 'default mobile viewports to 980 width' , async ( { browser , server } ) = > {
const context = await browser . newContext ( { viewport : { width : 320 , height : 480 } , isMobile : true } ) ;
2020-08-28 13:53:47 -07:00
const page = await context . newPage ( ) ;
await page . goto ( server . PREFIX + '/empty.html' ) ;
expect ( await page . evaluate ( ( ) = > window . innerWidth ) ) . toBe ( 980 ) ;
await context . close ( ) ;
} ) ;
2020-08-03 13:41:48 -07:00
2021-09-27 18:58:08 +02:00
it ( 'respect meta viewport tag' , async ( { browser , server } ) = > {
const context = await browser . newContext ( { viewport : { width : 320 , height : 480 } , isMobile : true } ) ;
2020-08-28 13:53:47 -07:00
const page = await context . newPage ( ) ;
await page . goto ( server . PREFIX + '/mobile.html' ) ;
expect ( await page . evaluate ( ( ) = > window . innerWidth ) ) . toBe ( 320 ) ;
await context . close ( ) ;
} ) ;
2020-09-21 08:20:05 -07:00
2021-09-27 18:58:08 +02:00
it ( 'should emulate the hover media feature' , async ( { playwright , browser } ) = > {
2020-09-21 08:20:05 -07:00
const iPhone = playwright . devices [ 'iPhone 6' ] ;
const mobilepage = await browser . newPage ( { . . . iPhone } ) ;
expect ( await mobilepage . evaluate ( ( ) = > matchMedia ( '(hover: hover)' ) . matches ) ) . toBe ( false ) ;
expect ( await mobilepage . evaluate ( ( ) = > matchMedia ( '(hover: none)' ) . matches ) ) . toBe ( true ) ;
expect ( await mobilepage . evaluate ( ( ) = > matchMedia ( '(any-hover: hover)' ) . matches ) ) . toBe ( false ) ;
expect ( await mobilepage . evaluate ( ( ) = > matchMedia ( '(any-hover: none)' ) . matches ) ) . toBe ( true ) ;
expect ( await mobilepage . evaluate ( ( ) = > matchMedia ( '(pointer: coarse)' ) . matches ) ) . toBe ( true ) ;
expect ( await mobilepage . evaluate ( ( ) = > matchMedia ( '(pointer: fine)' ) . matches ) ) . toBe ( false ) ;
expect ( await mobilepage . evaluate ( ( ) = > matchMedia ( '(any-pointer: coarse)' ) . matches ) ) . toBe ( true ) ;
expect ( await mobilepage . evaluate ( ( ) = > matchMedia ( '(any-pointer: fine)' ) . matches ) ) . toBe ( false ) ;
await mobilepage . close ( ) ;
const desktopPage = await browser . newPage ( ) ;
expect ( await desktopPage . evaluate ( ( ) = > matchMedia ( '(hover: none)' ) . matches ) ) . toBe ( false ) ;
expect ( await desktopPage . evaluate ( ( ) = > matchMedia ( '(hover: hover)' ) . matches ) ) . toBe ( true ) ;
expect ( await desktopPage . evaluate ( ( ) = > matchMedia ( '(any-hover: none)' ) . matches ) ) . toBe ( false ) ;
expect ( await desktopPage . evaluate ( ( ) = > matchMedia ( '(any-hover: hover)' ) . matches ) ) . toBe ( true ) ;
expect ( await desktopPage . evaluate ( ( ) = > matchMedia ( '(pointer: coarse)' ) . matches ) ) . toBe ( false ) ;
expect ( await desktopPage . evaluate ( ( ) = > matchMedia ( '(pointer: fine)' ) . matches ) ) . toBe ( true ) ;
expect ( await desktopPage . evaluate ( ( ) = > matchMedia ( '(any-pointer: coarse)' ) . matches ) ) . toBe ( false ) ;
expect ( await desktopPage . evaluate ( ( ) = > matchMedia ( '(any-pointer: fine)' ) . matches ) ) . toBe ( true ) ;
await desktopPage . close ( ) ;
} ) ;
2021-05-05 19:10:28 -07:00
2021-09-27 18:58:08 +02:00
it ( 'mouse should work with mobile viewports and cross process navigations' , async ( { browser , server , browserName } ) = > {
2021-05-05 19:10:28 -07:00
// @see https://crbug.com/929806
2021-09-27 18:58:08 +02:00
const context = await browser . newContext ( { viewport : { width : 360 , height : 640 } , isMobile : true } ) ;
2021-05-05 19:10:28 -07:00
const page = await context . newPage ( ) ;
await page . goto ( server . EMPTY_PAGE ) ;
await page . goto ( server . CROSS_PROCESS_PREFIX + '/mobile.html' ) ;
await page . evaluate ( ( ) = > {
document . addEventListener ( 'click' , event = > {
2023-10-23 09:31:30 -07:00
( window as any ) [ 'result' ] = { x : event.clientX , y : event.clientY } ;
2021-05-05 19:10:28 -07:00
} ) ;
} ) ;
await page . mouse . click ( 30 , 40 ) ;
2021-09-27 18:58:08 +02:00
expect ( await page . evaluate ( 'result' ) ) . toEqual ( { x : 30 , y : 40 } ) ;
2021-05-05 19:10:28 -07:00
await context . close ( ) ;
} ) ;
2022-02-25 22:56:51 +01:00
it ( 'should scroll when emulating a mobile viewport' , async ( { browser , server , browserName } ) = > {
const context = await browser . newContext ( {
viewport : { 'width' : 1000 , 'height' : 600 } ,
isMobile : true ,
} ) ;
const page = await context . newPage ( ) ;
await page . goto ( server . PREFIX + '/input/scrollable.html' ) ;
await page . mouse . move ( 50 , 60 ) ;
const error = await page . mouse . wheel ( 0 , 100 ) . catch ( e = > e ) ;
if ( browserName === 'webkit' )
expect ( error . message ) . toContain ( 'Mouse wheel is not supported in mobile WebKit' ) ;
else
await page . waitForFunction ( 'window.scrollY === 100' ) ;
await context . close ( ) ;
} ) ;
2023-09-08 08:58:07 -07:00
2023-09-12 15:01:44 -07:00
it ( 'view scale should reset after navigation' , async ( { browser , browserName } ) = > {
2023-09-08 08:58:07 -07:00
it . info ( ) . annotations . push ( { type : 'issue' , description : 'https://github.com/microsoft/playwright/issues/26876' } ) ;
const context = await browser . newContext ( {
viewport : { width : 390 , height : 664 } ,
isMobile : true ,
} ) ;
const page = await context . newPage ( ) ;
await page . goto ( ` data:text/html,<meta name='viewport' content='device-width, initial-scale=1'><button>Mobile Viewport</button> ` ) ;
await page . route ( '**/button.html' , route = > {
void route . fulfill ( {
body : ` <body>
< button > Click me < / button >
< script >
window . clicks = [ ] ;
document . addEventListener ( 'click' , e = > {
const dot = document . createElement ( 'div' ) ;
dot . style . position = 'absolute' ;
dot . style . width = '10px' ;
dot . style . height = '10px' ;
dot . style . borderRadius = '5px' ;
dot . style . backgroundColor = 'red' ;
dot . style . left = e . pageX + 'px' ;
dot . style . top = e . pageY + 'px' ;
dot . textContent = 'x: ' + e . pageX + ' y: ' + e . pageY ;
document . body . appendChild ( dot ) ;
window . clicks . push ( { x : e.pageX , y : e.pageY } ) ;
} ) ;
< / script >
< / body > ` ,
contentType : 'text/html' ,
} ) ;
} ) ;
await page . goto ( 'http://localhost/button.html' ) ;
await page . getByText ( 'Click me' ) . click ( { force : true } ) ;
2023-10-23 09:31:30 -07:00
const box = ( await page . locator ( 'button' ) . boundingBox ( ) ) ! ;
2023-09-12 15:01:44 -07:00
const clicks = await page . evaluate ( ( ) = > ( window as any ) . clicks ) ;
expect ( clicks . length ) . toBe ( 1 ) ;
const [ { x , y } ] = clicks ;
const isClickInsideButton = box . x <= x && x <= box . x + box . width && box . y <= y && y <= box . y + box . height ;
expect ( isClickInsideButton ) . toBe ( true ) ;
2023-09-08 08:58:07 -07:00
await context . close ( ) ;
} ) ;
2020-08-03 13:41:48 -07:00
} ) ;