Skip to content

Commit 7a6c148

Browse files
committed
feat: support device scale factor in --viewport
1 parent 376527f commit 7a6c148

4 files changed

Lines changed: 99 additions & 8 deletions

File tree

src/McpContext.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ interface McpContextOptions {
6363
experimentalIncludeAllPages?: boolean;
6464
// Whether CrUX data should be fetched.
6565
performanceCrux: boolean;
66+
// Viewport emulation applied before the context was created.
67+
initialViewport?: Viewport;
6668
}
6769

6870
const DEFAULT_TIMEOUT = 5_000;
@@ -85,6 +87,16 @@ function getNetworkMultiplierFromString(condition: string | null): number {
8587
return 1;
8688
}
8789

90+
function normalizeViewportSettings(viewport: Viewport): Viewport {
91+
return {
92+
deviceScaleFactor: 1,
93+
isMobile: false,
94+
hasTouch: false,
95+
isLandscape: false,
96+
...viewport,
97+
};
98+
}
99+
88100
export class McpContext implements Context {
89101
browser: Browser;
90102
logger: Debugger;
@@ -151,6 +163,11 @@ export class McpContext implements Context {
151163

152164
async #init() {
153165
const pages = await this.createPagesSnapshot();
166+
if (this.#selectedPage && this.#options.initialViewport) {
167+
this.#selectedPage.emulationSettings.viewport = normalizeViewportSettings(
168+
this.#options.initialViewport,
169+
);
170+
}
154171
await this.createExtensionServiceWorkersSnapshot();
155172
await this.#networkCollector.init(pages);
156173
await this.#consoleCollector.init(pages);
@@ -380,13 +397,7 @@ export class McpContext implements Context {
380397
await page.setViewport(null);
381398
delete newSettings.viewport;
382399
} else {
383-
const defaults = {
384-
deviceScaleFactor: 1,
385-
isMobile: false,
386-
hasTouch: false,
387-
isLandscape: false,
388-
};
389-
const viewport = {...defaults, ...options.viewport};
400+
const viewport = normalizeViewportSettings(options.viewport);
390401
await page.setViewport(viewport);
391402
newSettings.viewport = viewport;
392403
}

src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ export async function createMcpServer(
101101
experimentalDevToolsDebugging: devtools,
102102
experimentalIncludeAllPages: serverArgs.experimentalIncludeAllPages,
103103
performanceCrux: serverArgs.performanceCrux,
104+
initialViewport:
105+
serverArgs.viewport?.deviceScaleFactor === undefined
106+
? undefined
107+
: serverArgs.viewport,
104108
});
105109
}
106110
return context;

tests/tools/lighthouse.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import os from 'node:os';
1010
import path from 'node:path';
1111
import {describe, it} from 'node:test';
1212

13+
import type {ParsedArguments} from '../../src/bin/chrome-devtools-mcp-cli-options.js';
1314
import {lighthouseAudit} from '../../src/tools/lighthouse.js';
1415
import {serverHooks} from '../server.js';
1516
import {html, withMcpContext} from '../utils.js';
@@ -117,6 +118,69 @@ describe('lighthouse', () => {
117118
});
118119
});
119120

121+
it('restores launch-time viewport device scale factor', async () => {
122+
server.addHtmlRoute('/test-launch-viewport', html`<div>Test DPR</div>`);
123+
124+
await withMcpContext(
125+
async (response, context) => {
126+
const page = context.getSelectedPptrPage();
127+
await page.goto(server.getRoute('/test-launch-viewport'));
128+
129+
{
130+
const viewportData = await page.evaluate(() => {
131+
return {
132+
width: window.innerWidth,
133+
height: window.innerHeight,
134+
deviceScaleFactor: window.devicePixelRatio,
135+
};
136+
});
137+
138+
assert.deepStrictEqual(viewportData, {
139+
width: 400,
140+
height: 400,
141+
deviceScaleFactor: 2,
142+
});
143+
}
144+
145+
await lighthouseAudit.handler(
146+
{
147+
params: {
148+
mode: 'snapshot',
149+
device: 'desktop',
150+
},
151+
page: context.getSelectedMcpPage(),
152+
},
153+
response,
154+
context,
155+
);
156+
157+
{
158+
const viewportData = await page.evaluate(() => {
159+
return {
160+
width: window.innerWidth,
161+
height: window.innerHeight,
162+
deviceScaleFactor: window.devicePixelRatio,
163+
};
164+
});
165+
166+
assert.deepStrictEqual(viewportData, {
167+
width: 400,
168+
height: 400,
169+
deviceScaleFactor: 2,
170+
});
171+
}
172+
},
173+
{},
174+
{
175+
viewport: {
176+
width: 400,
177+
height: 400,
178+
deviceScaleFactor: 2,
179+
},
180+
} as ParsedArguments,
181+
);
182+
});
183+
120184
it('runs Lighthouse in snapshot mode with mobile device', async () => {
121185
server.addHtmlRoute('/test-mobile', html`<div>Test Mobile</div>`);
122186

tests/utils.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,15 @@ export async function withMcpContext(
105105
} = {},
106106
args: ParsedArguments = {} as ParsedArguments,
107107
) {
108-
await withBrowser(async browser => {
108+
await withBrowser(async (browser, page) => {
109+
if (args.viewport?.deviceScaleFactor !== undefined) {
110+
await page.setViewport({
111+
width: args.viewport.width,
112+
height: args.viewport.height,
113+
deviceScaleFactor: args.viewport.deviceScaleFactor,
114+
});
115+
}
116+
109117
const response = new McpResponse(args);
110118
if (context) {
111119
context.dispose();
@@ -116,6 +124,10 @@ export async function withMcpContext(
116124
{
117125
experimentalDevToolsDebugging: false,
118126
performanceCrux: options.performanceCrux ?? true,
127+
initialViewport:
128+
args.viewport?.deviceScaleFactor === undefined
129+
? undefined
130+
: args.viewport,
119131
},
120132
Locator,
121133
);

0 commit comments

Comments
 (0)