Skip to content

Commit

Permalink
feat: prefers-contrast
Browse files Browse the repository at this point in the history
  • Loading branch information
Skn0tt committed Jan 27, 2025
1 parent 640e6a8 commit cef75db
Show file tree
Hide file tree
Showing 19 changed files with 158 additions and 7 deletions.
12 changes: 12 additions & 0 deletions docs/src/api/class-page.md
Original file line number Diff line number Diff line change
Expand Up @@ -1309,6 +1309,18 @@ Emulates `'forced-colors'` media feature, supported values are `'active'` and `'
* langs: csharp, python
- `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
* since: v1.9
* discouraged: This method does not wait for the element to pass actionability
Expand Down
14 changes: 14 additions & 0 deletions docs/src/api/params.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'`.

## 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
* langs: js
- `logger` <[Logger]>
Expand Down Expand Up @@ -973,6 +985,8 @@ between the same pixel in compared images, between zero (strict) and one (lax),
- %%-context-option-reducedMotion-csharp-python-%%
- %%-context-option-forcedColors-%%
- %%-context-option-forcedColors-csharp-python-%%
- %%-context-option-contrast-%%
- %%-context-option-contrast-csharp-python-%%
- %%-context-option-logger-%%
- %%-context-option-videospath-%%
- %%-context-option-videosize-%%
Expand Down
1 change: 1 addition & 0 deletions packages/playwright-core/src/client/browserContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,7 @@ export async function prepareBrowserContextParams(options: BrowserContextOptions
colorScheme: options.colorScheme === null ? 'no-override' : options.colorScheme,
reducedMotion: options.reducedMotion === null ? 'no-override' : options.reducedMotion,
forcedColors: options.forcedColors === null ? 'no-override' : options.forcedColors,
contrast: options.contrast === null ? 'no-override' : options.contrast,
acceptDownloads: toAcceptDownloadsProtocol(options.acceptDownloads),
clientCertificates: await toClientCertificatesProtocol(options.clientCertificates),
};
Expand Down
3 changes: 2 additions & 1 deletion packages/playwright-core/src/client/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -483,12 +483,13 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
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({
media: options.media === null ? 'no-override' : options.media,
colorScheme: options.colorScheme === null ? 'no-override' : options.colorScheme,
reducedMotion: options.reducedMotion === null ? 'no-override' : options.reducedMotion,
forcedColors: options.forcedColors === null ? 'no-override' : options.forcedColors,
contrast: options.contrast === null ? 'no-override' : options.contrast,
});
}

