feat: add forced-colors media query emulation (#6903)
This commit is contained in:
parent
f7a490f80e
commit
e7d4d61442
|
|
@ -999,6 +999,15 @@ Emulates `'prefers-reduced-motion'` media feature, supported values are `'reduce
|
|||
|
||||
Emulates `'prefers-reduced-motion'` media feature, supported values are `'reduce'`, `'no-preference'`. Passing `null` disables reduced motion emulation.
|
||||
|
||||
### option: Page.emulateMedia.forcedColors
|
||||
- `forcedColors` <null|[ForcedColors]<"active"|"none">>
|
||||
|
||||
Emulates `'forced-colors'` media feature, supported values are `'active'` and `'none'`. Passing `null` disables forced colors emulation.
|
||||
|
||||
:::note
|
||||
It's not supported in WebKit, see [here](https://bugs.webkit.org/show_bug.cgi?id=225281) in their issue tracker.
|
||||
:::
|
||||
|
||||
## async method: Page.evalOnSelector
|
||||
* langs:
|
||||
- alias-python: eval_on_selector
|
||||
|
|
|
|||
|
|
@ -421,6 +421,16 @@ Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`
|
|||
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-forcedColors
|
||||
- `forcedColors` <[ForcedColors]<"active"|"none">>
|
||||
|
||||
Emulates `'forced-colors'` media feature, supported values are `'active'`, `'none'`. See [`method: Page.emulateMedia`] for more details. Defaults
|
||||
to `'none'`.
|
||||
|
||||
:::note
|
||||
It's not supported in WebKit, see [here](https://bugs.webkit.org/show_bug.cgi?id=225281) in their issue tracker.
|
||||
:::
|
||||
|
||||
## context-option-logger
|
||||
* langs: js
|
||||
- `logger` <[Logger]>
|
||||
|
|
@ -642,6 +652,7 @@ using the [`method: AndroidDevice.setDefaultTimeout`] method.
|
|||
- %%-context-option-httpcredentials-%%
|
||||
- %%-context-option-colorscheme-%%
|
||||
- %%-context-option-reducedMotion-%%
|
||||
- %%-context-option-forcedColors-%%
|
||||
- %%-context-option-logger-%%
|
||||
- %%-context-option-videospath-%%
|
||||
- %%-context-option-videosize-%%
|
||||
|
|
|
|||
|
|
@ -410,12 +410,13 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
|
|||
});
|
||||
}
|
||||
|
||||
async emulateMedia(options: { media?: 'screen' | 'print' | null, colorScheme?: 'dark' | 'light' | 'no-preference' | null, reducedMotion?: 'reduce' | 'no-preference' | null } = {}) {
|
||||
async emulateMedia(options: { media?: 'screen' | 'print' | null, colorScheme?: 'dark' | 'light' | 'no-preference' | null, reducedMotion?: 'reduce' | 'no-preference' | null, forcedColors?: 'active' | 'none' | null } = {}) {
|
||||
return this._wrapApiCall(async (channel: channels.PageChannel) => {
|
||||
await channel.emulateMedia({
|
||||
media: options.media === null ? 'null' : options.media,
|
||||
colorScheme: options.colorScheme === null ? 'null' : options.colorScheme,
|
||||
reducedMotion: options.reducedMotion === null ? 'null' : options.reducedMotion,
|
||||
forcedColors: options.forcedColors === null ? 'null' : options.forcedColors,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageInitializer, c
|
|||
media: params.media === 'null' ? null : params.media,
|
||||
colorScheme: params.colorScheme === 'null' ? null : params.colorScheme,
|
||||
reducedMotion: params.reducedMotion === 'null' ? null : params.reducedMotion,
|
||||
forcedColors: params.forcedColors === 'null' ? null : params.forcedColors,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -422,6 +422,7 @@ export type BrowserTypeLaunchPersistentContextParams = {
|
|||
hasTouch?: boolean,
|
||||
colorScheme?: 'dark' | 'light' | 'no-preference',
|
||||
reducedMotion?: 'reduce' | 'no-preference',
|
||||
forcedColors?: 'active' | 'none',
|
||||
acceptDownloads?: boolean,
|
||||
baseURL?: string,
|
||||
_debugName?: string,
|
||||
|
|
@ -494,6 +495,7 @@ export type BrowserTypeLaunchPersistentContextOptions = {
|
|||
hasTouch?: boolean,
|
||||
colorScheme?: 'dark' | 'light' | 'no-preference',
|
||||
reducedMotion?: 'reduce' | 'no-preference',
|
||||
forcedColors?: 'active' | 'none',
|
||||
acceptDownloads?: boolean,
|
||||
baseURL?: string,
|
||||
_debugName?: string,
|
||||
|
|
@ -587,6 +589,7 @@ export type BrowserNewContextParams = {
|
|||
hasTouch?: boolean,
|
||||
colorScheme?: 'dark' | 'light' | 'no-preference',
|
||||
reducedMotion?: 'reduce' | 'no-preference',
|
||||
forcedColors?: 'active' | 'none',
|
||||
acceptDownloads?: boolean,
|
||||
baseURL?: string,
|
||||
_debugName?: string,
|
||||
|
|
@ -646,6 +649,7 @@ export type BrowserNewContextOptions = {
|
|||
hasTouch?: boolean,
|
||||
colorScheme?: 'dark' | 'light' | 'no-preference',
|
||||
reducedMotion?: 'reduce' | 'no-preference',
|
||||
forcedColors?: 'active' | 'none',
|
||||
acceptDownloads?: boolean,
|
||||
baseURL?: string,
|
||||
_debugName?: string,
|
||||
|
|
@ -1168,11 +1172,13 @@ export type PageEmulateMediaParams = {
|
|||
media?: 'screen' | 'print' | 'null',
|
||||
colorScheme?: 'dark' | 'light' | 'no-preference' | 'null',
|
||||
reducedMotion?: 'reduce' | 'no-preference' | 'null',
|
||||
forcedColors?: 'active' | 'none' | 'null',
|
||||
};
|
||||
export type PageEmulateMediaOptions = {
|
||||
media?: 'screen' | 'print' | 'null',
|
||||
colorScheme?: 'dark' | 'light' | 'no-preference' | 'null',
|
||||
reducedMotion?: 'reduce' | 'no-preference' | 'null',
|
||||
forcedColors?: 'active' | 'none' | 'null',
|
||||
};
|
||||
export type PageEmulateMediaResult = void;
|
||||
export type PageExposeBindingParams = {
|
||||
|
|
@ -3365,6 +3371,7 @@ export type AndroidDeviceLaunchBrowserParams = {
|
|||
hasTouch?: boolean,
|
||||
colorScheme?: 'dark' | 'light' | 'no-preference',
|
||||
reducedMotion?: 'reduce' | 'no-preference',
|
||||
forcedColors?: 'active' | 'none',
|
||||
acceptDownloads?: boolean,
|
||||
_debugName?: string,
|
||||
recordVideo?: {
|
||||
|
|
@ -3411,6 +3418,7 @@ export type AndroidDeviceLaunchBrowserOptions = {
|
|||
hasTouch?: boolean,
|
||||
colorScheme?: 'dark' | 'light' | 'no-preference',
|
||||
reducedMotion?: 'reduce' | 'no-preference',
|
||||
forcedColors?: 'active' | 'none',
|
||||
acceptDownloads?: boolean,
|
||||
_debugName?: string,
|
||||
recordVideo?: {
|
||||
|
|
|
|||
|
|
@ -312,6 +312,11 @@ ContextOptions:
|
|||
literals:
|
||||
- reduce
|
||||
- no-preference
|
||||
forcedColors:
|
||||
type: enum?
|
||||
literals:
|
||||
- active
|
||||
- none
|
||||
acceptDownloads: boolean?
|
||||
baseURL: string?
|
||||
_debugName: string?
|
||||
|
|
@ -834,6 +839,13 @@ Page:
|
|||
- no-preference
|
||||
# Reset emulated value to the system default.
|
||||
- null
|
||||
forcedColors:
|
||||
type: enum?
|
||||
literals:
|
||||
- active
|
||||
- none
|
||||
# Reset emulated value to the system default.
|
||||
- null
|
||||
|
||||
exposeBinding:
|
||||
parameters:
|
||||
|
|
@ -2719,6 +2731,11 @@ AndroidDevice:
|
|||
literals:
|
||||
- reduce
|
||||
- no-preference
|
||||
forcedColors:
|
||||
type: enum?
|
||||
literals:
|
||||
- active
|
||||
- none
|
||||
acceptDownloads: boolean?
|
||||
_debugName: string?
|
||||
recordVideo:
|
||||
|
|
|
|||
|
|
@ -266,6 +266,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
|||
hasTouch: tOptional(tBoolean),
|
||||
colorScheme: tOptional(tEnum(['dark', 'light', 'no-preference'])),
|
||||
reducedMotion: tOptional(tEnum(['reduce', 'no-preference'])),
|
||||
forcedColors: tOptional(tEnum(['active', 'none'])),
|
||||
acceptDownloads: tOptional(tBoolean),
|
||||
baseURL: tOptional(tString),
|
||||
_debugName: tOptional(tString),
|
||||
|
|
@ -325,6 +326,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
|||
hasTouch: tOptional(tBoolean),
|
||||
colorScheme: tOptional(tEnum(['dark', 'light', 'no-preference'])),
|
||||
reducedMotion: tOptional(tEnum(['reduce', 'no-preference'])),
|
||||
forcedColors: tOptional(tEnum(['active', 'none'])),
|
||||
acceptDownloads: tOptional(tBoolean),
|
||||
baseURL: tOptional(tString),
|
||||
_debugName: tOptional(tString),
|
||||
|
|
@ -474,6 +476,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
|||
media: tOptional(tEnum(['screen', 'print', 'null'])),
|
||||
colorScheme: tOptional(tEnum(['dark', 'light', 'no-preference', 'null'])),
|
||||
reducedMotion: tOptional(tEnum(['reduce', 'no-preference', 'null'])),
|
||||
forcedColors: tOptional(tEnum(['active', 'none', 'null'])),
|
||||
});
|
||||
scheme.PageExposeBindingParams = tObject({
|
||||
name: tString,
|
||||
|
|
@ -1264,6 +1267,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
|||
hasTouch: tOptional(tBoolean),
|
||||
colorScheme: tOptional(tEnum(['dark', 'light', 'no-preference'])),
|
||||
reducedMotion: tOptional(tEnum(['reduce', 'no-preference'])),
|
||||
forcedColors: tOptional(tEnum(['active', 'none'])),
|
||||
acceptDownloads: tOptional(tBoolean),
|
||||
_debugName: tOptional(tString),
|
||||
recordVideo: tOptional(tObject({
|
||||
|
|
|
|||
|
|
@ -982,9 +982,11 @@ class FrameSession {
|
|||
return;
|
||||
const colorScheme = this._page._state.colorScheme === null ? '' : this._page._state.colorScheme;
|
||||
const reducedMotion = this._page._state.reducedMotion === null ? '' : this._page._state.reducedMotion;
|
||||
const forcedColors = this._page._state.forcedColors === null ? '' : this._page._state.forcedColors;
|
||||
const features = [
|
||||
{ name: 'prefers-color-scheme', value: colorScheme },
|
||||
{ name: 'prefers-reduced-motion', value: reducedMotion },
|
||||
{ name: 'forced-colors', value: forcedColors },
|
||||
];
|
||||
// Empty string disables the override.
|
||||
await this._client.send('Emulation.setEmulatedMedia', { media: this._page._state.mediaType || '', features });
|
||||
|
|
|
|||
|
|
@ -209,6 +209,10 @@ export class FFBrowserContext extends BrowserContext {
|
|||
browserContextId,
|
||||
reducedMotion: this._options.reducedMotion !== undefined ? this._options.reducedMotion : 'no-preference',
|
||||
}));
|
||||
promises.push(this._browser._connection.send('Browser.setForcedColors', {
|
||||
browserContextId,
|
||||
forcedColors: this._options.forcedColors !== undefined ? this._options.forcedColors : 'none',
|
||||
}));
|
||||
if (this._options.recordVideo) {
|
||||
promises.push(this._ensureVideosPath().then(() => {
|
||||
return this._browser._connection.send('Browser.setVideoRecordingOptions', {
|
||||
|
|
|
|||
|
|
@ -354,11 +354,13 @@ export class FFPage implements PageDelegate {
|
|||
async updateEmulateMedia(): Promise<void> {
|
||||
const colorScheme = this._page._state.colorScheme === null ? undefined : this._page._state.colorScheme;
|
||||
const reducedMotion = this._page._state.reducedMotion === null ? undefined : this._page._state.reducedMotion;
|
||||
const forcedColors = this._page._state.forcedColors === null ? undefined : this._page._state.forcedColors;
|
||||
await this._session.send('Page.setEmulatedMedia', {
|
||||
// Empty string means reset.
|
||||
type: this._page._state.mediaType === null ? '' : this._page._state.mediaType,
|
||||
colorScheme,
|
||||
reducedMotion,
|
||||
forcedColors,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ type PageState = {
|
|||
mediaType: types.MediaType | null;
|
||||
colorScheme: types.ColorScheme | null;
|
||||
reducedMotion: types.ReducedMotion | null;
|
||||
forcedColors: types.ForcedColors | null;
|
||||
extraHTTPHeaders: types.HeadersArray | null;
|
||||
};
|
||||
|
||||
|
|
@ -154,6 +155,7 @@ export class Page extends SdkObject {
|
|||
mediaType: null,
|
||||
colorScheme: browserContext._options.colorScheme !== undefined ? browserContext._options.colorScheme : 'light',
|
||||
reducedMotion: browserContext._options.reducedMotion !== undefined ? browserContext._options.reducedMotion : 'no-preference',
|
||||
forcedColors: browserContext._options.forcedColors !== undefined ? browserContext._options.forcedColors : 'none',
|
||||
extraHTTPHeaders: null,
|
||||
};
|
||||
this.accessibility = new accessibility.Accessibility(delegate.getAccessibilityTree.bind(delegate));
|
||||
|
|
@ -353,13 +355,15 @@ export class Page extends SdkObject {
|
|||
}), this._timeoutSettings.navigationTimeout(options));
|
||||
}
|
||||
|
||||
async emulateMedia(options: { media?: types.MediaType | null, colorScheme?: types.ColorScheme | null, reducedMotion?: types.ReducedMotion | null }) {
|
||||
async emulateMedia(options: { media?: types.MediaType | null, colorScheme?: types.ColorScheme | null, reducedMotion?: types.ReducedMotion | null, forcedColors?: types.ForcedColors | null }) {
|
||||
if (options.media !== undefined)
|
||||
this._state.mediaType = options.media;
|
||||
if (options.colorScheme !== undefined)
|
||||
this._state.colorScheme = options.colorScheme;
|
||||
if (options.reducedMotion !== undefined)
|
||||
this._state.reducedMotion = options.reducedMotion;
|
||||
if (options.forcedColors !== undefined)
|
||||
this._state.forcedColors = options.forcedColors;
|
||||
await this._delegate.updateEmulateMedia();
|
||||
await this._doSlowMo();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,6 +96,9 @@ export const colorSchemes: Set<ColorScheme> = new Set(['dark', 'light', 'no-pref
|
|||
export type ReducedMotion = 'no-preference' | 'reduce';
|
||||
export const reducedMotions: Set<ReducedMotion> = new Set(['no-preference', 'reduce']);
|
||||
|
||||
export type ForcedColors = 'active' | 'none';
|
||||
export const forcedColors: Set<ForcedColors> = new Set(['active', 'none']);
|
||||
|
||||
export type DeviceDescriptor = {
|
||||
userAgent: string,
|
||||
viewport: Size,
|
||||
|
|
@ -256,6 +259,7 @@ export type BrowserContextOptions = {
|
|||
hasTouch?: boolean,
|
||||
colorScheme?: ColorScheme,
|
||||
reducedMotion?: ReducedMotion,
|
||||
forcedColors?: ForcedColors,
|
||||
acceptDownloads?: boolean,
|
||||
recordVideo?: {
|
||||
dir: string,
|
||||
|
|
|
|||
|
|
@ -44,6 +44,13 @@ it('should support reducedMotion option', async ({launchPersistent}) => {
|
|||
expect(await page.evaluate(() => matchMedia('(prefers-reduced-motion: no-preference)').matches)).toBe(false);
|
||||
});
|
||||
|
||||
it('should support forcedColors option', async ({launchPersistent, browserName}) => {
|
||||
it.skip(browserName === 'webkit', 'https://bugs.webkit.org/show_bug.cgi?id=225281');
|
||||
const {page} = await launchPersistent({forcedColors: 'active'});
|
||||
expect(await page.evaluate(() => matchMedia('(forced-colors: active)').matches)).toBe(true);
|
||||
expect(await page.evaluate(() => matchMedia('(forced-colors: none)').matches)).toBe(false);
|
||||
});
|
||||
|
||||
it('should support timezoneId option', async ({launchPersistent, browserName}) => {
|
||||
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)');
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ it('should throw in case of bad media argument', async ({page}) => {
|
|||
expect(error.message).toContain('media: expected one of (screen|print|null)');
|
||||
});
|
||||
|
||||
it('should emulate scheme work', async ({page}) => {
|
||||
it('should emulate colorScheme should work', async ({page}) => {
|
||||
await page.emulateMedia({ colorScheme: '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);
|
||||
|
|
@ -125,3 +125,17 @@ it('should emulate reduced motion', async ({page}) => {
|
|||
expect(await page.evaluate(() => matchMedia('(prefers-reduced-motion: no-preference)').matches)).toBe(true);
|
||||
await page.emulateMedia({ reducedMotion: null });
|
||||
});
|
||||
|
||||
it('should emulate forcedColors ', async ({page, browserName, isAndroid}) => {
|
||||
it.skip(browserName === 'webkit', 'https://bugs.webkit.org/show_bug.cgi?id=225281');
|
||||
it.fixme(isAndroid);
|
||||
expect(await page.evaluate(() => matchMedia('(forced-colors: none)').matches)).toBe(true);
|
||||
await page.emulateMedia({ forcedColors: 'none' });
|
||||
expect(await page.evaluate(() => matchMedia('(forced-colors: none)').matches)).toBe(true);
|
||||
expect(await page.evaluate(() => matchMedia('(forced-colors: active)').matches)).toBe(false);
|
||||
await page.emulateMedia({ forcedColors: 'active' });
|
||||
expect(await page.evaluate(() => matchMedia('(forced-colors: none)').matches)).toBe(false);
|
||||
expect(await page.evaluate(() => matchMedia('(forced-colors: active)').matches)).toBe(true);
|
||||
await page.emulateMedia({ forcedColors: null });
|
||||
expect(await page.evaluate(() => matchMedia('(forced-colors: none)').matches)).toBe(true);
|
||||
});
|
||||
|
|
|
|||
44
types/types.d.ts
vendored
44
types/types.d.ts
vendored
|
|
@ -1932,6 +1932,14 @@ export interface Page {
|
|||
*/
|
||||
colorScheme?: null|"light"|"dark"|"no-preference";
|
||||
|
||||
/**
|
||||
* Emulates `'forced-colors'` media feature, supported values are `'active'` and `'none'`. Passing `null` disables forced
|
||||
* colors emulation.
|
||||
*
|
||||
* > NOTE: It's not supported in WebKit, see [here](https://bugs.webkit.org/show_bug.cgi?id=225281) in their issue tracker.
|
||||
*/
|
||||
forcedColors?: null|"active"|"none";
|
||||
|
||||
/**
|
||||
* Changes the CSS media type of the page. The only allowed values are `'screen'`, `'print'` and `null`. Passing `null`
|
||||
* disables CSS media emulation.
|
||||
|
|
@ -9719,6 +9727,15 @@ export interface BrowserType<Unused = {}> {
|
|||
*/
|
||||
extraHTTPHeaders?: { [key: string]: string; };
|
||||
|
||||
/**
|
||||
* Emulates `'forced-colors'` media feature, supported values are `'active'`, `'none'`. See
|
||||
* [page.emulateMedia([options])](https://playwright.dev/docs/api/class-page#page-emulate-media) for more details. Defaults
|
||||
* to `'none'`.
|
||||
*
|
||||
* > NOTE: It's not supported in WebKit, see [here](https://bugs.webkit.org/show_bug.cgi?id=225281) in their issue tracker.
|
||||
*/
|
||||
forcedColors?: "active"|"none";
|
||||
|
||||
geolocation?: {
|
||||
/**
|
||||
* Latitude between -90 and 90.
|
||||
|
|
@ -10904,6 +10921,15 @@ export interface AndroidDevice {
|
|||
*/
|
||||
extraHTTPHeaders?: { [key: string]: string; };
|
||||
|
||||
/**
|
||||
* Emulates `'forced-colors'` media feature, supported values are `'active'`, `'none'`. See
|
||||
* [page.emulateMedia([options])](https://playwright.dev/docs/api/class-page#page-emulate-media) for more details. Defaults
|
||||
* to `'none'`.
|
||||
*
|
||||
* > NOTE: It's not supported in WebKit, see [here](https://bugs.webkit.org/show_bug.cgi?id=225281) in their issue tracker.
|
||||
*/
|
||||
forcedColors?: "active"|"none";
|
||||
|
||||
geolocation?: {
|
||||
/**
|
||||
* Latitude between -90 and 90.
|
||||
|
|
@ -11671,6 +11697,15 @@ export interface Browser extends EventEmitter {
|
|||
*/
|
||||
extraHTTPHeaders?: { [key: string]: string; };
|
||||
|
||||
/**
|
||||
* Emulates `'forced-colors'` media feature, supported values are `'active'`, `'none'`. See
|
||||
* [page.emulateMedia([options])](https://playwright.dev/docs/api/class-page#page-emulate-media) for more details. Defaults
|
||||
* to `'none'`.
|
||||
*
|
||||
* > NOTE: It's not supported in WebKit, see [here](https://bugs.webkit.org/show_bug.cgi?id=225281) in their issue tracker.
|
||||
*/
|
||||
forcedColors?: "active"|"none";
|
||||
|
||||
geolocation?: {
|
||||
/**
|
||||
* Latitude between -90 and 90.
|
||||
|
|
@ -13932,6 +13967,15 @@ export interface BrowserContextOptions {
|
|||
*/
|
||||
extraHTTPHeaders?: { [key: string]: string; };
|
||||
|
||||
/**
|
||||
* Emulates `'forced-colors'` media feature, supported values are `'active'`, `'none'`. See
|
||||
* [page.emulateMedia([options])](https://playwright.dev/docs/api/class-page#page-emulate-media) for more details. Defaults
|
||||
* to `'none'`.
|
||||
*
|
||||
* > NOTE: It's not supported in WebKit, see [here](https://bugs.webkit.org/show_bug.cgi?id=225281) in their issue tracker.
|
||||
*/
|
||||
forcedColors?: "active"|"none";
|
||||
|
||||
geolocation?: Geolocation;
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue