豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content

Commit 5a23a8c

Browse files
authored
feat: support testing light and dark mode (#858)
Closes #130
1 parent d845ad4 commit 5a23a8c

File tree

8 files changed

+179
-0
lines changed

8 files changed

+179
-0
lines changed

docs/tool-reference.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@
206206

207207
**Parameters:**
208208

209+
- **colorScheme** (enum: "dark", "light", "auto") _(optional)_: [`Emulate`](#emulate) the dark or the light mode. Set to "auto" to reset to the default.
209210
- **cpuThrottlingRate** (number) _(optional)_: Represents the CPU slowdown factor. Set the rate to 1 to disable throttling. If omitted, throttling remains unchanged.
210211
- **geolocation** (unknown) _(optional)_: Geolocation to [`emulate`](#emulate). Set to null to clear the geolocation override.
211212
- **networkConditions** (enum: "No emulation", "Offline", "Slow 3G", "Fast 3G", "Slow 4G", "Fast 4G") _(optional)_: Throttle network. Set to "No emulation" to disable. If omitted, conditions remain unchanged.

src/McpContext.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ export class McpContext implements Context {
124124
#geolocationMap = new WeakMap<Page, GeolocationOptions>();
125125
#viewportMap = new WeakMap<Page, Viewport>();
126126
#userAgentMap = new WeakMap<Page, string>();
127+
#colorSchemeMap = new WeakMap<Page, 'dark' | 'light'>();
127128
#dialog?: Dialog;
128129

129130
#pageIdMap = new WeakMap<Page, number>();
@@ -353,6 +354,20 @@ export class McpContext implements Context {
353354
return this.#userAgentMap.get(page) ?? null;
354355
}
355356

357+
setColorScheme(scheme: 'dark' | 'light' | null): void {
358+
const page = this.getSelectedPage();
359+
if (scheme === null) {
360+
this.#colorSchemeMap.delete(page);
361+
} else {
362+
this.#colorSchemeMap.set(page, scheme);
363+
}
364+
}
365+
366+
getColorScheme(): 'dark' | 'light' | null {
367+
const page = this.getSelectedPage();
368+
return this.#colorSchemeMap.get(page) ?? null;
369+
}
370+
356371
setIsRunningPerformanceTrace(x: boolean): void {
357372
this.#isRunningTrace = x;
358373
}

src/McpResponse.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,7 @@ export class McpResponse implements Response {
435435
viewport?: object;
436436
userAgent?: string;
437437
cpuThrottlingRate?: number;
438+
colorScheme?: string;
438439
dialog?: {
439440
type: string;
440441
message: string;
@@ -482,6 +483,13 @@ export class McpResponse implements Response {
482483
structuredContent.cpuThrottlingRate = cpuThrottlingRate;
483484
}
484485

486+
const colorScheme = context.getColorScheme();
487+
if (colorScheme) {
488+
response.push(`## Color Scheme emulation`);
489+
response.push(`Emulating: ${colorScheme}`);
490+
structuredContent.colorScheme = colorScheme;
491+
}
492+
485493
const dialog = context.getDialog();
486494
if (dialog) {
487495
const defaultValueIfNeeded =

src/tools/ToolDefinition.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ export type Context = Readonly<{
122122
getViewport(): Viewport | null;
123123
setUserAgent(userAgent: string | null): void;
124124
getUserAgent(): string | null;
125+
setColorScheme(scheme: 'dark' | 'light' | null): void;
125126
saveTemporaryFile(
126127
data: Uint8Array<ArrayBufferLike>,
127128
mimeType: 'image/png' | 'image/jpeg' | 'image/webp',

src/tools/emulation.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ export const emulate = defineTool({
6363
.describe(
6464
'User agent to emulate. Set to null to clear the user agent override.',
6565
),
66+
colorScheme: zod
67+
.enum(['dark', 'light', 'auto'])
68+
.optional()
69+
.describe(
70+
'Emulate the dark or the light mode. Set to "auto" to reset to the default.',
71+
),
6672
viewport: zod
6773
.object({
6874
width: zod.number().int().min(0).describe('Page width in pixels.'),
@@ -158,6 +164,23 @@ export const emulate = defineTool({
158164
}
159165
}
160166

167+
if (request.params.colorScheme) {
168+
if (request.params.colorScheme === 'auto') {
169+
await page.emulateMediaFeatures([
170+
{name: 'prefers-color-scheme', value: ''},
171+
]);
172+
context.setColorScheme(null);
173+
} else {
174+
await page.emulateMediaFeatures([
175+
{
176+
name: 'prefers-color-scheme',
177+
value: request.params.colorScheme,
178+
},
179+
]);
180+
context.setColorScheme(request.params.colorScheme);
181+
}
182+
}
183+
161184
if (viewport !== undefined) {
162185
if (viewport === null) {
163186
await page.setViewport(null);

tests/McpResponse.test.js.snapshot

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,18 @@ exports[`McpResponse > adds an alert dialog 2`] = `
177177
}
178178
`;
179179

180+
exports[`McpResponse > adds color scheme emulation setting when it is set 1`] = `
181+
# test response
182+
## Color Scheme emulation
183+
Emulating: dark
184+
`;
185+
186+
exports[`McpResponse > adds color scheme emulation setting when it is set 2`] = `
187+
{
188+
"colorScheme": "dark"
189+
}
190+
`;
191+
180192
exports[`McpResponse > adds console messages when the setting is true 1`] = `
181193
# test response
182194
## Console messages

tests/McpResponse.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,20 @@ describe('McpResponse', () => {
383383
});
384384
});
385385

386+
it('adds color scheme emulation setting when it is set', async t => {
387+
await withMcpContext(async (response, context) => {
388+
context.setColorScheme('dark');
389+
const {content, structuredContent} = await response.handle(
390+
'test',
391+
context,
392+
);
393+
t.assert.snapshot?.(getTextContent(content[0]));
394+
t.assert.snapshot?.(
395+
JSON.stringify(stabilizeStructuredContent(structuredContent), null, 2),
396+
);
397+
});
398+
});
399+
386400
it('adds a prompt dialog', async t => {
387401
await withMcpContext(async (response, context) => {
388402
const page = context.getSelectedPage();

tests/tools/emulation.test.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,4 +467,109 @@ describe('emulation', () => {
467467
});
468468
});
469469
});
470+
471+
describe('colorScheme', () => {
472+
it('emulates color scheme', async () => {
473+
await withMcpContext(async (response, context) => {
474+
await emulate.handler(
475+
{
476+
params: {
477+
colorScheme: 'dark',
478+
},
479+
},
480+
response,
481+
context,
482+
);
483+
484+
assert.strictEqual(context.getColorScheme(), 'dark');
485+
const page = context.getSelectedPage();
486+
const scheme = await page.evaluate(() =>
487+
window.matchMedia('(prefers-color-scheme: dark)').matches
488+
? 'dark'
489+
: 'light',
490+
);
491+
assert.strictEqual(scheme, 'dark');
492+
});
493+
});
494+
495+
it('updates color scheme', async () => {
496+
await withMcpContext(async (response, context) => {
497+
await emulate.handler(
498+
{
499+
params: {
500+
colorScheme: 'dark',
501+
},
502+
},
503+
response,
504+
context,
505+
);
506+
assert.strictEqual(context.getColorScheme(), 'dark');
507+
508+
await emulate.handler(
509+
{
510+
params: {
511+
colorScheme: 'light',
512+
},
513+
},
514+
response,
515+
context,
516+
);
517+
assert.strictEqual(context.getColorScheme(), 'light');
518+
const page = context.getSelectedPage();
519+
const scheme = await page.evaluate(() =>
520+
window.matchMedia('(prefers-color-scheme: light)').matches
521+
? 'light'
522+
: 'dark',
523+
);
524+
assert.strictEqual(scheme, 'light');
525+
});
526+
});
527+
528+
it('resets color scheme when set to auto', async () => {
529+
await withMcpContext(async (response, context) => {
530+
const page = context.getSelectedPage();
531+
532+
const initial = await page.evaluate(
533+
() => window.matchMedia('(prefers-color-scheme: dark)').matches,
534+
);
535+
536+
await emulate.handler(
537+
{
538+
params: {
539+
colorScheme: 'dark',
540+
},
541+
},
542+
response,
543+
context,
544+
);
545+
assert.strictEqual(context.getColorScheme(), 'dark');
546+
// Check manually that it is dark
547+
548+
assert.strictEqual(
549+
await page.evaluate(
550+
() => window.matchMedia('(prefers-color-scheme: dark)').matches,
551+
),
552+
true,
553+
);
554+
555+
await emulate.handler(
556+
{
557+
params: {
558+
colorScheme: 'auto',
559+
},
560+
},
561+
response,
562+
context,
563+
);
564+
565+
assert.strictEqual(context.getColorScheme(), null);
566+
assert.strictEqual(
567+
await page.evaluate(
568+
() => window.matchMedia('(prefers-color-scheme: dark)').matches,
569+
),
570+
initial,
571+
);
572+
});
573+
});
574+
});
470575
});

0 commit comments

Comments
 (0)