feat: emulate prefers-contrast (#34494)

This commit is contained in:
Simon Knott 2025-02-04 11:15:51 +01:00 committed by GitHub
parent 96d4dc1eda
commit 5d82567346
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 158 additions and 7 deletions

View file

@ -1309,6 +1309,18 @@ Emulates `'forced-colors'` media feature, supported values are `'active'` and `'
* langs: csharp, python * langs: csharp, python
- `forcedColors` <[ForcedColors]<"active"|"none"|"null">> - `forcedColors` <[ForcedColors]<"active"|"none"|"null">>
### option: Page.emulateMedia.contrast
* since: v1.51
* langs: js, java
- `contrast` <null|[Contrast]<"no-preference"|"more">>
Emulates `'prefers-contrast'` media feature, supported values are `'no-preference'`, `'more'`. Passing `null` disables contrast emulation.
### option: Page.emulateMedia.contrast
* since: v1.51
* langs: csharp, python
- `contrast` <[Contrast]<"no-preference"|"more"|"null">>
## async method: Page.evalOnSelector ## async method: Page.evalOnSelector
* since: v1.9 * since: v1.9
* discouraged: This method does not wait for the element to pass actionability * discouraged: This method does not wait for the element to pass actionability

View file

@ -673,6 +673,18 @@ Emulates `'forced-colors'` media feature, supported values are `'active'`, `'non
Emulates `'forced-colors'` media feature, supported values are `'active'`, `'none'`. See [`method: Page.emulateMedia`] for more details. Passing `'null'` resets emulation to system defaults. Defaults to `'none'`. Emulates `'forced-colors'` media feature, supported values are `'active'`, `'none'`. See [`method: Page.emulateMedia`] for more details. Passing `'null'` resets emulation to system defaults. Defaults to `'none'`.
## context-option-contrast
* langs: js, java
- `contrast` <null|[ForcedColors]<"no-preference"|"more">>
Emulates `'prefers-contrast'` media feature, supported values are `'no-preference'`, `'more'`. See [`method: Page.emulateMedia`] for more details. Passing `null` resets emulation to system defaults. Defaults to `'no-preference'`.
## context-option-contrast-csharp-python
* langs: csharp, python
- `contrast` <[ForcedColors]<"no-preference"|"more"|"null">>
Emulates `'prefers-contrast'` media feature, supported values are `'no-preference'`, `'more'`. See [`method: Page.emulateMedia`] for more details. Passing `'null'` resets emulation to system defaults. Defaults to `'no-preference'`.
## context-option-logger ## context-option-logger
* langs: js * langs: js
- `logger` <[Logger]> - `logger` <[Logger]>
@ -973,6 +985,8 @@ between the same pixel in compared images, between zero (strict) and one (lax),
- %%-context-option-reducedMotion-csharp-python-%% - %%-context-option-reducedMotion-csharp-python-%%
- %%-context-option-forcedColors-%% - %%-context-option-forcedColors-%%
- %%-context-option-forcedColors-csharp-python-%% - %%-context-option-forcedColors-csharp-python-%%
- %%-context-option-contrast-%%
- %%-context-option-contrast-csharp-python-%%
- %%-context-option-logger-%% - %%-context-option-logger-%%
- %%-context-option-videospath-%% - %%-context-option-videospath-%%
- %%-context-option-videosize-%% - %%-context-option-videosize-%%

View file

@ -537,6 +537,7 @@ export async function prepareBrowserContextParams(options: BrowserContextOptions
colorScheme: options.colorScheme === null ? 'no-override' : options.colorScheme, colorScheme: options.colorScheme === null ? 'no-override' : options.colorScheme,
reducedMotion: options.reducedMotion === null ? 'no-override' : options.reducedMotion, reducedMotion: options.reducedMotion === null ? 'no-override' : options.reducedMotion,
forcedColors: options.forcedColors === null ? 'no-override' : options.forcedColors, forcedColors: options.forcedColors === null ? 'no-override' : options.forcedColors,
contrast: options.contrast === null ? 'no-override' : options.contrast,
acceptDownloads: toAcceptDownloadsProtocol(options.acceptDownloads), acceptDownloads: toAcceptDownloadsProtocol(options.acceptDownloads),
clientCertificates: await toClientCertificatesProtocol(options.clientCertificates), clientCertificates: await toClientCertificatesProtocol(options.clientCertificates),
}; };

View file

