Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: emulate prefers-contrast #34494

Merged
merged 1 commit into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading