From a86363fda695865ddc7c5eeb3c958f5c2da8e61b Mon Sep 17 00:00:00 2001 From: Mathias Bynens Date: Wed, 23 Oct 2019 13:55:00 +0200 Subject: [PATCH] feat(api): add page.emulateMedia{Type,Features} (#5012) --- docs/api.md | 76 +++++++++++++++++++++- experimental/puppeteer-firefox/lib/Page.js | 11 ++-- lib/Page.js | 33 ++++++++-- test/emulation.spec.js | 61 ++++++++++++++--- test/test.js | 1 + 5 files changed, 162 insertions(+), 20 deletions(-) diff --git a/docs/api.md b/docs/api.md index aa78657c..18106ded 100644 --- a/docs/api.md +++ b/docs/api.md @@ -108,7 +108,9 @@ * [page.coverage](#pagecoverage) * [page.deleteCookie(...cookies)](#pagedeletecookiecookies) * [page.emulate(options)](#pageemulateoptions) - * [page.emulateMedia(mediaType)](#pageemulatemediamediatype) + * [page.emulateMedia(type)](#pageemulatemediatype) + * [page.emulateMediaFeatures(features)](#pageemulatemediafeaturesfeatures) + * [page.emulateMediaType(type)](#pageemulatemediatypetype) * [page.evaluate(pageFunction[, ...args])](#pageevaluatepagefunction-args) * [page.evaluateHandle(pageFunction[, ...args])](#pageevaluatehandlepagefunction-args) * [page.evaluateOnNewDocument(pageFunction[, ...args])](#pageevaluateonnewdocumentpagefunction-args) @@ -1276,10 +1278,78 @@ puppeteer.launch().then(async browser => { List of all available devices is available in the source code: [DeviceDescriptors.js](https://github.com/GoogleChrome/puppeteer/blob/master/lib/DeviceDescriptors.js). -#### page.emulateMedia(mediaType) -- `mediaType` Changes the CSS media type of the page. The only allowed values are `'screen'`, `'print'` and `null`. Passing `null` disables media emulation. +#### page.emulateMedia(type) +- `type` Changes the CSS media type of the page. The only allowed values are `'screen'`, `'print'` and `null`. Passing `null` disables CSS media emulation. - returns: <[Promise]> +**Note:** This method is deprecated, and only kept around as an alias for backwards compatibility. Use [`page.emulateMediaType(type)`](#pageemulatemediatypetype) instead. + +#### page.emulateMediaFeatures(features) +- `features` > Given an array of media feature objects, emulates CSS media features on the page. Each media feature object must have the following properties: + - `name` <[string]> The CSS media feature name. Supported names are `'prefers-colors-scheme'` and `'prefers-reduced-motion'`. + - `value` <[string]> The value for the given CSS media feature. +- returns: <[Promise]> + +```js +await page.emulateMediaFeatures([{ name: 'prefers-color-scheme', value: 'dark' }]); +await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)); +// → true +await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)); +// → false +await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)); +// → false + +await page.emulateMediaFeatures([{ name: 'prefers-reduced-motion', value: 'reduce' }]); +await page.evaluate(() => matchMedia('(prefers-reduced-motion: reduce)').matches)); +// → true +await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)); +// → false + +await page.emulateMediaFeatures([{ name: 'prefers-reduced-motion', value: 'reduce' }]); +await page.evaluate(() => matchMedia('(prefers-reduced-motion: reduce)').matches)); +// → true +await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)); +// → false + +await page.emulateMediaFeatures([ + { name: 'prefers-color-scheme', value: 'dark' }, + { name: 'prefers-reduced-motion', value: 'reduce' }, +]); +await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)); +// → true +await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)); +// → false +await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)); +// → false +await page.evaluate(() => matchMedia('(prefers-reduced-motion: reduce)').matches)); +// → true +await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)); +// → false +``` + +#### page.emulateMediaType(type) +- `type` Changes the CSS media type of the page. The only allowed values are `'screen'`, `'print'` and `null`. Passing `null` disables CSS media emulation. +- returns: <[Promise]> + +```js +await page.evaluate(() => matchMedia('screen').matches)); +// → true +await page.evaluate(() => matchMedia('print').matches)); +// → true + +await page.emulateMediaType('print'); +await page.evaluate(() => matchMedia('screen').matches)); +// → false +await page.evaluate(() => matchMedia('print').matches)); +// → true + +await page.emulateMediaType(null); +await page.evaluate(() => matchMedia('screen').matches)); +// → true +await page.evaluate(() => matchMedia('print').matches)); +// → true +``` + #### page.evaluate(pageFunction[, ...args]) - `pageFunction` <[function]|[string]> Function to be evaluated in the page context - `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction` diff --git a/experimental/puppeteer-firefox/lib/Page.js b/experimental/puppeteer-firefox/lib/Page.js index ad7019b0..9e4fa5e6 100644 --- a/experimental/puppeteer-firefox/lib/Page.js +++ b/experimental/puppeteer-firefox/lib/Page.js @@ -150,11 +150,11 @@ class Page extends EventEmitter { } /** - * @param {?string} mediaType + * @param {?string} type */ - async emulateMedia(mediaType) { - assert(mediaType === 'screen' || mediaType === 'print' || mediaType === null, 'Unsupported media type: ' + mediaType); - await this._session.send('Page.setEmulatedMedia', {media: mediaType || ''}); + async emulateMediaType(type) { + assert(type === 'screen' || type === 'print' || type === null, 'Unsupported media type: ' + type); + await this._session.send('Page.setEmulatedMedia', {media: type || ''}); } /** @@ -747,6 +747,9 @@ class Page extends EventEmitter { } } +// Expose alias for deprecated method. +Page.prototype.emulateMedia = Page.prototype.emulateMediaType; + class ConsoleMessage { /** * @param {string} type diff --git a/lib/Page.js b/lib/Page.js index 0bdd217c..a7928a0f 100644 --- a/lib/Page.js +++ b/lib/Page.js @@ -801,11 +801,27 @@ class Page extends EventEmitter { } /** - * @param {?string} mediaType + * @param {?string} type */ - async emulateMedia(mediaType) { - assert(mediaType === 'screen' || mediaType === 'print' || mediaType === null, 'Unsupported media type: ' + mediaType); - await this._client.send('Emulation.setEmulatedMedia', {media: mediaType || ''}); + async emulateMediaType(type) { + assert(type === 'screen' || type === 'print' || type === null, 'Unsupported media type: ' + type); + await this._client.send('Emulation.setEmulatedMedia', {media: type || ''}); + } + + /** + * @param {?Array} features + */ + async emulateMediaFeatures(features) { + if (features === null) + await this._client.send('Emulation.setEmulatedMedia', {features: null}); + if (Array.isArray(features)) { + features.every(mediaFeature => { + const name = mediaFeature.name; + assert(/^prefers-(?:color-scheme|reduced-motion)$/.test(name), 'Unsupported media feature: ' + name); + return true; + }); + await this._client.send('Emulation.setEmulatedMedia', {features: features}); + } } /** @@ -1116,6 +1132,9 @@ class Page extends EventEmitter { } } +// Expose alias for deprecated method. +Page.prototype.emulateMedia = Page.prototype.emulateMediaType; + /** * @typedef {Object} PDFOptions * @property {number=} scale @@ -1161,6 +1180,12 @@ class Page extends EventEmitter { * @property {string=} encoding */ +/** + * @typedef {Object} MediaFeature + * @property {string} name + * @property {string} value + */ + /** @type {!Set} */ const supportedMetrics = new Set([ 'Timestamp', diff --git a/test/emulation.spec.js b/test/emulation.spec.js index 485394cc..6a9bd501 100644 --- a/test/emulation.spec.js +++ b/test/emulation.spec.js @@ -98,20 +98,63 @@ module.exports.addTests = function({testRunner, expect, puppeteer}) { }); describe('Page.emulateMedia', function() { + it('should be an alias for Page.emulateMediaType', async({page, server}) => { + expect(page.emulateMedia).toEqual(page.emulateMediaType); + }); + }); + + describe('Page.emulateMediaType', function() { it('should work', async({page, server}) => { - expect(await page.evaluate(() => window.matchMedia('screen').matches)).toBe(true); - expect(await page.evaluate(() => window.matchMedia('print').matches)).toBe(false); - await page.emulateMedia('print'); - expect(await page.evaluate(() => window.matchMedia('screen').matches)).toBe(false); - expect(await page.evaluate(() => window.matchMedia('print').matches)).toBe(true); - await page.emulateMedia(null); - expect(await page.evaluate(() => window.matchMedia('screen').matches)).toBe(true); - expect(await page.evaluate(() => window.matchMedia('print').matches)).toBe(false); + expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(true); + expect(await page.evaluate(() => matchMedia('print').matches)).toBe(false); + await page.emulateMediaType('print'); + expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(false); + expect(await page.evaluate(() => matchMedia('print').matches)).toBe(true); + await page.emulateMediaType(null); + expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(true); + expect(await page.evaluate(() => matchMedia('print').matches)).toBe(false); }); it('should throw in case of bad argument', async({page, server}) => { let error = null; - await page.emulateMedia('bad').catch(e => error = e); + await page.emulateMediaType('bad').catch(e => error = e); expect(error.message).toBe('Unsupported media type: bad'); }); }); + + describe_fails_ffox('Page.emulateMediaFeatures', function() { + it('should work', async({page, server}) => { + await page.emulateMediaFeatures([ + { name: 'prefers-reduced-motion', value: 'reduce' }, + ]); + expect(await page.evaluate(() => matchMedia('(prefers-reduced-motion: reduce)').matches)).toBe(true); + expect(await page.evaluate(() => matchMedia('(prefers-reduced-motion: no-preference)').matches)).toBe(false); + await page.emulateMediaFeatures([ + { name: 'prefers-color-scheme', value: 'light' }, + ]); + expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(true); + expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(false); + expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)).toBe(false); + await page.emulateMediaFeatures([ + { name: 'prefers-color-scheme', value: 'dark' }, + ]); + expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true); + expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(false); + expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)).toBe(false); + await page.emulateMediaFeatures([ + { name: 'prefers-reduced-motion', value: 'reduce' }, + { name: 'prefers-color-scheme', value: 'light' }, + ]); + expect(await page.evaluate(() => matchMedia('(prefers-reduced-motion: reduce)').matches)).toBe(true); + expect(await page.evaluate(() => matchMedia('(prefers-reduced-motion: no-preference)').matches)).toBe(false); + expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(true); + expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(false); + expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)).toBe(false); + }); + it('should throw in case of bad argument', async({page, server}) => { + let error = null; + await page.emulateMediaFeatures([{ name: 'bad', value: '' }]).catch(e => error = e); + expect(error.message).toBe('Unsupported media feature: bad'); + }); + }); + }; diff --git a/test/test.js b/test/test.js index 8e70d988..55c5cc65 100644 --- a/test/test.js +++ b/test/test.js @@ -73,6 +73,7 @@ beforeEach(async({server, httpsServer}) => { }); const CHROMIUM_NO_COVERAGE = new Set([ + 'page.emulateMedia', // Legacy alias for `page.emulateMediaType`. ]); if (process.env.BROWSER === 'firefox') {