1/** 2 * Copyright 2018 Google Inc. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17import expect from 'expect'; 18import { 19 getTestState, 20 setupTestBrowserHooks, 21 setupTestPageAndContextHooks, 22 itFailsFirefox, 23 describeFailsFirefox, 24} from './mocha-utils'; // eslint-disable-line import/extensions 25 26describe('Emulation', () => { 27 setupTestBrowserHooks(); 28 setupTestPageAndContextHooks(); 29 let iPhone; 30 let iPhoneLandscape; 31 32 before(() => { 33 const { puppeteer } = getTestState(); 34 iPhone = puppeteer.devices['iPhone 6']; 35 iPhoneLandscape = puppeteer.devices['iPhone 6 landscape']; 36 }); 37 38 describe('Page.viewport', function () { 39 it('should get the proper viewport size', async () => { 40 const { page } = getTestState(); 41 42 expect(page.viewport()).toEqual({ width: 800, height: 600 }); 43 await page.setViewport({ width: 123, height: 456 }); 44 expect(page.viewport()).toEqual({ width: 123, height: 456 }); 45 }); 46 it('should support mobile emulation', async () => { 47 const { page, server } = getTestState(); 48 49 await page.goto(server.PREFIX + '/mobile.html'); 50 expect(await page.evaluate(() => window.innerWidth)).toBe(800); 51 await page.setViewport(iPhone.viewport); 52 expect(await page.evaluate(() => window.innerWidth)).toBe(375); 53 await page.setViewport({ width: 400, height: 300 }); 54 expect(await page.evaluate(() => window.innerWidth)).toBe(400); 55 }); 56 it('should support touch emulation', async () => { 57 const { page, server } = getTestState(); 58 59 await page.goto(server.PREFIX + '/mobile.html'); 60 expect(await page.evaluate(() => 'ontouchstart' in window)).toBe(false); 61 await page.setViewport(iPhone.viewport); 62 expect(await page.evaluate(() => 'ontouchstart' in window)).toBe(true); 63 expect(await page.evaluate(dispatchTouch)).toBe('Received touch'); 64 await page.setViewport({ width: 100, height: 100 }); 65 expect(await page.evaluate(() => 'ontouchstart' in window)).toBe(false); 66 67 function dispatchTouch() { 68 let fulfill; 69 const promise = new Promise((x) => (fulfill = x)); 70 window.ontouchstart = () => { 71 fulfill('Received touch'); 72 }; 73 window.dispatchEvent(new Event('touchstart')); 74 75 fulfill('Did not receive touch'); 76 77 return promise; 78 } 79 }); 80 it('should be detectable by Modernizr', async () => { 81 const { page, server } = getTestState(); 82 83 await page.goto(server.PREFIX + '/detect-touch.html'); 84 expect(await page.evaluate(() => document.body.textContent.trim())).toBe( 85 'NO' 86 ); 87 await page.setViewport(iPhone.viewport); 88 await page.goto(server.PREFIX + '/detect-touch.html'); 89 expect(await page.evaluate(() => document.body.textContent.trim())).toBe( 90 'YES' 91 ); 92 }); 93 it('should detect touch when applying viewport with touches', async () => { 94 const { page, server } = getTestState(); 95 96 await page.setViewport({ width: 800, height: 600, hasTouch: true }); 97 await page.addScriptTag({ url: server.PREFIX + '/modernizr.js' }); 98 expect(await page.evaluate(() => globalThis.Modernizr.touchevents)).toBe( 99 true 100 ); 101 }); 102 it('should support landscape emulation', async () => { 103 const { page, server } = getTestState(); 104 105 await page.goto(server.PREFIX + '/mobile.html'); 106 expect(await page.evaluate(() => screen.orientation.type)).toBe( 107 'portrait-primary' 108 ); 109 await page.setViewport(iPhoneLandscape.viewport); 110 expect(await page.evaluate(() => screen.orientation.type)).toBe( 111 'landscape-primary' 112 ); 113 await page.setViewport({ width: 100, height: 100 }); 114 expect(await page.evaluate(() => screen.orientation.type)).toBe( 115 'portrait-primary' 116 ); 117 }); 118 }); 119 120 describe('Page.emulate', function () { 121 it('should work', async () => { 122 const { page, server } = getTestState(); 123 124 await page.goto(server.PREFIX + '/mobile.html'); 125 await page.emulate(iPhone); 126 expect(await page.evaluate(() => window.innerWidth)).toBe(375); 127 expect(await page.evaluate(() => navigator.userAgent)).toContain( 128 'iPhone' 129 ); 130 }); 131 it('should support clicking', async () => { 132 const { page, server } = getTestState(); 133 134 await page.emulate(iPhone); 135 await page.goto(server.PREFIX + '/input/button.html'); 136 const button = await page.$('button'); 137 await page.evaluate( 138 (button: HTMLElement) => (button.style.marginTop = '200px'), 139 button 140 ); 141 await button.click(); 142 expect(await page.evaluate(() => globalThis.result)).toBe('Clicked'); 143 }); 144 }); 145 146 describe('Page.emulateMediaType', function () { 147 it('should work', async () => { 148 const { page } = getTestState(); 149 150 expect(await page.evaluate(() => matchMedia('screen').matches)).toBe( 151 true 152 ); 153 expect(await page.evaluate(() => matchMedia('print').matches)).toBe( 154 false 155 ); 156 await page.emulateMediaType('print'); 157 expect(await page.evaluate(() => matchMedia('screen').matches)).toBe( 158 false 159 ); 160 expect(await page.evaluate(() => matchMedia('print').matches)).toBe(true); 161 await page.emulateMediaType(null); 162 expect(await page.evaluate(() => matchMedia('screen').matches)).toBe( 163 true 164 ); 165 expect(await page.evaluate(() => matchMedia('print').matches)).toBe( 166 false 167 ); 168 }); 169 it('should throw in case of bad argument', async () => { 170 const { page } = getTestState(); 171 172 let error = null; 173 await page.emulateMediaType('bad').catch((error_) => (error = error_)); 174 expect(error.message).toBe('Unsupported media type: bad'); 175 }); 176 }); 177 178 describe('Page.emulateMediaFeatures', function () { 179 it('should work', async () => { 180 const { page } = getTestState(); 181 182 await page.emulateMediaFeatures([ 183 { name: 'prefers-reduced-motion', value: 'reduce' }, 184 ]); 185 expect( 186 await page.evaluate( 187 () => matchMedia('(prefers-reduced-motion: reduce)').matches 188 ) 189 ).toBe(true); 190 expect( 191 await page.evaluate( 192 () => matchMedia('(prefers-reduced-motion: no-preference)').matches 193 ) 194 ).toBe(false); 195 await page.emulateMediaFeatures([ 196 { name: 'prefers-color-scheme', value: 'light' }, 197 ]); 198 expect( 199 await page.evaluate( 200 () => matchMedia('(prefers-color-scheme: light)').matches 201 ) 202 ).toBe(true); 203 expect( 204 await page.evaluate( 205 () => matchMedia('(prefers-color-scheme: dark)').matches 206 ) 207 ).toBe(false); 208 await page.emulateMediaFeatures([ 209 { name: 'prefers-color-scheme', value: 'dark' }, 210 ]); 211 expect( 212 await page.evaluate( 213 () => matchMedia('(prefers-color-scheme: dark)').matches 214 ) 215 ).toBe(true); 216 expect( 217 await page.evaluate( 218 () => matchMedia('(prefers-color-scheme: light)').matches 219 ) 220 ).toBe(false); 221 await page.emulateMediaFeatures([ 222 { name: 'prefers-reduced-motion', value: 'reduce' }, 223 { name: 'prefers-color-scheme', value: 'light' }, 224 ]); 225 expect( 226 await page.evaluate( 227 () => matchMedia('(prefers-reduced-motion: reduce)').matches 228 ) 229 ).toBe(true); 230 expect( 231 await page.evaluate( 232 () => matchMedia('(prefers-reduced-motion: no-preference)').matches 233 ) 234 ).toBe(false); 235 expect( 236 await page.evaluate( 237 () => matchMedia('(prefers-color-scheme: light)').matches 238 ) 239 ).toBe(true); 240 expect( 241 await page.evaluate( 242 () => matchMedia('(prefers-color-scheme: dark)').matches 243 ) 244 ).toBe(false); 245 await page.emulateMediaFeatures([{ name: 'color-gamut', value: 'srgb' }]); 246 expect( 247 await page.evaluate(() => matchMedia('(color-gamut: p3)').matches) 248 ).toBe(false); 249 expect( 250 await page.evaluate(() => matchMedia('(color-gamut: srgb)').matches) 251 ).toBe(true); 252 expect( 253 await page.evaluate(() => matchMedia('(color-gamut: rec2020)').matches) 254 ).toBe(false); 255 await page.emulateMediaFeatures([{ name: 'color-gamut', value: 'p3' }]); 256 expect( 257 await page.evaluate(() => matchMedia('(color-gamut: p3)').matches) 258 ).toBe(true); 259 expect( 260 await page.evaluate(() => matchMedia('(color-gamut: srgb)').matches) 261 ).toBe(true); 262 expect( 263 await page.evaluate(() => matchMedia('(color-gamut: rec2020)').matches) 264 ).toBe(false); 265 await page.emulateMediaFeatures([ 266 { name: 'color-gamut', value: 'rec2020' }, 267 ]); 268 expect( 269 await page.evaluate(() => matchMedia('(color-gamut: p3)').matches) 270 ).toBe(true); 271 expect( 272 await page.evaluate(() => matchMedia('(color-gamut: srgb)').matches) 273 ).toBe(true); 274 expect( 275 await page.evaluate(() => matchMedia('(color-gamut: rec2020)').matches) 276 ).toBe(true); 277 }); 278 it('should throw in case of bad argument', async () => { 279 const { page } = getTestState(); 280 281 let error = null; 282 await page 283 .emulateMediaFeatures([{ name: 'bad', value: '' }]) 284 .catch((error_) => (error = error_)); 285 expect(error.message).toBe('Unsupported media feature: bad'); 286 }); 287 }); 288 289 describe('Page.emulateTimezone', function () { 290 it('should work', async () => { 291 const { page } = getTestState(); 292 293 await page.evaluate(() => { 294 globalThis.date = new Date(1479579154987); 295 }); 296 await page.emulateTimezone('America/Jamaica'); 297 expect(await page.evaluate(() => globalThis.date.toString())).toBe( 298 'Sat Nov 19 2016 13:12:34 GMT-0500 (Eastern Standard Time)' 299 ); 300 301 await page.emulateTimezone('Pacific/Honolulu'); 302 expect(await page.evaluate(() => globalThis.date.toString())).toBe( 303 'Sat Nov 19 2016 08:12:34 GMT-1000 (Hawaii-Aleutian Standard Time)' 304 ); 305 306 await page.emulateTimezone('America/Buenos_Aires'); 307 expect(await page.evaluate(() => globalThis.date.toString())).toBe( 308 'Sat Nov 19 2016 15:12:34 GMT-0300 (Argentina Standard Time)' 309 ); 310 311 await page.emulateTimezone('Europe/Berlin'); 312 expect(await page.evaluate(() => globalThis.date.toString())).toBe( 313 'Sat Nov 19 2016 19:12:34 GMT+0100 (Central European Standard Time)' 314 ); 315 }); 316 317 it('should throw for invalid timezone IDs', async () => { 318 const { page } = getTestState(); 319 320 let error = null; 321 await page.emulateTimezone('Foo/Bar').catch((error_) => (error = error_)); 322 expect(error.message).toBe('Invalid timezone ID: Foo/Bar'); 323 await page.emulateTimezone('Baz/Qux').catch((error_) => (error = error_)); 324 expect(error.message).toBe('Invalid timezone ID: Baz/Qux'); 325 }); 326 }); 327 328 describe('Page.emulateVisionDeficiency', function () { 329 it('should work', async () => { 330 const { page, server } = getTestState(); 331 332 await page.setViewport({ width: 500, height: 500 }); 333 await page.goto(server.PREFIX + '/grid.html'); 334 335 { 336 await page.emulateVisionDeficiency('none'); 337 const screenshot = await page.screenshot(); 338 expect(screenshot).toBeGolden('screenshot-sanity.png'); 339 } 340 341 { 342 await page.emulateVisionDeficiency('achromatopsia'); 343 const screenshot = await page.screenshot(); 344 expect(screenshot).toBeGolden('vision-deficiency-achromatopsia.png'); 345 } 346 347 { 348 await page.emulateVisionDeficiency('blurredVision'); 349 const screenshot = await page.screenshot(); 350 expect(screenshot).toBeGolden('vision-deficiency-blurredVision.png'); 351 } 352 353 { 354 await page.emulateVisionDeficiency('deuteranopia'); 355 const screenshot = await page.screenshot(); 356 expect(screenshot).toBeGolden('vision-deficiency-deuteranopia.png'); 357 } 358 359 { 360 await page.emulateVisionDeficiency('protanopia'); 361 const screenshot = await page.screenshot(); 362 expect(screenshot).toBeGolden('vision-deficiency-protanopia.png'); 363 } 364 365 { 366 await page.emulateVisionDeficiency('tritanopia'); 367 const screenshot = await page.screenshot(); 368 expect(screenshot).toBeGolden('vision-deficiency-tritanopia.png'); 369 } 370 371 { 372 await page.emulateVisionDeficiency('none'); 373 const screenshot = await page.screenshot(); 374 expect(screenshot).toBeGolden('screenshot-sanity.png'); 375 } 376 }); 377 378 it('should throw for invalid vision deficiencies', async () => { 379 const { page } = getTestState(); 380 381 let error = null; 382 await page 383 // @ts-expect-error deliberately passign invalid deficiency 384 .emulateVisionDeficiency('invalid') 385 .catch((error_) => (error = error_)); 386 expect(error.message).toBe('Unsupported vision deficiency: invalid'); 387 }); 388 }); 389 390 describe('Page.emulateNetworkConditions', function () { 391 it('should change navigator.connection.effectiveType', async () => { 392 const { page, puppeteer } = getTestState(); 393 394 const slow3G = puppeteer.networkConditions['Slow 3G']; 395 const fast3G = puppeteer.networkConditions['Fast 3G']; 396 397 expect( 398 await page.evaluate('window.navigator.connection.effectiveType') 399 ).toBe('4g'); 400 await page.emulateNetworkConditions(fast3G); 401 expect( 402 await page.evaluate('window.navigator.connection.effectiveType') 403 ).toBe('3g'); 404 await page.emulateNetworkConditions(slow3G); 405 expect( 406 await page.evaluate('window.navigator.connection.effectiveType') 407 ).toBe('2g'); 408 await page.emulateNetworkConditions(null); 409 }); 410 }); 411}); 412