diff --git a/docs/api.md b/docs/api.md index 95176e466f7..24631e17c19 100644 --- a/docs/api.md +++ b/docs/api.md @@ -29,7 +29,7 @@ * [page.addScriptTag(url)](#pageaddscripttagurl) * [page.click(selector)](#pageclickselector) * [page.close()](#pageclose) - * [page.emulate(name, options)](#pageemulatename-options) + * [page.emulate(name)](#pageemulatename) * [page.emulatedDevices()](#pageemulateddevices) * [page.evaluate(pageFunction, ...args)](#pageevaluatepagefunction-args) * [page.evaluateOnInitialized(pageFunction, ...args)](#pageevaluateoninitializedpagefunction-args) @@ -316,10 +316,8 @@ Adds a `` tag to the page with the desired url. Alternatively, #### page.close() - returns: <[Promise]> Returns promise which resolves when page gets closed. -#### page.emulate(name, options) +#### page.emulate(name) - `name` <[string]> A name of the device to be emulated. Get the full list of emulated devices via `page.emulatedDevices()`. -- `options` <[Object]> Emulation parameters which might have the following properties: - - `landscape` <[boolean]> Emulates device in the landscape mode, defaults to `false`. - returns: <[Promise]> Returns promise which resolves when device is emulated. Can reload the page if switching between mobile and desktop devices. #### page.emulatedDevices() diff --git a/lib/DeviceDescriptors.js b/lib/DeviceDescriptors.js index f39da5f6fea..d9f6934d33c 100644 --- a/lib/DeviceDescriptors.js +++ b/lib/DeviceDescriptors.js @@ -14,1136 +14,619 @@ * limitations under the License. */ -module.exports = -[ +module.exports = [ { - 'type': 'emulated-device', - 'device': { - 'show-by-default': false, - 'title': 'iPhone 4' , - 'screen': { - 'horizontal': { - 'width': 480, - 'height': 320 - }, - 'device-pixel-ratio': 2, - 'vertical': { - 'width': 320, - 'height': 480 - } - }, - 'capabilities': [ - 'touch', - 'mobile' - ], - 'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1', - 'type': 'phone', - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'Blackberry PlayBook', + 'userAgent': 'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/7.2.1.0 Safari/536.2+', + 'viewport': { + 'width': 600, + 'height': 1024, + 'deviceScaleFactor': 1, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false } }, { - 'type': 'emulated-device', - 'order': 40, - 'device': { - 'show-by-default': true, - 'title': 'iPhone 5', - 'screen': { - 'horizontal': { - 'outline' : { - 'image': '@url(iPhone5-landscape.svg)', - 'insets' : { 'left': 115, 'top': 25, 'right': 115, 'bottom': 28 } - }, - 'width': 568, - 'height': 320 - }, - 'device-pixel-ratio': 2, - 'vertical': { - 'outline' : { - 'image': '@url(iPhone5-portrait.svg)', - 'insets' : { 'left': 29, 'top': 105, 'right': 25, 'bottom': 111 } - }, - 'width': 320, - 'height': 568 - } - }, - 'capabilities': [ - 'touch', - 'mobile' - ], - 'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1', - 'type': 'phone', - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'Blackberry PlayBook landscape', + 'userAgent': 'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/7.2.1.0 Safari/536.2+', + 'viewport': { + 'width': 1024, + 'height': 600, + 'deviceScaleFactor': 1, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': true } }, { - 'type': 'emulated-device', - 'order': 50, - 'device': { - 'show-by-default': true, - 'title': 'iPhone 6', - 'screen': { - 'horizontal': { - 'outline' : { - 'image': '@url(iPhone6-landscape.svg)', - 'insets' : { 'left': 106, 'top': 28, 'right': 106, 'bottom': 28 } - }, - 'width': 667, - 'height': 375 - }, - 'device-pixel-ratio': 2, - 'vertical': { - 'outline' : { - 'image': '@url(iPhone6-portrait.svg)', - 'insets' : { 'left': 28, 'top': 105, 'right': 28, 'bottom': 105 } - }, - 'width': 375, - 'height': 667 - } - }, - 'capabilities': [ - 'touch', - 'mobile' - ], - 'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1', - 'type': 'phone', - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'BlackBerry Z30', + 'userAgent': 'Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.0.9.2372 Mobile Safari/537.10+', + 'viewport': { + 'width': 360, + 'height': 640, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false } }, { - 'type': 'emulated-device', - 'order': 60, - 'device': { - 'show-by-default': true, - 'title': 'iPhone 6 Plus', - 'screen': { - 'horizontal': { - 'outline' : { - 'image': '@url(iPhone6Plus-landscape.svg)', - 'insets' : { 'left': 109, 'top': 29, 'right': 109, 'bottom': 27 } - }, - 'width': 736, - 'height': 414 - }, - 'device-pixel-ratio': 3, - 'vertical': { - 'outline' : { - 'image': '@url(iPhone6Plus-portrait.svg)', - 'insets' : { 'left': 26, 'top': 107, 'right': 30, 'bottom': 111 } - }, - 'width': 414, - 'height': 736 - } - }, - 'capabilities': [ - 'touch', - 'mobile' - ], - 'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1', - 'type': 'phone', - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'BlackBerry Z30 landscape', + 'userAgent': 'Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.0.9.2372 Mobile Safari/537.10+', + 'viewport': { + 'width': 640, + 'height': 360, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': true } }, { - 'type': 'emulated-device', - 'device': { - 'show-by-default': false, - 'title': 'BlackBerry Z30', - 'screen': { - 'horizontal': { - 'width': 640, - 'height': 360 - }, - 'device-pixel-ratio': 2, - 'vertical': { - 'width': 360, - 'height': 640 - } - }, - 'capabilities': [ - 'touch', - 'mobile' - ], - 'user-agent': 'Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.0.9.2372 Mobile Safari/537.10+', - 'type': 'phone', - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'Galaxy Note 3', + 'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', + 'viewport': { + 'width': 360, + 'height': 640, + 'deviceScaleFactor': 3, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false } }, { - 'type': 'emulated-device', - 'device': { - 'show-by-default': false, - 'title': 'Nexus 4', - 'screen': { - 'horizontal': { - 'width': 640, - 'height': 384 - }, - 'device-pixel-ratio': 2, - 'vertical': { - 'width': 384, - 'height': 640 - } - }, - 'capabilities': [ - 'touch', - 'mobile' - ], - 'user-agent': 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.104 Mobile Safari/537.36', - 'type': 'phone', - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'Galaxy Note 3 landscape', + 'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', + 'viewport': { + 'width': 640, + 'height': 360, + 'deviceScaleFactor': 3, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': true } }, { - 'type': 'emulated-device', - 'device': { - 'title': 'Nexus 5', - 'type': 'phone', - 'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.104 Mobile Safari/537.36', - 'capabilities': [ - 'touch', - 'mobile' - ], - 'show-by-default': false, - 'screen': { - 'device-pixel-ratio': 3, - 'vertical': { - 'width': 360, - 'height': 640 - }, - 'horizontal': { - 'width': 640, - 'height': 360 - } - }, - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 25, 'right': 0, 'bottom': 48 }, - 'image': '@url(google-nexus-5-vertical-default-1x.png) 1x, @url(google-nexus-5-vertical-default-2x.png) 2x' - }, - { - 'title': 'navigation bar', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 80, 'right': 0, 'bottom': 48 }, - 'image': '@url(google-nexus-5-vertical-navigation-1x.png) 1x, @url(google-nexus-5-vertical-navigation-2x.png) 2x' - }, - { - 'title': 'keyboard', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 80, 'right': 0, 'bottom': 312 }, - 'image': '@url(google-nexus-5-vertical-keyboard-1x.png) 1x, @url(google-nexus-5-vertical-keyboard-2x.png) 2x' - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 25, 'right': 42, 'bottom': 0 }, - 'image': '@url(google-nexus-5-horizontal-default-1x.png) 1x, @url(google-nexus-5-horizontal-default-2x.png) 2x' - }, - { - 'title': 'navigation bar', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 80, 'right': 42, 'bottom': 0 }, - 'image': '@url(google-nexus-5-horizontal-navigation-1x.png) 1x, @url(google-nexus-5-horizontal-navigation-2x.png) 2x' - }, - { - 'title': 'keyboard', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 80, 'right': 42, 'bottom': 202 }, - 'image': '@url(google-nexus-5-horizontal-keyboard-1x.png) 1x, @url(google-nexus-5-horizontal-keyboard-2x.png) 2x' - } - ] + 'name': 'Galaxy Note II', + 'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', + 'viewport': { + 'width': 360, + 'height': 640, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false } }, { - 'type': 'emulated-device', - 'order': 20, - 'device': { - 'title': 'Nexus 5X', - 'type': 'phone', - 'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.104 Mobile Safari/537.36', - 'capabilities': [ - 'touch', - 'mobile' - ], - 'show-by-default': true, - 'screen': { - 'device-pixel-ratio': 2.625, - 'vertical': { - 'outline' : { - 'image': '@url(Nexus5X-portrait.svg)', - 'insets' : { 'left': 18, 'top': 88, 'right': 22, 'bottom': 98 } - }, - 'width': 412, - 'height': 732 - }, - 'horizontal': { - 'outline' : { - 'image': '@url(Nexus5X-landscape.svg)', - 'insets' : { 'left': 88, 'top': 21, 'right': 98, 'bottom': 19 } - }, - 'width': 732, - 'height': 412 - } - }, - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 24, 'right': 0, 'bottom': 48 }, - 'image': '@url(google-nexus-5x-vertical-default-1x.png) 1x, @url(google-nexus-5x-vertical-default-2x.png) 2x' - }, - { - 'title': 'navigation bar', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 80, 'right': 0, 'bottom': 48 }, - 'image': '@url(google-nexus-5x-vertical-navigation-1x.png) 1x, @url(google-nexus-5x-vertical-navigation-2x.png) 2x' - }, - { - 'title': 'keyboard', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 80, 'right': 0, 'bottom': 342 }, - 'image': '@url(google-nexus-5x-vertical-keyboard-1x.png) 1x, @url(google-nexus-5x-vertical-keyboard-2x.png) 2x' - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 24, 'right': 48, 'bottom': 0 }, - 'image': '@url(google-nexus-5x-horizontal-default-1x.png) 1x, @url(google-nexus-5x-horizontal-default-2x.png) 2x' - }, - { - 'title': 'navigation bar', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 80, 'right': 48, 'bottom': 0 }, - 'image': '@url(google-nexus-5x-horizontal-navigation-1x.png) 1x, @url(google-nexus-5x-horizontal-navigation-2x.png) 2x' - }, - { - 'title': 'keyboard', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 80, 'right': 48, 'bottom': 222 }, - 'image': '@url(google-nexus-5x-horizontal-keyboard-1x.png) 1x, @url(google-nexus-5x-horizontal-keyboard-2x.png) 2x' - } - ] + 'name': 'Galaxy Note II landscape', + 'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', + 'viewport': { + 'width': 640, + 'height': 360, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': true } }, { - 'type': 'emulated-device', - 'device': { - 'show-by-default': false, - 'title': 'Nexus 6', - 'screen': { - 'horizontal': { - 'width': 732, - 'height': 412 - }, - 'device-pixel-ratio': 3.5, - 'vertical': { - 'width': 412, - 'height': 732 - } - }, - 'capabilities': [ - 'touch', - 'mobile' - ], - 'user-agent': 'Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.104 Mobile Safari/537.36', - 'type': 'phone', - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'Galaxy S III', + 'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', + 'viewport': { + 'width': 360, + 'height': 640, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false } }, { - 'type': 'emulated-device', - 'order': 30, - 'device': { - 'show-by-default': true, - 'title': 'Nexus 6P', - 'screen': { - 'horizontal': { - 'outline' : { - 'image': '@url(Nexus6P-landscape.svg)', - 'insets' : { 'left': 94, 'top': 17, 'right': 88, 'bottom': 17 } - }, - 'width': 732, - 'height': 412 - }, - 'device-pixel-ratio': 3.5, - 'vertical': { - 'outline' : { - 'image': '@url(Nexus6P-portrait.svg)', - 'insets' : { 'left': 16, 'top': 94, 'right': 16, 'bottom': 88 } - }, - 'width': 412, - 'height': 732 - } - }, - 'capabilities': [ - 'touch', - 'mobile' - ], - 'user-agent': 'Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.104 Mobile Safari/537.36', - 'type': 'phone', - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'Galaxy S III landscape', + 'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', + 'viewport': { + 'width': 640, + 'height': 360, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': true } }, { - 'type': 'emulated-device', - 'device': { - 'show-by-default': false, - 'title': 'LG Optimus L70', - 'screen': { - 'horizontal': { - 'width': 640, - 'height': 384 - }, - 'device-pixel-ratio': 1.25, - 'vertical': { - 'width': 384, - 'height': 640 - } - }, - 'capabilities': [ - 'touch', - 'mobile' - ], - 'user-agent': 'Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.104 Mobile Safari/537.36', - 'type': 'phone', - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'Galaxy S5', + 'userAgent': 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36', + 'viewport': { + 'width': 360, + 'height': 640, + 'deviceScaleFactor': 3, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false } }, { - 'type': 'emulated-device', - 'device': { - 'show-by-default': false, - 'title': 'Nokia N9', - 'screen': { - 'horizontal': { - 'width': 640, - 'height': 360 - }, - 'device-pixel-ratio': 1, - 'vertical': { - 'width': 360, - 'height': 640 - } - }, - 'capabilities': [ - 'touch', - 'mobile' - ], - 'user-agent': 'Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13', - 'type': 'phone', - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'Galaxy S5 landscape', + 'userAgent': 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36', + 'viewport': { + 'width': 640, + 'height': 360, + 'deviceScaleFactor': 3, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': true } }, { - 'type': 'emulated-device', - 'device': { - 'show-by-default': false, - 'title': 'Nokia Lumia 520', - 'screen': { - 'horizontal': { - 'width': 533, - 'height': 320 - }, - 'device-pixel-ratio': 1.5, - 'vertical': { - 'width': 320, - 'height': 533 - } - }, - 'capabilities': [ - 'touch', - 'mobile' - ], - 'user-agent': 'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)', - 'type': 'phone', - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'iPad', + 'userAgent': 'Mozilla/5.0 (iPad; CPU OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1', + 'viewport': { + 'width': 768, + 'height': 1024, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false } }, { - 'type': 'emulated-device', - 'device': { - 'show-by-default': false, - 'title': 'Microsoft Lumia 550', - 'screen': { - 'horizontal': { - 'width': 640, - 'height': 360 - }, - 'device-pixel-ratio': 2, - 'vertical': { - 'width': 640, - 'height': 360 - } - }, - 'capabilities': [ - 'touch', - 'mobile' - ], - 'user-agent': 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263', - 'type': 'phone', - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'iPad landscape', + 'userAgent': 'Mozilla/5.0 (iPad; CPU OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1', + 'viewport': { + 'width': 1024, + 'height': 768, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': true } }, { - 'type': 'emulated-device', - 'device': { - 'show-by-default': false, - 'title': 'Microsoft Lumia 950', - 'screen': { - 'horizontal': { - 'width': 640, - 'height': 360 - }, - 'device-pixel-ratio': 4, - 'vertical': { - 'width': 360, - 'height': 640 - } - }, - 'capabilities': [ - 'touch', - 'mobile' - ], - 'user-agent': 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263', - 'type': 'phone', - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'iPad Mini', + 'userAgent': 'Mozilla/5.0 (iPad; CPU OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1', + 'viewport': { + 'width': 768, + 'height': 1024, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false } }, { - 'type': 'emulated-device', - 'device': { - 'show-by-default': false, - 'title': 'Galaxy S III', - 'screen': { - 'horizontal': { - 'width': 640, - 'height': 360 - }, - 'device-pixel-ratio': 2, - 'vertical': { - 'width': 360, - 'height': 640 - } - }, - 'capabilities': [ - 'touch', - 'mobile' - ], - 'user-agent': 'Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', - 'type': 'phone', - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'iPad Mini landscape', + 'userAgent': 'Mozilla/5.0 (iPad; CPU OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1', + 'viewport': { + 'width': 1024, + 'height': 768, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': true } }, { - 'type': 'emulated-device', - 'order': 10, - 'device': { - 'show-by-default': true, - 'title': 'Galaxy S5', - 'screen': { - 'horizontal': { - 'width': 640, - 'height': 360 - }, - 'device-pixel-ratio': 3, - 'vertical': { - 'width': 360, - 'height': 640 - } - }, - 'capabilities': [ - 'touch', - 'mobile' - ], - 'user-agent': 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.104 Mobile Safari/537.36', - 'type': 'phone', - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'iPad Pro', + 'userAgent': 'Mozilla/5.0 (iPad; CPU OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1', + 'viewport': { + 'width': 1024, + 'height': 1366, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false } }, { - 'type': 'emulated-device', - 'device': { - 'show-by-default': false, - 'title': 'Kindle Fire HDX', - 'screen': { - 'horizontal': { - 'width': 2560, - 'height': 1600 - }, - 'device-pixel-ratio': 2, - 'vertical': { - 'width': 1600, - 'height': 2560 - } - }, - 'capabilities': [ - 'touch', - 'mobile' - ], - 'user-agent': 'Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true', - 'type': 'tablet', - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'iPad Pro landscape', + 'userAgent': 'Mozilla/5.0 (iPad; CPU OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1', + 'viewport': { + 'width': 1366, + 'height': 1024, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': true } }, { - 'type': 'emulated-device', - 'device': { - 'show-by-default': false, - 'title': 'iPad Mini', - 'screen': { - 'horizontal': { - 'width': 1024, - 'height': 768 - }, - 'device-pixel-ratio': 2, - 'vertical': { - 'width': 768, - 'height': 1024 - } - }, - 'capabilities': [ - 'touch', - 'mobile' - ], - 'user-agent': 'Mozilla/5.0 (iPad; CPU OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1', - 'type': 'tablet', - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'iPhone 4', + 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1', + 'viewport': { + 'width': 320, + 'height': 480, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false } }, { - 'type': 'emulated-device', - 'order': 70, - 'device': { - 'show-by-default': true, - 'title': 'iPad', - 'screen': { - 'horizontal': { - 'outline' : { - 'image': '@url(iPad-landscape.svg)', - 'insets' : { 'left': 112, 'top': 56, 'right': 116, 'bottom': 52 } - }, - 'width': 1024, - 'height': 768 - }, - 'device-pixel-ratio': 2, - 'vertical': { - 'outline' : { - 'image': '@url(iPad-portrait.svg)', - 'insets' : { 'left': 52, 'top': 114, 'right': 55, 'bottom': 114 } - }, - 'width': 768, - 'height': 1024 - } - }, - 'capabilities': [ - 'touch', - 'mobile' - ], - 'user-agent': 'Mozilla/5.0 (iPad; CPU OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1', - 'type': 'tablet', - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'iPhone 4 landscape', + 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1', + 'viewport': { + 'width': 480, + 'height': 320, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': true } }, { - 'type': 'emulated-device', - 'order': 80, - 'device': { - 'show-by-default': true, - 'title': 'iPad Pro', - 'screen': { - 'horizontal': { - 'width': 1366, - 'height': 1024 - }, - 'device-pixel-ratio': 2, - 'vertical': { - 'width': 1024, - 'height': 1366 - } - }, - 'capabilities': [ - 'touch', - 'mobile' - ], - 'user-agent': 'Mozilla/5.0 (iPad; CPU OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1', - 'type': 'tablet', - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'iPhone 5', + 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1', + 'viewport': { + 'width': 320, + 'height': 568, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false } }, { - 'type': 'emulated-device', - 'device': { - 'show-by-default': false, - 'title': 'Blackberry PlayBook', - 'screen': { - 'horizontal': { - 'width': 1024, - 'height': 600 - }, - 'device-pixel-ratio': 1, - 'vertical': { - 'width': 600, - 'height': 1024 - } - }, - 'capabilities': [ - 'touch', - 'mobile' - ], - 'user-agent': 'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/7.2.1.0 Safari/536.2+', - 'type': 'tablet', - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'iPhone 5 landscape', + 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1', + 'viewport': { + 'width': 568, + 'height': 320, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': true } }, { - 'type': 'emulated-device', - 'device': { - 'show-by-default': false, - 'title': 'Nexus 10', - 'screen': { - 'horizontal': { - 'width': 1280, - 'height': 800 - }, - 'device-pixel-ratio': 2, - 'vertical': { - 'width': 800, - 'height': 1280 - } - }, - 'capabilities': [ - 'touch', - 'mobile' - ], - 'user-agent': 'Mozilla/5.0 (Linux; Android 4.3; Nexus 10 Build/JSS15Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.104 Safari/537.36', - 'type': 'tablet', - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'iPhone 6', + 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1', + 'viewport': { + 'width': 375, + 'height': 667, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false } }, { - 'type': 'emulated-device', - 'device': { - 'show-by-default': false, - 'title': 'Nexus 7', - 'screen': { - 'horizontal': { - 'width': 960, - 'height': 600 - }, - 'device-pixel-ratio': 2, - 'vertical': { - 'width': 600, - 'height': 960 - } - }, - 'capabilities': [ - 'touch', - 'mobile' - ], - 'user-agent': 'Mozilla/5.0 (Linux; Android 4.3; Nexus 7 Build/JSS15Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.104 Safari/537.36', - 'type': 'tablet', - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'iPhone 6 landscape', + 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1', + 'viewport': { + 'width': 667, + 'height': 375, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': true } }, { - 'type': 'emulated-device', - 'device': { - 'show-by-default': false, - 'title': 'Galaxy Note 3', - 'screen': { - 'horizontal': { - 'width': 640, - 'height': 360 - }, - 'device-pixel-ratio': 3, - 'vertical': { - 'width': 360, - 'height': 640 - } - }, - 'capabilities': [ - 'touch', - 'mobile' - ], - 'user-agent': 'Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', - 'type': 'phone', - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'iPhone 6 Plus', + 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1', + 'viewport': { + 'width': 414, + 'height': 736, + 'deviceScaleFactor': 3, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false } }, { - 'type': 'emulated-device', - 'device': { - 'show-by-default': false, - 'title': 'Galaxy Note II', - 'screen': { - 'horizontal': { - 'width': 640, - 'height': 360 - }, - 'device-pixel-ratio': 2, - 'vertical': { - 'width': 360, - 'height': 640 - } - }, - 'capabilities': [ - 'touch', - 'mobile' - ], - 'user-agent': 'Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', - 'type': 'phone', - 'modes': [ - { - 'title': 'default', - 'orientation': 'vertical', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - }, - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'iPhone 6 Plus landscape', + 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1', + 'viewport': { + 'width': 736, + 'height': 414, + 'deviceScaleFactor': 3, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': true } }, { - 'type': 'emulated-device', - 'device': { - 'show-by-default': false, - 'title': 'Laptop with touch', - 'screen': { - 'horizontal': { - 'width': 1280, - 'height': 950 - }, - 'device-pixel-ratio': 1, - 'vertical': { - 'width': 950, - 'height': 1280 - } - }, - 'capabilities': [ - 'touch' - ], - 'user-agent': '', - 'type': 'notebook', - 'modes': [ - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'Kindle Fire HDX', + 'userAgent': 'Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true', + 'viewport': { + 'width': 800, + 'height': 1280, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false } }, { - 'type': 'emulated-device', - 'device': { - 'show-by-default': false, - 'title': 'Laptop with HiDPI screen', - 'screen': { - 'horizontal': { - 'width': 1440, - 'height': 900 - }, - 'device-pixel-ratio': 2, - 'vertical': { - 'width': 900, - 'height': 1440 - } - }, - 'capabilities': [], - 'user-agent': '', - 'type': 'notebook', - 'modes': [ - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'Kindle Fire HDX landscape', + 'userAgent': 'Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true', + 'viewport': { + 'width': 1280, + 'height': 800, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': true } }, { - 'type': 'emulated-device', - 'device': { - 'show-by-default': false, - 'title': 'Laptop with MDPI screen', - 'screen': { - 'horizontal': { - 'width': 1280, - 'height': 800 - }, - 'device-pixel-ratio': 1, - 'vertical': { - 'width': 800, - 'height': 1280 - } - }, - 'capabilities': [], - 'user-agent': '', - 'type': 'notebook', - 'modes': [ - { - 'title': 'default', - 'orientation': 'horizontal', - 'insets': { 'left': 0, 'top': 0, 'right': 0, 'bottom': 0 } - } - ] + 'name': 'LG Optimus L70', + 'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/%s Mobile Safari/537.36', + 'viewport': { + 'width': 384, + 'height': 640, + 'deviceScaleFactor': 1.25, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false + } + }, + { + 'name': 'LG Optimus L70 landscape', + 'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/%s Mobile Safari/537.36', + 'viewport': { + 'width': 640, + 'height': 384, + 'deviceScaleFactor': 1.25, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': true + } + }, + { + 'name': 'Microsoft Lumia 550', + 'userAgent': 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263', + 'viewport': { + 'width': 640, + 'height': 360, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false + } + }, + { + 'name': 'Microsoft Lumia 950', + 'userAgent': 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263', + 'viewport': { + 'width': 360, + 'height': 640, + 'deviceScaleFactor': 4, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false + } + }, + { + 'name': 'Microsoft Lumia 950 landscape', + 'userAgent': 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263', + 'viewport': { + 'width': 640, + 'height': 360, + 'deviceScaleFactor': 4, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': true + } + }, + { + 'name': 'Nexus 10', + 'userAgent': 'Mozilla/5.0 (Linux; Android 4.3; Nexus 10 Build/JSS15Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36', + 'viewport': { + 'width': 800, + 'height': 1280, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false + } + }, + { + 'name': 'Nexus 10 landscape', + 'userAgent': 'Mozilla/5.0 (Linux; Android 4.3; Nexus 10 Build/JSS15Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36', + 'viewport': { + 'width': 1280, + 'height': 800, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': true + } + }, + { + 'name': 'Nexus 4', + 'userAgent': 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36', + 'viewport': { + 'width': 384, + 'height': 640, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false + } + }, + { + 'name': 'Nexus 4 landscape', + 'userAgent': 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36', + 'viewport': { + 'width': 640, + 'height': 384, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': true + } + }, + { + 'name': 'Nexus 5', + 'userAgent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36', + 'viewport': { + 'width': 360, + 'height': 640, + 'deviceScaleFactor': 3, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false + } + }, + { + 'name': 'Nexus 5 landscape', + 'userAgent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36', + 'viewport': { + 'width': 640, + 'height': 360, + 'deviceScaleFactor': 3, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': true + } + }, + { + 'name': 'Nexus 5X', + 'userAgent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36', + 'viewport': { + 'width': 412, + 'height': 732, + 'deviceScaleFactor': 2.625, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false + } + }, + { + 'name': 'Nexus 5X landscape', + 'userAgent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36', + 'viewport': { + 'width': 732, + 'height': 412, + 'deviceScaleFactor': 2.625, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': true + } + }, + { + 'name': 'Nexus 6', + 'userAgent': 'Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36', + 'viewport': { + 'width': 412, + 'height': 732, + 'deviceScaleFactor': 3.5, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false + } + }, + { + 'name': 'Nexus 6 landscape', + 'userAgent': 'Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36', + 'viewport': { + 'width': 732, + 'height': 412, + 'deviceScaleFactor': 3.5, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': true + } + }, + { + 'name': 'Nexus 6P', + 'userAgent': 'Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36', + 'viewport': { + 'width': 412, + 'height': 732, + 'deviceScaleFactor': 3.5, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false + } + }, + { + 'name': 'Nexus 6P landscape', + 'userAgent': 'Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36', + 'viewport': { + 'width': 732, + 'height': 412, + 'deviceScaleFactor': 3.5, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': true + } + }, + { + 'name': 'Nexus 7', + 'userAgent': 'Mozilla/5.0 (Linux; Android 4.3; Nexus 7 Build/JSS15Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36', + 'viewport': { + 'width': 600, + 'height': 960, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false + } + }, + { + 'name': 'Nexus 7 landscape', + 'userAgent': 'Mozilla/5.0 (Linux; Android 4.3; Nexus 7 Build/JSS15Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36', + 'viewport': { + 'width': 960, + 'height': 600, + 'deviceScaleFactor': 2, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': true + } + }, + { + 'name': 'Nokia Lumia 520', + 'userAgent': 'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)', + 'viewport': { + 'width': 320, + 'height': 533, + 'deviceScaleFactor': 1.5, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false + } + }, + { + 'name': 'Nokia Lumia 520 landscape', + 'userAgent': 'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)', + 'viewport': { + 'width': 533, + 'height': 320, + 'deviceScaleFactor': 1.5, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': true + } + }, + { + 'name': 'Nokia N9', + 'userAgent': 'Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13', + 'viewport': { + 'width': 480, + 'height': 854, + 'deviceScaleFactor': 1, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': false + } + }, + { + 'name': 'Nokia N9 landscape', + 'userAgent': 'Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13', + 'viewport': { + 'width': 854, + 'height': 480, + 'deviceScaleFactor': 1, + 'isMobile': true, + 'hasTouch': true, + 'isLandscape': true } } ]; +for (let device of module.exports) + module.exports[device.name] = device; diff --git a/lib/EmulationManager.js b/lib/EmulationManager.js index 85425ad617f..ce78c41664b 100644 --- a/lib/EmulationManager.js +++ b/lib/EmulationManager.js @@ -21,7 +21,7 @@ class EmulationManager { * @return {!Promise>} */ static deviceNames() { - return Promise.resolve(DeviceDescriptors.map(entry => entry['device'].title)); + return Promise.resolve(DeviceDescriptors.map(device => device.name)); } /** @@ -29,31 +29,20 @@ class EmulationManager { * @param {!Object=} options * @return {!Page.Viewport} */ - static deviceViewport(name, options) { - options = options || {}; - const descriptor = DeviceDescriptors.find(entry => entry['device'].title === name)['device']; - if (!descriptor) + static deviceViewport(name) { + const device = DeviceDescriptors.find(device => device.name === name); + if (!device) throw new Error(`Unable to emulate ${name}, no such device metrics in the library.`); - const device = EmulationManager.loadFromJSONV1(descriptor); - const viewport = options.landscape ? device.horizontal : device.vertical; - return { - width: viewport.width, - height: viewport.height, - deviceScaleFactor: device.deviceScaleFactor, - isMobile: device.capabilities.includes('mobile'), - hasTouch: device.capabilities.includes('touch'), - isLandscape: options.landscape || false - }; + return device.viewport; } /** * @param {string} name */ - static deviceUserAgent(name, options) { - const descriptor = DeviceDescriptors.find(entry => entry['device'].title === name)['device']; - if (!descriptor) + static deviceUserAgent(name) { + const device = DeviceDescriptors.find(device => device.name === name); + if (!device) throw new Error(`Unable to emulate ${name}, no such device metrics in the library.`); - const device = EmulationManager.loadFromJSONV1(descriptor); return device.userAgent; } @@ -110,86 +99,6 @@ class EmulationManager { } return reloadNeeded; } - - /** - * @param {*} json - * @return {?Object} - */ - static loadFromJSONV1(json) { - /** - * @param {*} object - * @param {string} key - * @param {string} type - * @param {*=} defaultValue - * @return {*} - */ - function parseValue(object, key, type, defaultValue) { - if (typeof object !== 'object' || object === null || !object.hasOwnProperty(key)) { - if (typeof defaultValue !== 'undefined') - return defaultValue; - throw new Error('Emulated device is missing required property \'' + key + '\''); - } - const value = object[key]; - if (typeof value !== type || value === null) - throw new Error('Emulated device property \'' + key + '\' has wrong type \'' + typeof value + '\''); - return value; - } - - /** - * @param {*} object - * @param {string} key - * @return {number} - */ - function parseIntValue(object, key) { - const value = /** @type {number} */ (parseValue(object, key, 'number')); - if (value !== Math.abs(value)) - throw new Error('Emulated device value \'' + key + '\' must be integer'); - return value; - } - - /** - * @param {*} json - * @return {!{width: number, height: number}} - */ - function parseOrientation(json) { - const result = {}; - const minDeviceSize = 50; - const maxDeviceSize = 9999; - result.width = parseIntValue(json, 'width'); - if (result.width < 0 || result.width > maxDeviceSize || - result.width < minDeviceSize) - throw new Error('Emulated device has wrong width: ' + result.width); - - result.height = parseIntValue(json, 'height'); - if (result.height < 0 || result.height > maxDeviceSize || - result.height < minDeviceSize) - throw new Error('Emulated device has wrong height: ' + result.height); - - return /** @type {!{width: number, height: number}} */ (result); - } - - const result = {}; - result.type = /** @type {string} */ (parseValue(json, 'type', 'string')); - result.userAgent = /** @type {string} */ (parseValue(json, 'user-agent', 'string')); - - const capabilities = parseValue(json, 'capabilities', 'object', []); - if (!Array.isArray(capabilities)) - throw new Error('Emulated device capabilities must be an array'); - result.capabilities = []; - for (let i = 0; i < capabilities.length; ++i) { - if (typeof capabilities[i] !== 'string') - throw new Error('Emulated device capability must be a string'); - result.capabilities.push(capabilities[i]); - } - - result.deviceScaleFactor = /** @type {number} */ (parseValue(json['screen'], 'device-pixel-ratio', 'number')); - if (result.deviceScaleFactor < 0 || result.deviceScaleFactor > 100) - throw new Error('Emulated device has wrong deviceScaleFactor: ' + result.deviceScaleFactor); - - result.vertical = parseOrientation(parseValue(json['screen'], 'vertical', 'object')); - result.horizontal = parseOrientation(parseValue(json['screen'], 'horizontal', 'object')); - return result; - } } EmulationManager._touchScriptId = Symbol('emulatingTouchScriptId'); diff --git a/lib/Page.js b/lib/Page.js index 88091bffedb..630c9268736 100644 --- a/lib/Page.js +++ b/lib/Page.js @@ -358,13 +358,12 @@ class Page extends EventEmitter { /** * @param {string} name - * @param {!Object=} options * @return {!Promise} */ - emulate(name, options) { + emulate(name) { return Promise.all([ this.setUserAgent(EmulationManager.deviceUserAgent(name)), - this.setViewport(EmulationManager.deviceViewport(name, options)) + this.setViewport(EmulationManager.deviceViewport(name)) ]); } diff --git a/utils/fetch_devices.js b/utils/fetch_devices.js new file mode 100755 index 00000000000..5f102589387 --- /dev/null +++ b/utils/fetch_devices.js @@ -0,0 +1,224 @@ +#!/usr/bin/env node +/** + * Copyright 2017 Google Inc. All rights reserved. + * + * 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. + */ + +const util = require('util'); +const fs = require('fs'); +const path = require('path'); +const DEVICES_URL = 'https://raw.githubusercontent.com/ChromeDevTools/devtools-frontend/master/front_end/emulated_devices/module.json'; + +const template = `/** + * Copyright 2017 Google Inc. All rights reserved. + * + * 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. + */ + +module.exports = %s; +for (let device of module.exports) + module.exports[device.name] = device; +`; + +const help = `Usage: node ${path.basename(__filename)} [-u ] + -u, --url The URL to load devices descriptor from. If not set, + devices will be fetched from the tip-of-tree of DevTools + frontend. + + -h, --help Show this help message + +Fetch Chrome DevTools front-end emulation devices from given URL, convert them to puppeteer +devices and save to the . +`; + +let argv = require('minimist')(process.argv.slice(2), { + alias: { u: 'url', h: 'help' }, +}); + +if (argv.help) { + console.log(help); + return; +} + +let url = argv.url || DEVICES_URL; +let outputPath = argv._[0]; +if (!outputPath) { + console.log('ERROR: output file name is missing. Use --help for help.'); + return; +} + +main(url); + +async function main(url) { + console.log('GET ' + url); + let text = await httpGET(url); + let json = null; + try { + json = JSON.parse(text); + } catch (e) { + console.error(`FAILED: error parsing response - ${e.message}`); + return; + } + let devicePayloads = json.extensions.filter(extension => extension.type === 'emulated-device').map(extension => extension.device); + let devices = []; + for (let payload of devicePayloads) { + let device = createDevice(payload, false); + let landscape = createDevice(payload, true); + devices.push(device); + if (landscape.viewport.width !== device.viewport.width || landscape.viewport.height !== device.viewport.height) + devices.push(landscape); + } + devices = devices.filter(device => device.viewport.isMobile); + devices.sort((a, b) => a.name.localeCompare(b.name)); + // Use single-quotes instead of double-quotes to conform with codestyle. + let serialized = JSON.stringify(devices, null, 2) + .replace(/'/g, `\\'`) + .replace(/"/g, `'`); + let result = util.format(template, serialized); + fs.writeFileSync(outputPath, result, 'utf8'); +} + +/** + * @param {*} descriptor + * @param {boolean} landscape + * @return {!Object} + */ +function createDevice(descriptor, landscape) { + const devicePayload = loadFromJSONV1(descriptor); + const viewportPayload = landscape ? devicePayload.horizontal : devicePayload.vertical; + return { + name: descriptor.title + (landscape ? ' landscape' : ''), + userAgent: devicePayload.userAgent, + viewport: { + width: viewportPayload.width, + height: viewportPayload.height, + deviceScaleFactor: devicePayload.deviceScaleFactor, + isMobile: devicePayload.capabilities.includes('mobile'), + hasTouch: devicePayload.capabilities.includes('touch'), + isLandscape: landscape || false + } + }; +} + +/** + * @param {*} json + * @return {?Object} + */ +function loadFromJSONV1(json) { + /** + * @param {*} object + * @param {string} key + * @param {string} type + * @param {*=} defaultValue + * @return {*} + */ + function parseValue(object, key, type, defaultValue) { + if (typeof object !== 'object' || object === null || !object.hasOwnProperty(key)) { + if (typeof defaultValue !== 'undefined') + return defaultValue; + throw new Error('Emulated device is missing required property \'' + key + '\''); + } + const value = object[key]; + if (typeof value !== type || value === null) + throw new Error('Emulated device property \'' + key + '\' has wrong type \'' + typeof value + '\''); + return value; + } + + /** + * @param {*} object + * @param {string} key + * @return {number} + */ + function parseIntValue(object, key) { + const value = /** @type {number} */ (parseValue(object, key, 'number')); + if (value !== Math.abs(value)) + throw new Error('Emulated device value \'' + key + '\' must be integer'); + return value; + } + + /** + * @param {*} json + * @return {!{width: number, height: number}} + */ + function parseOrientation(json) { + const result = {}; + const minDeviceSize = 50; + const maxDeviceSize = 9999; + result.width = parseIntValue(json, 'width'); + if (result.width < 0 || result.width > maxDeviceSize || + result.width < minDeviceSize) + throw new Error('Emulated device has wrong width: ' + result.width); + + result.height = parseIntValue(json, 'height'); + if (result.height < 0 || result.height > maxDeviceSize || + result.height < minDeviceSize) + throw new Error('Emulated device has wrong height: ' + result.height); + + return /** @type {!{width: number, height: number}} */ (result); + } + + const result = {}; + result.type = /** @type {string} */ (parseValue(json, 'type', 'string')); + result.userAgent = /** @type {string} */ (parseValue(json, 'user-agent', 'string')); + + const capabilities = parseValue(json, 'capabilities', 'object', []); + if (!Array.isArray(capabilities)) + throw new Error('Emulated device capabilities must be an array'); + result.capabilities = []; + for (let i = 0; i < capabilities.length; ++i) { + if (typeof capabilities[i] !== 'string') + throw new Error('Emulated device capability must be a string'); + result.capabilities.push(capabilities[i]); + } + + result.deviceScaleFactor = /** @type {number} */ (parseValue(json['screen'], 'device-pixel-ratio', 'number')); + if (result.deviceScaleFactor < 0 || result.deviceScaleFactor > 100) + throw new Error('Emulated device has wrong deviceScaleFactor: ' + result.deviceScaleFactor); + + result.vertical = parseOrientation(parseValue(json['screen'], 'vertical', 'object')); + result.horizontal = parseOrientation(parseValue(json['screen'], 'horizontal', 'object')); + return result; +} + +/** + * @param {url} + * @return {!Promise} + */ +function httpGET(url) { + let fulfill, reject; + const promise = new Promise((res, rej) => { + fulfill = res; + reject = rej; + }); + const driver = url.startsWith('https://') ? require('https') : require('http'); + const request = driver.get(url, response => { + let data = ''; + response.setEncoding('utf8'); + response.on('data', chunk => data += chunk); + response.on('end', () => fulfill(data)); + response.on('error', reject); + }); + request.on('error', reject); + return promise; +}