feat: added reduced motion media query emulation (#6646)

This commit is contained in:
Max Schmitt 2021-05-22 01:56:09 +02:00 committed by GitHub
parent af2fec6bcf
commit ba29e99ace
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 143 additions and 27 deletions

View file

@ -8,12 +8,12 @@
}, },
{ {
"name": "firefox", "name": "firefox",
"revision": "1265", "revision": "1266",
"installByDefault": true "installByDefault": true
}, },
{ {
"name": "firefox-stable", "name": "firefox-stable",
"revision": "1255", "revision": "1256",
"installByDefault": false "installByDefault": false
}, },
{ {

View file

@ -970,6 +970,11 @@ Passing `null` disables CSS media emulation.
Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. Passing Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. Passing
`null` disables color scheme emulation. `null` disables color scheme emulation.
### option: Page.emulateMedia.reducedMotion
- `reducedMotion` <null|[ReducedMotion]<"reduce"|"no-preference">>
Emulates `'prefers-reduced-motion'` media feature, supported values are `'reduce'`, `'no-preference'`. Passing `null` disables reduced motion emulation.
## async method: Page.evalOnSelector ## async method: Page.evalOnSelector
* langs: * langs:
- alias-python: eval_on_selector - alias-python: eval_on_selector

View file

@ -366,6 +366,12 @@ Credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/W
Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. See Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. See
[`method: Page.emulateMedia`] for more details. Defaults to `'light'`. [`method: Page.emulateMedia`] for more details. Defaults to `'light'`.
## context-option-reducedMotion
- `reducedMotion` <[ReducedMotion]<"reduce"|"no-preference">>
Emulates `'prefers-reduced-motion'` media feature, supported values are `'reduce'`, `'no-preference'`. See [`method: Page.emulateMedia`] for more details. Defaults
to `'no-preference'`.
## context-option-logger ## context-option-logger
* langs: js * langs: js
- `logger` <[Logger]> - `logger` <[Logger]>
@ -578,6 +584,7 @@ using the [`method: AndroidDevice.setDefaultTimeout`] method.
- %%-context-option-offline-%% - %%-context-option-offline-%%
- %%-context-option-httpcredentials-%% - %%-context-option-httpcredentials-%%
- %%-context-option-colorscheme-%% - %%-context-option-colorscheme-%%
- %%-context-option-reducedMotion-%%
- %%-context-option-logger-%% - %%-context-option-logger-%%
- %%-context-option-videospath-%% - %%-context-option-videospath-%%
- %%-context-option-videosize-%% - %%-context-option-videosize-%%

View file

@ -423,11 +423,12 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
}); });
} }
async emulateMedia(options: { media?: 'screen' | 'print' | null, colorScheme?: 'dark' | 'light' | 'no-preference' | null } = {}) { async emulateMedia(options: { media?: 'screen' | 'print' | null, colorScheme?: 'dark' | 'light' | 'no-preference' | null, reducedMotion?: 'reduce' | 'no-preference' | null } = {}) {
return this._wrapApiCall('page.emulateMedia', async (channel: channels.PageChannel) => { return this._wrapApiCall('page.emulateMedia', async (channel: channels.PageChannel) => {
await channel.emulateMedia({ await channel.emulateMedia({
media: options.media === null ? 'null' : options.media, media: options.media === null ? 'null' : options.media,
colorScheme: options.colorScheme === null ? 'null' : options.colorScheme, colorScheme: options.colorScheme === null ? 'null' : options.colorScheme,
reducedMotion: options.reducedMotion === null ? 'null' : options.reducedMotion,
}); });
}); });
} }

View file

@ -121,6 +121,7 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageInitializer> i
await this._page.emulateMedia({ await this._page.emulateMedia({
media: params.media === 'null' ? null : params.media, media: params.media === 'null' ? null : params.media,
colorScheme: params.colorScheme === 'null' ? null : params.colorScheme, colorScheme: params.colorScheme === 'null' ? null : params.colorScheme,
reducedMotion: params.reducedMotion === 'null' ? null : params.reducedMotion,
}); });
} }