@ -483,12 +483,13 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
await this._channel.requestGC(); await this._channel.requestGC();
} }
async emulateMedia(options: { media?: 'screen' | 'print' | null, colorScheme?: 'dark' | 'light' | 'no-preference' | null, reducedMotion?: 'reduce' | 'no-preference' | null, forcedColors?: 'active' | 'none' | null } = {}) { async emulateMedia(options: { media?: 'screen' | 'print' | null, colorScheme?: 'dark' | 'light' | 'no-preference' | null, reducedMotion?: 'reduce' | 'no-preference' | null, forcedColors?: 'active' | 'none' | null, contrast?: 'no-preference' | 'more' | null } = {}) {
await this._channel.emulateMedia({ await this._channel.emulateMedia({
media: options.media === null ? 'no-override' : options.media, media: options.media === null ? 'no-override' : options.media,
colorScheme: options.colorScheme === null ? 'no-override' : options.colorScheme, colorScheme: options.colorScheme === null ? 'no-override' : options.colorScheme,
reducedMotion: options.reducedMotion === null ? 'no-override' : options.reducedMotion, reducedMotion: options.reducedMotion === null ? 'no-override' : options.reducedMotion,
forcedColors: options.forcedColors === null ? 'no-override' : options.forcedColors, forcedColors: options.forcedColors === null ? 'no-override' : options.forcedColors,
contrast: options.contrast === null ? 'no-override' : options.contrast,
}); });
} }

View file

@ -58,7 +58,7 @@ export type ClientCertificate = {
passphrase?: string; passphrase?: string;
}; };
export type BrowserContextOptions = Omit<channels.BrowserNewContextOptions, 'viewport' | 'noDefaultViewport' | 'extraHTTPHeaders' | 'clientCertificates' | 'storageState' | 'recordHar' | 'colorScheme' | 'reducedMotion' | 'forcedColors' | 'acceptDownloads'> & { export type BrowserContextOptions = Omit<channels.BrowserNewContextOptions, 'viewport' | 'noDefaultViewport' | 'extraHTTPHeaders' | 'clientCertificates' | 'storageState' | 'recordHar' | 'colorScheme' | 'reducedMotion' | 'forcedColors' | 'acceptDownloads' | 'contrast'> & {
viewport?: Size | null; viewport?: Size | null;
extraHTTPHeaders?: Headers; extraHTTPHeaders?: Headers;
logger?: Logger; logger?: Logger;
@ -80,6 +80,7 @@ export type BrowserContextOptions = Omit<channels.BrowserNewContextOptions, 'vie
colorScheme?: 'dark' | 'light' | 'no-preference' | null; colorScheme?: 'dark' | 'light' | 'no-preference' | null;
reducedMotion?: 'reduce' | 'no-preference' | null; reducedMotion?: 'reduce' | 'no-preference' | null;
forcedColors?: 'active' | 'none' | null; forcedColors?: 'active' | 'none' | null;
contrast?: 'more' | 'no-preference' | null;
acceptDownloads?: boolean; acceptDownloads?: boolean;
clientCertificates?: ClientCertificate[]; clientCertificates?: ClientCertificate[];
}; };

View file