Expand Down
3 changes: 2 additions & 1 deletion packages/playwright-core/src/client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export type ClientCertificate = {
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;
extraHTTPHeaders?: Headers;
logger?: Logger;
Expand All @@ -80,6 +80,7 @@ export type BrowserContextOptions = Omit<channels.BrowserNewContextOptions, 'vie
colorScheme?: 'dark' | 'light' | 'no-preference' | null;
reducedMotion?: 'reduce' | 'no-preference' | null;
forcedColors?: 'active' | 'none' | null;
contrast?: 'more' | 'no-preference' | null;
acceptDownloads?: boolean;
clientCertificates?: ClientCertificate[];
};
Expand Down
5 changes: 5 additions & 0 deletions packages/playwright-core/src/protocol/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,7 @@ scheme.BrowserTypeLaunchPersistentContextParams = tObject({
reducedMotion: tOptional(tEnum(['reduce', 'no-preference', 'no-override'])),
forcedColors: tOptional(tEnum(['active', 'none', 'no-override'])),
acceptDownloads: tOptional(tEnum(['accept', 'deny', 'internal-browser-default'])),
contrast: tOptional(tEnum(['no-preference', 'more', 'no-override'])),
baseURL: tOptional(tString),
recordVideo: tOptional(tObject({
dir: tString,
Expand Down Expand Up @@ -668,6 +669,7 @@ scheme.BrowserNewContextParams = tObject({
reducedMotion: tOptional(tEnum(['reduce', 'no-preference', 'no-override'])),
forcedColors: tOptional(tEnum(['active', 'none', 'no-override'])),
acceptDownloads: tOptional(tEnum(['accept', 'deny', 'internal-browser-default'])),
contrast: tOptional(tEnum(['no-preference', 'more', 'no-override'])),
baseURL: tOptional(tString),
recordVideo: tOptional(tObject({
dir: tString,
Expand Down Expand Up @@ -737,6 +739,7 @@ scheme.BrowserNewContextForReuseParams = tObject({
reducedMotion: tOptional(tEnum(['reduce', 'no-preference', 'no-override'])),
forcedColors: tOptional(tEnum(['active', 'none', 'no-override'])),
acceptDownloads: tOptional(tEnum(['accept', 'deny', 'internal-browser-default'])),
contrast: tOptional(tEnum(['no-preference', 'more', 'no-override'])),
baseURL: tOptional(tString),
recordVideo: tOptional(tObject({
dir: tString,
Expand Down Expand Up @@ -1118,6 +1121,7 @@ scheme.PageEmulateMediaParams = tObject({
colorScheme: tOptional(tEnum(['dark', 'light', 'no-preference', 'no-override'])),
reducedMotion: tOptional(tEnum(['reduce', 'no-preference', 'no-override'])),
forcedColors: tOptional(tEnum(['active', 'none', 'no-override'])),
contrast: tOptional(tEnum(['no-preference', 'more', 'no-override'])),
});
scheme.PageEmulateMediaResult = tOptional(tObject({}));
scheme.PageExposeBindingParams = tObject({
Expand Down Expand Up @@ -2634,6 +2638,7 @@ scheme.AndroidDeviceLaunchBrowserParams = tObject({
reducedMotion: tOptional(tEnum(['reduce', 'no-preference', 'no-override'])),
forcedColors: tOptional(tEnum(['active', 'none', 'no-override'])),
acceptDownloads: tOptional(tEnum(['accept', 'deny', 'internal-browser-default'])),
contrast: tOptional(tEnum(['no-preference', 'more', 'no-override'])),
baseURL: tOptional(tString),
recordVideo: tOptional(tObject({
dir: tString,
Expand Down
1 change: 1 addition & 0 deletions packages/playwright-core/src/server/browserContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,7 @@ const paramsThatAllowContextReuse: (keyof channels.BrowserNewContextForReusePara
'colorScheme',
'forcedColors',
'reducedMotion',
'contrast',
'screen',
'userAgent',
'viewport',
Expand Down
2 changes: 2 additions & 0 deletions packages/playwright-core/src/server/chromium/crPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1026,10 +1026,12 @@ class FrameSession {
const colorScheme = emulatedMedia.colorScheme === 'no-override' ? '' : emulatedMedia.colorScheme;
const reducedMotion = emulatedMedia.reducedMotion === 'no-override' ? '' : emulatedMedia.reducedMotion;
const forcedColors = emulatedMedia.forcedColors === 'no-override' ? '' : emulatedMedia.forcedColors;
const contrast = emulatedMedia.contrast === 'no-override' ? '' : emulatedMedia.contrast;
const features = [
{ name: 'prefers-color-scheme', value: colorScheme },
{ name: 'prefers-reduced-motion', value: reducedMotion },
{ name: 'forced-colors', value: forcedColors },
{ name: 'prefers-contrast', value: contrast },
];
await this._client.send('Emulation.setEmulatedMedia', { media, features });
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageChannel, Brows
colorScheme: params.colorScheme,
reducedMotion: params.reducedMotion,
forcedColors: params.forcedColors,
contrast: params.contrast,
});
}

Expand Down
6 changes: 6 additions & 0 deletions packages/playwright-core/src/server/firefox/ffBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,12 @@ export class FFBrowserContext extends BrowserContext {
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) {
promises.push(this._ensureVideosPath().then(() => {
return this._browser.session.send('Browser.setVideoRecordingOptions', {
Expand Down
2 changes: 2 additions & 0 deletions packages/playwright-core/src/server/firefox/ffPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,12 +347,14 @@ export class FFPage implements PageDelegate {
const colorScheme = emulatedMedia.colorScheme === 'no-override' ? undefined : emulatedMedia.colorScheme;
const reducedMotion = emulatedMedia.reducedMotion === 'no-override' ? undefined : emulatedMedia.reducedMotion;
const forcedColors = emulatedMedia.forcedColors === 'no-override' ? undefined : emulatedMedia.forcedColors;
const contrast = emulatedMedia.contrast === 'no-override' ? undefined : emulatedMedia.contrast;
await this._session.send('Page.setEmulatedMedia', {
// Empty string means reset.
type: emulatedMedia.media === 'no-override' ? '' : emulatedMedia.media,
colorScheme,
reducedMotion,
forcedColors,
contrast,
});
}

Expand Down
4 changes: 4 additions & 0 deletions packages/playwright-core/src/server/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ type EmulatedMedia = {
colorScheme: types.ColorScheme;
reducedMotion: types.ReducedMotion;
forcedColors: types.ForcedColors;
contrast: types.Contrast;
};

type ExpectScreenshotOptions = ImageComparatorOptions & ScreenshotOptions & {
Expand Down Expand Up @@ -530,6 +531,8 @@ export class Page extends SdkObject {
this._emulatedMedia.reducedMotion = options.reducedMotion;
if (options.forcedColors !== undefined)
this._emulatedMedia.forcedColors = options.forcedColors;
if (options.contrast !== undefined)
this._emulatedMedia.contrast = options.contrast;

await this._delegate.updateEmulateMedia();
}
Expand All @@ -541,6 +544,7 @@ export class Page extends SdkObject {
colorScheme: this._emulatedMedia.colorScheme !== undefined ? this._emulatedMedia.colorScheme : contextOptions.colorScheme ?? 'light',
reducedMotion: this._emulatedMedia.reducedMotion !== undefined ? this._emulatedMedia.reducedMotion : contextOptions.reducedMotion ?? 'no-preference',
forcedColors: this._emulatedMedia.forcedColors !== undefined ? this._emulatedMedia.forcedColors : contextOptions.forcedColors ?? 'none',
contrast: this._emulatedMedia.contrast !== undefined ? this._emulatedMedia.contrast : contextOptions.contrast ?? 'no-preference',
};
}

Expand Down
2 changes: 2 additions & 0 deletions packages/playwright-core/src/server/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ export type ReducedMotion = 'no-preference' | 'reduce' | 'no-override';

export type ForcedColors = 'active' | 'none' | 'no-override';

export type Contrast = 'no-preference' | 'more' | 'no-override';

export type DeviceDescriptor = {
userAgent: string,
viewport: Size,
Expand Down
16 changes: 12 additions & 4 deletions packages/playwright-core/src/server/webkit/wkPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,8 @@ export class WKPage implements PageDelegate {
if (contextOptions.userAgent)
promises.push(this.updateUserAgent());
const emulatedMedia = this._page.emulatedMedia();
if (emulatedMedia.media || emulatedMedia.colorScheme || emulatedMedia.reducedMotion || emulatedMedia.forcedColors)
promises.push(WKPage._setEmulateMedia(session, 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, emulatedMedia.contrast));
const bootstrapScript = this._calculateBootstrapScript();
if (bootstrapScript.length)
promises.push(session.send('Page.setBootstrapScript', { source: bootstrapScript }));
Expand Down Expand Up @@ -615,7 +615,7 @@ export class WKPage implements PageDelegate {
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 = [];
promises.push(session.send('Page.setEmulatedMedia', { media: mediaType === 'no-override' ? '' : mediaType }));
let appearance: any = undefined;
Expand All @@ -639,6 +639,13 @@ export class WKPage implements PageDelegate {
case 'no-override': forcedColorsWk = undefined; break;
}
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);
}

Expand All @@ -661,7 +668,8 @@ export class WKPage implements PageDelegate {
const colorScheme = emulatedMedia.colorScheme;
const reducedMotion = emulatedMedia.reducedMotion;
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> {
Expand Down
34 changes: 34 additions & 0 deletions packages/playwright-core/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2565,6 +2565,12 @@ export interface Page {
*/
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
* forced colors emulation.
Expand Down Expand Up @@ -9770,6 +9776,13 @@ export interface Browser {
*/
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
* [emulating devices with device scale factor](https://playwright.dev/docs/emulation#devices).
Expand Down Expand Up @@ -14783,6 +14796,13 @@ export interface BrowserType<Unused = {}> {
*/
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
* [emulating devices with device scale factor](https://playwright.dev/docs/emulation#devices).
Expand Down Expand Up @@ -16595,6 +16615,13 @@ export interface AndroidDevice {
*/
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
* [emulating devices with device scale factor](https://playwright.dev/docs/emulation#devices).
Expand Down Expand Up @@ -21952,6 +21979,13 @@ export interface BrowserContextOptions {
*/
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
* [emulating devices with device scale factor](https://playwright.dev/docs/emulation#devices).
Expand Down
Loading

0 comments on commit cef75db

Please sign in to comment.