View file

@ -313,6 +313,7 @@ export type BrowserTypeLaunchPersistentContextParams = {
isMobile?: boolean, isMobile?: boolean,
hasTouch?: boolean, hasTouch?: boolean,
colorScheme?: 'dark' | 'light' | 'no-preference', colorScheme?: 'dark' | 'light' | 'no-preference',
reducedMotion?: 'reduce' | 'no-preference',
acceptDownloads?: boolean, acceptDownloads?: boolean,
_debugName?: string, _debugName?: string,
recordVideo?: { recordVideo?: {
@ -382,6 +383,7 @@ export type BrowserTypeLaunchPersistentContextOptions = {
isMobile?: boolean, isMobile?: boolean,
hasTouch?: boolean, hasTouch?: boolean,
colorScheme?: 'dark' | 'light' | 'no-preference', colorScheme?: 'dark' | 'light' | 'no-preference',
reducedMotion?: 'reduce' | 'no-preference',
acceptDownloads?: boolean, acceptDownloads?: boolean,
_debugName?: string, _debugName?: string,
recordVideo?: { recordVideo?: {
@ -471,6 +473,7 @@ export type BrowserNewContextParams = {
isMobile?: boolean, isMobile?: boolean,
hasTouch?: boolean, hasTouch?: boolean,
colorScheme?: 'dark' | 'light' | 'no-preference', colorScheme?: 'dark' | 'light' | 'no-preference',
reducedMotion?: 'reduce' | 'no-preference',
acceptDownloads?: boolean, acceptDownloads?: boolean,
_debugName?: string, _debugName?: string,
recordVideo?: { recordVideo?: {
@ -527,6 +530,7 @@ export type BrowserNewContextOptions = {
isMobile?: boolean, isMobile?: boolean,
hasTouch?: boolean, hasTouch?: boolean,
colorScheme?: 'dark' | 'light' | 'no-preference', colorScheme?: 'dark' | 'light' | 'no-preference',
reducedMotion?: 'reduce' | 'no-preference',
acceptDownloads?: boolean, acceptDownloads?: boolean,
_debugName?: string, _debugName?: string,
recordVideo?: { recordVideo?: {
@ -973,10 +977,12 @@ export type PageCloseResult = void;
export type PageEmulateMediaParams = { export type PageEmulateMediaParams = {
media?: 'screen' | 'print' | 'null', media?: 'screen' | 'print' | 'null',
colorScheme?: 'dark' | 'light' | 'no-preference' | 'null', colorScheme?: 'dark' | 'light' | 'no-preference' | 'null',
reducedMotion?: 'reduce' | 'no-preference' | 'null',
}; };
export type PageEmulateMediaOptions = { export type PageEmulateMediaOptions = {
media?: 'screen' | 'print' | 'null', media?: 'screen' | 'print' | 'null',
colorScheme?: 'dark' | 'light' | 'no-preference' | 'null', colorScheme?: 'dark' | 'light' | 'no-preference' | 'null',
reducedMotion?: 'reduce' | 'no-preference' | 'null',
}; };
export type PageEmulateMediaResult = void; export type PageEmulateMediaResult = void;
export type PageExposeBindingParams = { export type PageExposeBindingParams = {
@ -2934,6 +2940,7 @@ export type AndroidDeviceLaunchBrowserParams = {
isMobile?: boolean, isMobile?: boolean,
hasTouch?: boolean, hasTouch?: boolean,
colorScheme?: 'dark' | 'light' | 'no-preference', colorScheme?: 'dark' | 'light' | 'no-preference',
reducedMotion?: 'reduce' | 'no-preference',
acceptDownloads?: boolean, acceptDownloads?: boolean,
_debugName?: string, _debugName?: string,
recordVideo?: { recordVideo?: {
@ -2978,6 +2985,7 @@ export type AndroidDeviceLaunchBrowserOptions = {
isMobile?: boolean, isMobile?: boolean,
hasTouch?: boolean, hasTouch?: boolean,
colorScheme?: 'dark' | 'light' | 'no-preference', colorScheme?: 'dark' | 'light' | 'no-preference',
reducedMotion?: 'reduce' | 'no-preference',
acceptDownloads?: boolean, acceptDownloads?: boolean,
_debugName?: string, _debugName?: string,
recordVideo?: { recordVideo?: {

View file

@ -312,6 +312,11 @@ ContextOptions:
- dark - dark
- light - light
- no-preference - no-preference
reducedMotion:
type: enum?
literals:
- reduce
- no-preference
acceptDownloads: boolean? acceptDownloads: boolean?
_debugName: string? _debugName: string?
recordVideo: recordVideo:
@ -717,6 +722,13 @@ Page:
- no-preference - no-preference
# Reset emulated value to the system default. # Reset emulated value to the system default.
- null - null
reducedMotion:
type: enum?
literals:
- reduce
- no-preference
# Reset emulated value to the system default.
- null
exposeBinding: exposeBinding:
parameters: parameters:
@ -2374,6 +2386,11 @@ AndroidDevice:
- dark - dark
- light - light
- no-preference - no-preference
reducedMotion:
type: enum?
literals:
- reduce
- no-preference
acceptDownloads: boolean? acceptDownloads: boolean?
_debugName: string? _debugName: string?
recordVideo: recordVideo:

View file

@ -231,6 +231,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
isMobile: tOptional(tBoolean), isMobile: tOptional(tBoolean),
hasTouch: tOptional(tBoolean), hasTouch: tOptional(tBoolean),
colorScheme: tOptional(tEnum(['dark', 'light', 'no-preference'])), colorScheme: tOptional(tEnum(['dark', 'light', 'no-preference'])),
reducedMotion: tOptional(tEnum(['reduce', 'no-preference'])),
acceptDownloads: tOptional(tBoolean), acceptDownloads: tOptional(tBoolean),
_debugName: tOptional(tString), _debugName: tOptional(tString),
recordVideo: tOptional(tObject({ recordVideo: tOptional(tObject({
@ -289,6 +290,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
isMobile: tOptional(tBoolean), isMobile: tOptional(tBoolean),
hasTouch: tOptional(tBoolean), hasTouch: tOptional(tBoolean),
colorScheme: tOptional(tEnum(['dark', 'light', 'no-preference'])), colorScheme: tOptional(tEnum(['dark', 'light', 'no-preference'])),
reducedMotion: tOptional(tEnum(['reduce', 'no-preference'])),
acceptDownloads: tOptional(tBoolean), acceptDownloads: tOptional(tBoolean),
_debugName: tOptional(tString), _debugName: tOptional(tString),
recordVideo: tOptional(tObject({ recordVideo: tOptional(tObject({
@ -410,6 +412,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
scheme.PageEmulateMediaParams = tObject({ scheme.PageEmulateMediaParams = tObject({
media: tOptional(tEnum(['screen', 'print', 'null'])), media: tOptional(tEnum(['screen', 'print', 'null'])),
colorScheme: tOptional(tEnum(['dark', 'light', 'no-preference', 'null'])), colorScheme: tOptional(tEnum(['dark', 'light', 'no-preference', 'null'])),
reducedMotion: tOptional(tEnum(['reduce', 'no-preference', 'null'])),
}); });
scheme.PageExposeBindingParams = tObject({ scheme.PageExposeBindingParams = tObject({
name: tString, name: tString,
@ -1128,6 +1131,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
isMobile: tOptional(tBoolean), isMobile: tOptional(tBoolean),
hasTouch: tOptional(tBoolean), hasTouch: tOptional(tBoolean),
colorScheme: tOptional(tEnum(['dark', 'light', 'no-preference'])), colorScheme: tOptional(tEnum(['dark', 'light', 'no-preference'])),
reducedMotion: tOptional(tEnum(['reduce', 'no-preference'])),
acceptDownloads: tOptional(tBoolean), acceptDownloads: tOptional(tBoolean),
_debugName: tOptional(tString), _debugName: tOptional(tString),
recordVideo: tOptional(tObject({ recordVideo: tOptional(tObject({

View file

@ -1006,8 +1006,13 @@ class FrameSession {
async _updateEmulateMedia(initial: boolean): Promise<void> { async _updateEmulateMedia(initial: boolean): Promise<void> {
if (this._crPage._browserContext._browser.isClank()) if (this._crPage._browserContext._browser.isClank())
return; return;
const colorScheme = this._page._state.colorScheme || this._crPage._browserContext._options.colorScheme || 'light'; const colorScheme = this._page._state.colorScheme === null ? '' : this._page._state.colorScheme;
const features = colorScheme ? [{ name: 'prefers-color-scheme', value: colorScheme }] : []; const reducedMotion = this._page._state.reducedMotion === null ? '' : this._page._state.reducedMotion;
const features = [
{ name: 'prefers-color-scheme', value: colorScheme },
{ name: 'prefers-reduced-motion', value: reducedMotion },
];
// Empty string disables the override.
await this._client.send('Emulation.setEmulatedMedia', { media: this._page._state.mediaType || '', features }); await this._client.send('Emulation.setEmulatedMedia', { media: this._page._state.mediaType || '', features });
} }

View file

@ -198,8 +198,14 @@ export class FFBrowserContext extends BrowserContext {
promises.push(this.setGeolocation(this._options.geolocation)); promises.push(this.setGeolocation(this._options.geolocation));
if (this._options.offline) if (this._options.offline)
promises.push(this.setOffline(this._options.offline)); promises.push(this.setOffline(this._options.offline));
if (this._options.colorScheme) promises.push(this._browser._connection.send('Browser.setColorScheme', {
promises.push(this._browser._connection.send('Browser.setColorScheme', { browserContextId, colorScheme: this._options.colorScheme })); browserContextId,
colorScheme: this._options.colorScheme !== undefined ? this._options.colorScheme : 'light',
}));
promises.push(this._browser._connection.send('Browser.setReducedMotion', {
browserContextId,
reducedMotion: this._options.reducedMotion !== undefined ? this._options.reducedMotion : 'no-preference',
}));
if (this._options.recordVideo) { if (this._options.recordVideo) {
promises.push(this._ensureVideosPath().then(() => { promises.push(this._ensureVideosPath().then(() => {
return this._browser._connection.send('Browser.setVideoRecordingOptions', { return this._browser._connection.send('Browser.setVideoRecordingOptions', {

View file

@ -352,11 +352,13 @@ export class FFPage implements PageDelegate {
} }
async updateEmulateMedia(): Promise<void> { async updateEmulateMedia(): Promise<void> {
const colorScheme = this._page._state.colorScheme || this._browserContext._options.colorScheme || 'light'; const colorScheme = this._page._state.colorScheme === null ? undefined : this._page._state.colorScheme;
const reducedMotion = this._page._state.reducedMotion === null ? undefined : this._page._state.reducedMotion;
await this._session.send('Page.setEmulatedMedia', { await this._session.send('Page.setEmulatedMedia', {
// Empty string means reset. // Empty string means reset.
type: this._page._state.mediaType === null ? '' : this._page._state.mediaType, type: this._page._state.mediaType === null ? '' : this._page._state.mediaType,
colorScheme colorScheme,
reducedMotion,
}); });
} }

View file

@ -88,6 +88,7 @@ type PageState = {
emulatedSize: { screen: types.Size, viewport: types.Size } | null; emulatedSize: { screen: types.Size, viewport: types.Size } | null;
mediaType: types.MediaType | null; mediaType: types.MediaType | null;
colorScheme: types.ColorScheme | null; colorScheme: types.ColorScheme | null;
reducedMotion: types.ReducedMotion | null;
extraHTTPHeaders: types.HeadersArray | null; extraHTTPHeaders: types.HeadersArray | null;
}; };
@ -159,7 +160,8 @@ export class Page extends SdkObject {
this._state = { this._state = {
emulatedSize: browserContext._options.viewport ? { viewport: browserContext._options.viewport, screen: browserContext._options.screen || browserContext._options.viewport } : null, emulatedSize: browserContext._options.viewport ? { viewport: browserContext._options.viewport, screen: browserContext._options.screen || browserContext._options.viewport } : null,
mediaType: null, mediaType: null,
colorScheme: null, colorScheme: browserContext._options.colorScheme !== undefined ? browserContext._options.colorScheme : 'light',
reducedMotion: browserContext._options.reducedMotion !== undefined ? browserContext._options.reducedMotion : 'no-preference',
extraHTTPHeaders: null, extraHTTPHeaders: null,
}; };
this.accessibility = new accessibility.Accessibility(delegate.getAccessibilityTree.bind(delegate)); this.accessibility = new accessibility.Accessibility(delegate.getAccessibilityTree.bind(delegate));
@ -359,15 +361,13 @@ export class Page extends SdkObject {
}), this._timeoutSettings.navigationTimeout(options)); }), this._timeoutSettings.navigationTimeout(options));
} }
async emulateMedia(options: { media?: types.MediaType | null, colorScheme?: types.ColorScheme | null }) { async emulateMedia(options: { media?: types.MediaType | null, colorScheme?: types.ColorScheme | null, reducedMotion?: types.ReducedMotion | null }) {
if (options.media !== undefined)
assert(options.media === null || types.mediaTypes.has(options.media), 'media: expected one of (screen|print|null)');
if (options.colorScheme !== undefined)
assert(options.colorScheme === null || types.colorSchemes.has(options.colorScheme), 'colorScheme: expected one of (dark|light|no-preference|null)');
if (options.media !== undefined) if (options.media !== undefined)
this._state.mediaType = options.media; this._state.mediaType = options.media;
if (options.colorScheme !== undefined) if (options.colorScheme !== undefined)
this._state.colorScheme = options.colorScheme; this._state.colorScheme = options.colorScheme;
if (options.reducedMotion !== undefined)
this._state.reducedMotion = options.reducedMotion;
await this._delegate.updateEmulateMedia(); await this._delegate.updateEmulateMedia();
await this._doSlowMo(); await this._doSlowMo();
} }

View file

@ -84,6 +84,9 @@ export const mediaTypes: Set<MediaType> = new Set(['screen', 'print']);
export type ColorScheme = 'dark' | 'light' | 'no-preference'; export type ColorScheme = 'dark' | 'light' | 'no-preference';
export const colorSchemes: Set<ColorScheme> = new Set(['dark', 'light', 'no-preference']); export const colorSchemes: Set<ColorScheme> = new Set(['dark', 'light', 'no-preference']);
export type ReducedMotion = 'no-preference' | 'reduce';
export const reducedMotions: Set<ReducedMotion> = new Set(['no-preference', 'reduce']);
export type DeviceDescriptor = { export type DeviceDescriptor = {
userAgent: string, userAgent: string,
viewport: Size, viewport: Size,
@ -237,6 +240,7 @@ export type BrowserContextOptions = {
isMobile?: boolean, isMobile?: boolean,
hasTouch?: boolean, hasTouch?: boolean,
colorScheme?: ColorScheme, colorScheme?: ColorScheme,
reducedMotion?: ReducedMotion,
acceptDownloads?: boolean, acceptDownloads?: boolean,
recordVideo?: { recordVideo?: {
dir: string, dir: string,

View file

@ -180,8 +180,8 @@ export class WKPage implements PageDelegate {
const contextOptions = this._browserContext._options; const contextOptions = this._browserContext._options;
if (contextOptions.userAgent) if (contextOptions.userAgent)
promises.push(session.send('Page.overrideUserAgent', { value: contextOptions.userAgent })); promises.push(session.send('Page.overrideUserAgent', { value: contextOptions.userAgent }));
if (this._page._state.mediaType || this._page._state.colorScheme) if (this._page._state.mediaType || this._page._state.colorScheme || this._page._state.reducedMotion)
promises.push(WKPage._setEmulateMedia(session, this._page._state.mediaType, this._page._state.colorScheme)); promises.push(WKPage._setEmulateMedia(session, this._page._state.mediaType, this._page._state.colorScheme, this._page._state.reducedMotion));
for (const world of ['main', 'utility'] as const) { for (const world of ['main', 'utility'] as const) {
const bootstrapScript = this._calculateBootstrapScript(world); const bootstrapScript = this._calculateBootstrapScript(world);
if (bootstrapScript.length) if (bootstrapScript.length)
@ -580,17 +580,21 @@ export class WKPage implements PageDelegate {
await this._page._onFileChooserOpened(handle); await this._page._onFileChooserOpened(handle);
} }
private static async _setEmulateMedia(session: WKSession, mediaType: types.MediaType | null, colorScheme: types.ColorScheme | null): Promise<void> { private static async _setEmulateMedia(session: WKSession, mediaType: types.MediaType | null, colorScheme: types.ColorScheme | null, reducedMotion: types.ReducedMotion | null): Promise<void> {
const promises = []; const promises = [];
promises.push(session.send('Page.setEmulatedMedia', { media: mediaType || '' })); promises.push(session.send('Page.setEmulatedMedia', { media: mediaType || '' }));
if (colorScheme !== null) { let appearance: any = undefined;
let appearance: any = ''; switch (colorScheme) {
switch (colorScheme) { case 'light': appearance = 'Light'; break;
case 'light': appearance = 'Light'; break; case 'dark': appearance = 'Dark'; break;
case 'dark': appearance = 'Dark'; break;
}
promises.push(session.send('Page.setForcedAppearance', { appearance }));
} }
promises.push(session.send('Page.setForcedAppearance', { appearance }));
let reducedMotionWk: any = undefined;
switch (reducedMotion) {
case 'reduce': reducedMotionWk = 'Reduce'; break;
case 'no-preference': reducedMotionWk = 'NoPreference'; break;
}
promises.push(session.send('Page.setForcedReducedMotion', { reducedMotion: reducedMotionWk }));
await Promise.all(promises); await Promise.all(promises);
} }
@ -609,8 +613,9 @@ export class WKPage implements PageDelegate {
} }
async updateEmulateMedia(): Promise<void> { async updateEmulateMedia(): Promise<void> {
const colorScheme = this._page._state.colorScheme || this._browserContext._options.colorScheme || 'light'; const colorScheme = this._page._state.colorScheme;
await this._forAllSessions(session => WKPage._setEmulateMedia(session, this._page._state.mediaType, colorScheme)); const reducedMotion = this._page._state.reducedMotion;
await this._forAllSessions(session => WKPage._setEmulateMedia(session, this._page._state.mediaType, colorScheme, reducedMotion));
} }
async setEmulatedSize(emulatedSize: types.EmulatedSize): Promise<void> { async setEmulatedSize(emulatedSize: types.EmulatedSize): Promise<void> {

View file

@ -38,6 +38,12 @@ it('should support colorScheme option', async ({launchPersistent}) => {
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true); expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true);
}); });
it('should support reducedMotion option', async ({launchPersistent}) => {
const {page} = await launchPersistent({reducedMotion: '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);
});
it('should support timezoneId option', async ({launchPersistent}) => { it('should support timezoneId option', async ({launchPersistent}) => {
const {page} = await launchPersistent({locale: 'en-US', timezoneId: 'America/Jamaica'}); const {page} = await launchPersistent({locale: 'en-US', timezoneId: 'America/Jamaica'});
expect(await page.evaluate(() => new Date(1479579154987).toString())).toBe('Sat Nov 19 2016 13:12:34 GMT-0500 (Eastern Standard Time)'); expect(await page.evaluate(() => new Date(1479579154987).toString())).toBe('Sat Nov 19 2016 13:12:34 GMT-0500 (Eastern Standard Time)');

View file

@ -114,3 +114,14 @@ it('should change the actual colors in css', async ({page}) => {
await page.emulateMedia({ colorScheme: 'light' }); await page.emulateMedia({ colorScheme: 'light' });
expect(await getBackgroundColor()).toBe('rgb(255, 255, 255)'); expect(await getBackgroundColor()).toBe('rgb(255, 255, 255)');
}); });
it('should emulate reduced motion', async ({page}) => {
expect(await page.evaluate(() => matchMedia('(prefers-reduced-motion: no-preference)').matches)).toBe(true);
await page.emulateMedia({ reducedMotion: '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.emulateMedia({ reducedMotion: 'no-preference' });
expect(await page.evaluate(() => matchMedia('(prefers-reduced-motion: reduce)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(prefers-reduced-motion: no-preference)').matches)).toBe(true);
await page.emulateMedia({ reducedMotion: null });
});

34
types/types.d.ts vendored
View file

@ -1679,6 +1679,12 @@ export interface Page {
* disables CSS media emulation. * disables CSS media emulation.
*/ */
media?: null|"screen"|"print"; media?: null|"screen"|"print";
/**
* Emulates `'prefers-reduced-motion'` media feature, supported values are `'reduce'`, `'no-preference'`. Passing `null`
* disables reduced motion emulation.
*/
reducedMotion?: null|"reduce"|"no-preference";
}): Promise<void>; }): Promise<void>;
/** /**
@ -7174,6 +7180,13 @@ export interface BrowserType<Unused = {}> {
}; };
}; };
/**
* Emulates `'prefers-reduced-motion'` media feature, supported values are `'reduce'`, `'no-preference'`. See
* [page.emulateMedia([options])](https://playwright.dev/docs/api/class-page#pageemulatemediaoptions) for more details.
* Defaults to `'no-preference'`.
*/
reducedMotion?: "reduce"|"no-preference";
/** /**
* Emulates consistent window screen size available inside web page via `window.screen`. Is only used when the `viewport` * Emulates consistent window screen size available inside web page via `window.screen`. Is only used when the `viewport`
* is set. * is set.
@ -8197,6 +8210,13 @@ export interface AndroidDevice {
}; };
}; };
/**
* Emulates `'prefers-reduced-motion'` media feature, supported values are `'reduce'`, `'no-preference'`. See
* [page.emulateMedia([options])](https://playwright.dev/docs/api/class-page#pageemulatemediaoptions) for more details.
* Defaults to `'no-preference'`.
*/
reducedMotion?: "reduce"|"no-preference";
/** /**
* Emulates consistent window screen size available inside web page via `window.screen`. Is only used when the `viewport` * Emulates consistent window screen size available inside web page via `window.screen`. Is only used when the `viewport`
* is set. * is set.
@ -8974,6 +8994,13 @@ export interface Browser extends EventEmitter {
}; };
}; };
/**
* Emulates `'prefers-reduced-motion'` media feature, supported values are `'reduce'`, `'no-preference'`. See
* [page.emulateMedia([options])](https://playwright.dev/docs/api/class-page#pageemulatemediaoptions) for more details.
* Defaults to `'no-preference'`.
*/
reducedMotion?: "reduce"|"no-preference";
/** /**
* Emulates consistent window screen size available inside web page via `window.screen`. Is only used when the `viewport` * Emulates consistent window screen size available inside web page via `window.screen`. Is only used when the `viewport`
* is set. * is set.
@ -11019,6 +11046,13 @@ export interface BrowserContextOptions {
}; };
}; };
/**
* Emulates `'prefers-reduced-motion'` media feature, supported values are `'reduce'`, `'no-preference'`. See
* [page.emulateMedia([options])](https://playwright.dev/docs/api/class-page#pageemulatemediaoptions) for more details.
* Defaults to `'no-preference'`.
*/
reducedMotion?: "reduce"|"no-preference";
/** /**
* Emulates consistent window screen size available inside web page via `window.screen`. Is only used when the `viewport` * Emulates consistent window screen size available inside web page via `window.screen`. Is only used when the `viewport`
* is set. * is set.