@ -582,6 +582,7 @@ scheme.BrowserTypeLaunchPersistentContextParams = tObject({
reducedMotion: tOptional(tEnum(['reduce', 'no-preference', 'no-override'])), reducedMotion: tOptional(tEnum(['reduce', 'no-preference', 'no-override'])),
forcedColors: tOptional(tEnum(['active', 'none', 'no-override'])), forcedColors: tOptional(tEnum(['active', 'none', 'no-override'])),
acceptDownloads: tOptional(tEnum(['accept', 'deny', 'internal-browser-default'])), acceptDownloads: tOptional(tEnum(['accept', 'deny', 'internal-browser-default'])),
contrast: tOptional(tEnum(['no-preference', 'more', 'no-override'])),
baseURL: tOptional(tString), baseURL: tOptional(tString),
recordVideo: tOptional(tObject({ recordVideo: tOptional(tObject({
dir: tString, dir: tString,
@ -668,6 +669,7 @@ scheme.BrowserNewContextParams = tObject({
reducedMotion: tOptional(tEnum(['reduce', 'no-preference', 'no-override'])), reducedMotion: tOptional(tEnum(['reduce', 'no-preference', 'no-override'])),
forcedColors: tOptional(tEnum(['active', 'none', 'no-override'])), forcedColors: tOptional(tEnum(['active', 'none', 'no-override'])),
acceptDownloads: tOptional(tEnum(['accept', 'deny', 'internal-browser-default'])), acceptDownloads: tOptional(tEnum(['accept', 'deny', 'internal-browser-default'])),
contrast: tOptional(tEnum(['no-preference', 'more', 'no-override'])),
baseURL: tOptional(tString), baseURL: tOptional(tString),
recordVideo: tOptional(tObject({ recordVideo: tOptional(tObject({
dir: tString, dir: tString,
@ -737,6 +739,7 @@ scheme.BrowserNewContextForReuseParams = tObject({
reducedMotion: tOptional(tEnum(['reduce', 'no-preference', 'no-override'])), reducedMotion: tOptional(tEnum(['reduce', 'no-preference', 'no-override'])),
forcedColors: tOptional(tEnum(['active', 'none', 'no-override'])), forcedColors: tOptional(tEnum(['active', 'none', 'no-override'])),
acceptDownloads: tOptional(tEnum(['accept', 'deny', 'internal-browser-default'])), acceptDownloads: tOptional(tEnum(['accept', 'deny', 'internal-browser-default'])),
contrast: tOptional(tEnum(['no-preference', 'more', 'no-override'])),
baseURL: tOptional(tString), baseURL: tOptional(tString),
recordVideo: tOptional(tObject({ recordVideo: tOptional(tObject({
dir: tString, dir: tString,
@ -1118,6 +1121,7 @@ scheme.PageEmulateMediaParams = tObject({
colorScheme: tOptional(tEnum(['dark', 'light', 'no-preference', 'no-override'])), colorScheme: tOptional(tEnum(['dark', 'light', 'no-preference', 'no-override'])),
reducedMotion: tOptional(tEnum(['reduce', 'no-preference', 'no-override'])), reducedMotion: tOptional(tEnum(['reduce', 'no-preference', 'no-override'])),
forcedColors: tOptional(tEnum(['active', 'none', 'no-override'])), forcedColors: tOptional(tEnum(['active', 'none', 'no-override'])),
contrast: tOptional(tEnum(['no-preference', 'more', 'no-override'])),
}); });
scheme.PageEmulateMediaResult = tOptional(tObject({})); scheme.PageEmulateMediaResult = tOptional(tObject({}));
scheme.PageExposeBindingParams = tObject({ scheme.PageExposeBindingParams = tObject({
@ -2640,6 +2644,7 @@ scheme.AndroidDeviceLaunchBrowserParams = tObject({
reducedMotion: tOptional(tEnum(['reduce', 'no-preference', 'no-override'])), reducedMotion: tOptional(tEnum(['reduce', 'no-preference', 'no-override'])),
forcedColors: tOptional(tEnum(['active', 'none', 'no-override'])), forcedColors: tOptional(tEnum(['active', 'none', 'no-override'])),
acceptDownloads: tOptional(tEnum(['accept', 'deny', 'internal-browser-default'])), acceptDownloads: tOptional(tEnum(['accept', 'deny', 'internal-browser-default'])),
contrast: tOptional(tEnum(['no-preference', 'more', 'no-override'])),
baseURL: tOptional(tString), baseURL: tOptional(tString),
recordVideo: tOptional(tObject({ recordVideo: tOptional(tObject({
dir: tString, dir: tString,

View file

@ -762,6 +762,7 @@ const paramsThatAllowContextReuse: (keyof channels.BrowserNewContextForReusePara
'colorScheme', 'colorScheme',
'forcedColors', 'forcedColors',
'reducedMotion', 'reducedMotion',
'contrast',
'screen', 'screen',
'userAgent', 'userAgent',
'viewport', 'viewport',

View file

@ -1026,10 +1026,12 @@ class FrameSession {
const colorScheme = emulatedMedia.colorScheme === 'no-override' ? '' : emulatedMedia.colorScheme; const colorScheme = emulatedMedia.colorScheme === 'no-override' ? '' : emulatedMedia.colorScheme;
const reducedMotion = emulatedMedia.reducedMotion === 'no-override' ? '' : emulatedMedia.reducedMotion; const reducedMotion = emulatedMedia.reducedMotion === 'no-override' ? '' : emulatedMedia.reducedMotion;
const forcedColors = emulatedMedia.forcedColors === 'no-override' ? '' : emulatedMedia.forcedColors; const forcedColors = emulatedMedia.forcedColors === 'no-override' ? '' : emulatedMedia.forcedColors;
const contrast = emulatedMedia.contrast === 'no-override' ? '' : emulatedMedia.contrast;
const features = [ const features = [
{ name: 'prefers-color-scheme', value: colorScheme }, { name: 'prefers-color-scheme', value: colorScheme },
{ name: 'prefers-reduced-motion', value: reducedMotion }, { name: 'prefers-reduced-motion', value: reducedMotion },
{ name: 'forced-colors', value: forcedColors }, { name: 'forced-colors', value: forcedColors },
{ name: 'prefers-contrast', value: contrast },
]; ];
await this._client.send('Emulation.setEmulatedMedia', { media, features }); await this._client.send('Emulation.setEmulatedMedia', { media, features });
} }

View file

@ -162,6 +162,7 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageChannel, Brows
colorScheme: params.colorScheme, colorScheme: params.colorScheme,
reducedMotion: params.reducedMotion, reducedMotion: params.reducedMotion,
forcedColors: params.forcedColors, forcedColors: params.forcedColors,
contrast: params.contrast,
}); });
} }

View file

@ -240,6 +240,12 @@ export class FFBrowserContext extends BrowserContext {
forcedColors: this._options.forcedColors !== undefined ? this._options.forcedColors : 'none', forcedColors: this._options.forcedColors !== undefined ? this._options.forcedColors : 'none',
})); }));
} }
if (this._options.contrast !== 'no-override') {
promises.push(this._browser.session.send('Browser.setContrast', {
browserContextId,
contrast: this._options.contrast !== undefined ? this._options.contrast : 'no-preference',
}));
}
if (this._options.recordVideo) { if (this._options.recordVideo) {
promises.push(this._ensureVideosPath().then(() => { promises.push(this._ensureVideosPath().then(() => {
return this._browser.session.send('Browser.setVideoRecordingOptions', { return this._browser.session.send('Browser.setVideoRecordingOptions', {

View file

@ -347,12 +347,14 @@ export class FFPage implements PageDelegate {
const colorScheme = emulatedMedia.colorScheme === 'no-override' ? undefined : emulatedMedia.colorScheme; const colorScheme = emulatedMedia.colorScheme === 'no-override' ? undefined : emulatedMedia.colorScheme;
const reducedMotion = emulatedMedia.reducedMotion === 'no-override' ? undefined : emulatedMedia.reducedMotion; const reducedMotion = emulatedMedia.reducedMotion === 'no-override' ? undefined : emulatedMedia.reducedMotion;
const forcedColors = emulatedMedia.forcedColors === 'no-override' ? undefined : emulatedMedia.forcedColors; const forcedColors = emulatedMedia.forcedColors === 'no-override' ? undefined : emulatedMedia.forcedColors;
const contrast = emulatedMedia.contrast === 'no-override' ? undefined : emulatedMedia.contrast;
await this._session.send('Page.setEmulatedMedia', { await this._session.send('Page.setEmulatedMedia', {
// Empty string means reset. // Empty string means reset.
type: emulatedMedia.media === 'no-override' ? '' : emulatedMedia.media, type: emulatedMedia.media === 'no-override' ? '' : emulatedMedia.media,
colorScheme, colorScheme,
reducedMotion, reducedMotion,
forcedColors, forcedColors,
contrast,
}); });
} }

View file

@ -107,6 +107,7 @@ type EmulatedMedia = {
colorScheme: types.ColorScheme; colorScheme: types.ColorScheme;
reducedMotion: types.ReducedMotion; reducedMotion: types.ReducedMotion;
forcedColors: types.ForcedColors; forcedColors: types.ForcedColors;
contrast: types.Contrast;
}; };
type ExpectScreenshotOptions = ImageComparatorOptions & ScreenshotOptions & { type ExpectScreenshotOptions = ImageComparatorOptions & ScreenshotOptions & {
@ -530,6 +531,8 @@ export class Page extends SdkObject {
this._emulatedMedia.reducedMotion = options.reducedMotion; this._emulatedMedia.reducedMotion = options.reducedMotion;
if (options.forcedColors !== undefined) if (options.forcedColors !== undefined)
this._emulatedMedia.forcedColors = options.forcedColors; this._emulatedMedia.forcedColors = options.forcedColors;
if (options.contrast !== undefined)
this._emulatedMedia.contrast = options.contrast;
await this._delegate.updateEmulateMedia(); await this._delegate.updateEmulateMedia();
} }
@ -541,6 +544,7 @@ export class Page extends SdkObject {
colorScheme: this._emulatedMedia.colorScheme !== undefined ? this._emulatedMedia.colorScheme : contextOptions.colorScheme ?? 'light', colorScheme: this._emulatedMedia.colorScheme !== undefined ? this._emulatedMedia.colorScheme : contextOptions.colorScheme ?? 'light',
reducedMotion: this._emulatedMedia.reducedMotion !== undefined ? this._emulatedMedia.reducedMotion : contextOptions.reducedMotion ?? 'no-preference', reducedMotion: this._emulatedMedia.reducedMotion !== undefined ? this._emulatedMedia.reducedMotion : contextOptions.reducedMotion ?? 'no-preference',
forcedColors: this._emulatedMedia.forcedColors !== undefined ? this._emulatedMedia.forcedColors : contextOptions.forcedColors ?? 'none', forcedColors: this._emulatedMedia.forcedColors !== undefined ? this._emulatedMedia.forcedColors : contextOptions.forcedColors ?? 'none',
contrast: this._emulatedMedia.contrast !== undefined ? this._emulatedMedia.contrast : contextOptions.contrast ?? 'no-preference',
}; };
} }

View file

@ -84,6 +84,8 @@ export type ReducedMotion = 'no-preference' | 'reduce' | 'no-override';
export type ForcedColors = 'active' | 'none' | 'no-override'; export type ForcedColors = 'active' | 'none' | 'no-override';
export type Contrast = 'no-preference' | 'more' | 'no-override';
export type DeviceDescriptor = { export type DeviceDescriptor = {
userAgent: string, userAgent: string,
viewport: Size, viewport: Size,

View file

@ -191,8 +191,8 @@ export class WKPage implements PageDelegate {
if (contextOptions.userAgent) if (contextOptions.userAgent)
promises.push(this.updateUserAgent()); promises.push(this.updateUserAgent());
const emulatedMedia = this._page.emulatedMedia(); const emulatedMedia = this._page.emulatedMedia();
if (emulatedMedia.media || emulatedMedia.colorScheme || emulatedMedia.reducedMotion || emulatedMedia.forcedColors) if (emulatedMedia.media || emulatedMedia.colorScheme || emulatedMedia.reducedMotion || emulatedMedia.forcedColors || emulatedMedia.contrast)
promises.push(WKPage._setEmulateMedia(session, emulatedMedia.media, emulatedMedia.colorScheme, emulatedMedia.reducedMotion, emulatedMedia.forcedColors)); promises.push(WKPage._setEmulateMedia(session, emulatedMedia.media, emulatedMedia.colorScheme, emulatedMedia.reducedMotion, emulatedMedia.forcedColors, emulatedMedia.contrast));
const bootstrapScript = this._calculateBootstrapScript(); const bootstrapScript = this._calculateBootstrapScript();
if (bootstrapScript.length) if (bootstrapScript.length)
promises.push(session.send('Page.setBootstrapScript', { source: bootstrapScript })); promises.push(session.send('Page.setBootstrapScript', { source: bootstrapScript }));
@ -615,7 +615,7 @@ export class WKPage implements PageDelegate {
await this._page._onFileChooserOpened(handle); await this._page._onFileChooserOpened(handle);
} }
private static async _setEmulateMedia(session: WKSession, mediaType: types.MediaType, colorScheme: types.ColorScheme, reducedMotion: types.ReducedMotion, forcedColors: types.ForcedColors): Promise<void> { private static async _setEmulateMedia(session: WKSession, mediaType: types.MediaType, colorScheme: types.ColorScheme, reducedMotion: types.ReducedMotion, forcedColors: types.ForcedColors, contrast: types.Contrast): Promise<void> {
const promises = []; const promises = [];
promises.push(session.send('Page.setEmulatedMedia', { media: mediaType === 'no-override' ? '' : mediaType })); promises.push(session.send('Page.setEmulatedMedia', { media: mediaType === 'no-override' ? '' : mediaType }));
let appearance: any = undefined; let appearance: any = undefined;
@ -639,6 +639,13 @@ export class WKPage implements PageDelegate {
case 'no-override': forcedColorsWk = undefined; break; case 'no-override': forcedColorsWk = undefined; break;
} }
promises.push(session.send('Page.setForcedColors', { forcedColors: forcedColorsWk })); promises.push(session.send('Page.setForcedColors', { forcedColors: forcedColorsWk }));
let contrastWk: any = undefined;
switch (contrast) {
case 'more': contrastWk = 'More'; break;
case 'no-preference': contrastWk = 'NoPreference'; break;
case 'no-override': contrastWk = undefined; break;
}
promises.push(session.send('Page.overrideUserPreference', { name: 'PrefersContrast', value: contrastWk }));
await Promise.all(promises); await Promise.all(promises);
} }
@ -661,7 +668,8 @@ export class WKPage implements PageDelegate {
const colorScheme = emulatedMedia.colorScheme; const colorScheme = emulatedMedia.colorScheme;
const reducedMotion = emulatedMedia.reducedMotion; const reducedMotion = emulatedMedia.reducedMotion;
const forcedColors = emulatedMedia.forcedColors; const forcedColors = emulatedMedia.forcedColors;
await this._forAllSessions(session => WKPage._setEmulateMedia(session, emulatedMedia.media, colorScheme, reducedMotion, forcedColors)); const contrast = emulatedMedia.contrast;
await this._forAllSessions(session => WKPage._setEmulateMedia(session, emulatedMedia.media, colorScheme, reducedMotion, forcedColors, contrast));
} }
async updateEmulatedViewportSize(): Promise<void> { async updateEmulatedViewportSize(): Promise<void> {

View file

@ -2565,6 +2565,12 @@ export interface Page {
*/ */
colorScheme?: null|"light"|"dark"|"no-preference"; colorScheme?: null|"light"|"dark"|"no-preference";
/**
* Emulates `'prefers-contrast'` media feature, supported values are `'no-preference'`, `'more'`. Passing `null`
* disables contrast emulation.
*/
contrast?: null|"no-preference"|"more";
/** /**
* Emulates `'forced-colors'` media feature, supported values are `'active'` and `'none'`. Passing `null` disables * Emulates `'forced-colors'` media feature, supported values are `'active'` and `'none'`. Passing `null` disables
* forced colors emulation. * forced colors emulation.
@ -9770,6 +9776,13 @@ export interface Browser {
*/ */
colorScheme?: null|"light"|"dark"|"no-preference"; colorScheme?: null|"light"|"dark"|"no-preference";
/**
* Emulates `'prefers-contrast'` media feature, supported values are `'no-preference'`, `'more'`. See
* [page.emulateMedia([options])](https://playwright.dev/docs/api/class-page#page-emulate-media) for more details.
* Passing `null` resets emulation to system defaults. Defaults to `'no-preference'`.
*/
contrast?: null|"no-preference"|"more";
/** /**
* Specify device scale factor (can be thought of as dpr). Defaults to `1`. Learn more about * Specify device scale factor (can be thought of as dpr). Defaults to `1`. Learn more about
* [emulating devices with device scale factor](https://playwright.dev/docs/emulation#devices). * [emulating devices with device scale factor](https://playwright.dev/docs/emulation#devices).
@ -14797,6 +14810,13 @@ export interface BrowserType<Unused = {}> {
*/ */
colorScheme?: null|"light"|"dark"|"no-preference"; colorScheme?: null|"light"|"dark"|"no-preference";
/**
* Emulates `'prefers-contrast'` media feature, supported values are `'no-preference'`, `'more'`. See
* [page.emulateMedia([options])](https://playwright.dev/docs/api/class-page#page-emulate-media) for more details.
* Passing `null` resets emulation to system defaults. Defaults to `'no-preference'`.
*/
contrast?: null|"no-preference"|"more";
/** /**
* Specify device scale factor (can be thought of as dpr). Defaults to `1`. Learn more about * Specify device scale factor (can be thought of as dpr). Defaults to `1`. Learn more about
* [emulating devices with device scale factor](https://playwright.dev/docs/emulation#devices). * [emulating devices with device scale factor](https://playwright.dev/docs/emulation#devices).
@ -16609,6 +16629,13 @@ export interface AndroidDevice {
*/ */
colorScheme?: null|"light"|"dark"|"no-preference"; colorScheme?: null|"light"|"dark"|"no-preference";
/**
* Emulates `'prefers-contrast'` media feature, supported values are `'no-preference'`, `'more'`. See
* [page.emulateMedia([options])](https://playwright.dev/docs/api/class-page#page-emulate-media) for more details.
* Passing `null` resets emulation to system defaults. Defaults to `'no-preference'`.
*/
contrast?: null|"no-preference"|"more";
/** /**
* Specify device scale factor (can be thought of as dpr). Defaults to `1`. Learn more about * Specify device scale factor (can be thought of as dpr). Defaults to `1`. Learn more about
* [emulating devices with device scale factor](https://playwright.dev/docs/emulation#devices). * [emulating devices with device scale factor](https://playwright.dev/docs/emulation#devices).
@ -21969,6 +21996,13 @@ export interface BrowserContextOptions {
*/ */
colorScheme?: null|"light"|"dark"|"no-preference"; colorScheme?: null|"light"|"dark"|"no-preference";
/**
* Emulates `'prefers-contrast'` media feature, supported values are `'no-preference'`, `'more'`. See
* [page.emulateMedia([options])](https://playwright.dev/docs/api/class-page#page-emulate-media) for more details.
* Passing `null` resets emulation to system defaults. Defaults to `'no-preference'`.
*/
contrast?: null|"no-preference"|"more";
/** /**
* Specify device scale factor (can be thought of as dpr). Defaults to `1`. Learn more about * Specify device scale factor (can be thought of as dpr). Defaults to `1`. Learn more about
* [emulating devices with device scale factor](https://playwright.dev/docs/emulation#devices). * [emulating devices with device scale factor](https://playwright.dev/docs/emulation#devices).

View file

@ -1006,6 +1006,7 @@ export type BrowserTypeLaunchPersistentContextParams = {
reducedMotion?: 'reduce' | 'no-preference' | 'no-override', reducedMotion?: 'reduce' | 'no-preference' | 'no-override',
forcedColors?: 'active' | 'none' | 'no-override', forcedColors?: 'active' | 'none' | 'no-override',
acceptDownloads?: 'accept' | 'deny' | 'internal-browser-default', acceptDownloads?: 'accept' | 'deny' | 'internal-browser-default',
contrast?: 'no-preference' | 'more' | 'no-override',
baseURL?: string, baseURL?: string,
recordVideo?: { recordVideo?: {
dir: string, dir: string,
@ -1086,6 +1087,7 @@ export type BrowserTypeLaunchPersistentContextOptions = {
reducedMotion?: 'reduce' | 'no-preference' | 'no-override', reducedMotion?: 'reduce' | 'no-preference' | 'no-override',
forcedColors?: 'active' | 'none' | 'no-override', forcedColors?: 'active' | 'none' | 'no-override',
acceptDownloads?: 'accept' | 'deny' | 'internal-browser-default', acceptDownloads?: 'accept' | 'deny' | 'internal-browser-default',
contrast?: 'no-preference' | 'more' | 'no-override',
baseURL?: string, baseURL?: string,
recordVideo?: { recordVideo?: {
dir: string, dir: string,
@ -1201,6 +1203,7 @@ export type BrowserNewContextParams = {
reducedMotion?: 'reduce' | 'no-preference' | 'no-override', reducedMotion?: 'reduce' | 'no-preference' | 'no-override',
forcedColors?: 'active' | 'none' | 'no-override', forcedColors?: 'active' | 'none' | 'no-override',
acceptDownloads?: 'accept' | 'deny' | 'internal-browser-default', acceptDownloads?: 'accept' | 'deny' | 'internal-browser-default',
contrast?: 'no-preference' | 'more' | 'no-override',
baseURL?: string, baseURL?: string,
recordVideo?: { recordVideo?: {
dir: string, dir: string,
@ -1267,6 +1270,7 @@ export type BrowserNewContextOptions = {
reducedMotion?: 'reduce' | 'no-preference' | 'no-override', reducedMotion?: 'reduce' | 'no-preference' | 'no-override',
forcedColors?: 'active' | 'none' | 'no-override', forcedColors?: 'active' | 'none' | 'no-override',
acceptDownloads?: 'accept' | 'deny' | 'internal-browser-default', acceptDownloads?: 'accept' | 'deny' | 'internal-browser-default',
contrast?: 'no-preference' | 'more' | 'no-override',
baseURL?: string, baseURL?: string,
recordVideo?: { recordVideo?: {
dir: string, dir: string,
@ -1336,6 +1340,7 @@ export type BrowserNewContextForReuseParams = {
reducedMotion?: 'reduce' | 'no-preference' | 'no-override', reducedMotion?: 'reduce' | 'no-preference' | 'no-override',
forcedColors?: 'active' | 'none' | 'no-override', forcedColors?: 'active' | 'none' | 'no-override',
acceptDownloads?: 'accept' | 'deny' | 'internal-browser-default', acceptDownloads?: 'accept' | 'deny' | 'internal-browser-default',
contrast?: 'no-preference' | 'more' | 'no-override',
baseURL?: string, baseURL?: string,
recordVideo?: { recordVideo?: {
dir: string, dir: string,
@ -1402,6 +1407,7 @@ export type BrowserNewContextForReuseOptions = {
reducedMotion?: 'reduce' | 'no-preference' | 'no-override', reducedMotion?: 'reduce' | 'no-preference' | 'no-override',
forcedColors?: 'active' | 'none' | 'no-override', forcedColors?: 'active' | 'none' | 'no-override',
acceptDownloads?: 'accept' | 'deny' | 'internal-browser-default', acceptDownloads?: 'accept' | 'deny' | 'internal-browser-default',
contrast?: 'no-preference' | 'more' | 'no-override',
baseURL?: string, baseURL?: string,
recordVideo?: { recordVideo?: {
dir: string, dir: string,
@ -2063,12 +2069,14 @@ export type PageEmulateMediaParams = {
colorScheme?: 'dark' | 'light' | 'no-preference' | 'no-override', colorScheme?: 'dark' | 'light' | 'no-preference' | 'no-override',
reducedMotion?: 'reduce' | 'no-preference' | 'no-override', reducedMotion?: 'reduce' | 'no-preference' | 'no-override',
forcedColors?: 'active' | 'none' | 'no-override', forcedColors?: 'active' | 'none' | 'no-override',
contrast?: 'no-preference' | 'more' | 'no-override',
}; };
export type PageEmulateMediaOptions = { export type PageEmulateMediaOptions = {
media?: 'screen' | 'print' | 'no-override', media?: 'screen' | 'print' | 'no-override',
colorScheme?: 'dark' | 'light' | 'no-preference' | 'no-override', colorScheme?: 'dark' | 'light' | 'no-preference' | 'no-override',
reducedMotion?: 'reduce' | 'no-preference' | 'no-override', reducedMotion?: 'reduce' | 'no-preference' | 'no-override',
forcedColors?: 'active' | 'none' | 'no-override', forcedColors?: 'active' | 'none' | 'no-override',
contrast?: 'no-preference' | 'more' | 'no-override',
}; };
export type PageEmulateMediaResult = void; export type PageEmulateMediaResult = void;
export type PageExposeBindingParams = { export type PageExposeBindingParams = {
@ -4761,6 +4769,7 @@ export type AndroidDeviceLaunchBrowserParams = {
reducedMotion?: 'reduce' | 'no-preference' | 'no-override', reducedMotion?: 'reduce' | 'no-preference' | 'no-override',
forcedColors?: 'active' | 'none' | 'no-override', forcedColors?: 'active' | 'none' | 'no-override',
acceptDownloads?: 'accept' | 'deny' | 'internal-browser-default', acceptDownloads?: 'accept' | 'deny' | 'internal-browser-default',
contrast?: 'no-preference' | 'more' | 'no-override',
baseURL?: string, baseURL?: string,
recordVideo?: { recordVideo?: {
dir: string, dir: string,
@ -4825,6 +4834,7 @@ export type AndroidDeviceLaunchBrowserOptions = {
reducedMotion?: 'reduce' | 'no-preference' | 'no-override', reducedMotion?: 'reduce' | 'no-preference' | 'no-override',
forcedColors?: 'active' | 'none' | 'no-override', forcedColors?: 'active' | 'none' | 'no-override',
acceptDownloads?: 'accept' | 'deny' | 'internal-browser-default', acceptDownloads?: 'accept' | 'deny' | 'internal-browser-default',
contrast?: 'no-preference' | 'more' | 'no-override',
baseURL?: string, baseURL?: string,
recordVideo?: { recordVideo?: {
dir: string, dir: string,

View file

@ -508,6 +508,12 @@ ContextOptions:
- accept - accept
- deny - deny
- internal-browser-default - internal-browser-default
contrast:
type: enum?
literals:
- no-preference
- more
- no-override
baseURL: string? baseURL: string?
recordVideo: recordVideo:
type: object? type: object?
@ -1420,6 +1426,12 @@ Page:
- active - active
- none - none
- no-override - no-override
contrast:
type: enum?
literals:
- no-preference
- more
- no-override
flags: flags:
snapshot: true snapshot: true

View file

@ -51,6 +51,12 @@ it('should support forcedColors option', async ({ launchPersistent, browserName
expect(await page.evaluate(() => matchMedia('(forced-colors: none)').matches)).toBe(false); expect(await page.evaluate(() => matchMedia('(forced-colors: none)').matches)).toBe(false);
}); });
it('should support contrast option', async ({ launchPersistent }) => {
const { page } = await launchPersistent({ contrast: 'more' });
expect.soft(await page.evaluate(() => matchMedia('(prefers-contrast: more)').matches)).toBe(true);
expect.soft(await page.evaluate(() => matchMedia('(prefers-contrast: no-preference)').matches)).toBe(false);
});
it('should support timezoneId option', async ({ launchPersistent, browserName }) => { it('should support timezoneId option', async ({ launchPersistent, browserName }) => {
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

@ -15,7 +15,24 @@
* limitations under the License. * limitations under the License.
*/ */
import { test as it, expect } from './pageTest'; import type { Page } from 'packages/playwright-test';
import { test as it, expect as baseExpect } from './pageTest';
const expect = baseExpect.extend({
async toMatchMedia(page: Page, mediaQuery: string) {
const pass = await page.evaluate(mediaQuery => matchMedia(mediaQuery).matches, mediaQuery).catch(() => false);
return {
message() {
if (pass)
return `Expected "${mediaQuery}" not to match, but it did`;
else
return `Expected "${mediaQuery}" to match, but it did not`;
},
pass,
name: 'toMatchMedia',
};
},
});
it('should emulate type @smoke', async ({ page }) => { it('should emulate type @smoke', async ({ page }) => {
expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(true); expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(true);
@ -158,3 +175,15 @@ it('should emulate forcedColors ', async ({ page, browserName }) => {
await page.emulateMedia({ forcedColors: null }); await page.emulateMedia({ forcedColors: null });
expect(await page.evaluate(() => matchMedia('(forced-colors: none)').matches)).toBe(true); expect(await page.evaluate(() => matchMedia('(forced-colors: none)').matches)).toBe(true);
}); });
it('should emulate contrast ', async ({ page }) => {
await expect(page).toMatchMedia('(prefers-contrast: no-preference)');
await page.emulateMedia({ contrast: 'no-preference' });
await expect(page).toMatchMedia('(prefers-contrast: no-preference)');
await expect(page).not.toMatchMedia('(prefers-contrast: more)');
await page.emulateMedia({ contrast: 'more' });
await expect(page).not.toMatchMedia('(prefers-contrast: no-preference)');
await expect(page).toMatchMedia('(prefers-contrast: more)');
await page.emulateMedia({ contrast: null });
await expect(page).toMatchMedia('(prefers-contrast: no-preference)');
});