mirror of
https://github.com/ChromeDevTools/chrome-devtools-mcp.git
synced 2026-11-04 15:00:11 +00:00
fix(lighthouse): restore the emulated viewport after a navigation audit (#3189)
Fixes #2968 After a `lighthouse_audit` in `navigation` mode, the page keeps the emulated width and height but loses `devicePixelRatio`, the screen size and the screen orientation, while every response still prints `Emulating viewport: {...}`. Calling `emulate` again with the same viewport does not repair it. Cause: `restoreEmulation()` re-sent the same viewport. Lighthouse uses its own CDP session, which replaces the page's device metrics override and clears it when it detaches. Chrome ignores an override that is identical to the last one the same session sent, so puppeteer's second `setViewport` with the same values does nothing. Change: when a viewport is configured, `restoreEmulation()` first sends it with `width + 1`, then applies the real one through `emulate()`. `isMobile` and `hasTouch` are unchanged between the two calls, so puppeteer does not reload the page (`setViewport(null)` first would, I checked). How I checked it (GitHub Actions on a scratch repo, Chrome with the AppArmor userns restriction off, base `6606011`): - Probe on the real browser, mobile viewport 390x844x3, `navigation` audit: before the audit `dpr 3, screen.width 390, portrait-primary`; after the audit without the change `dpr 1, screen.width 3840 (host), landscape-primary`; plain `restoreEmulation()` again: unchanged. With the width + 1 step first: `dpr 3, screen.width 390, portrait-primary` and a marker set on `window` survives, so no reload. `setViewport(null)` first also repairs it but reloads the page (marker lost). `snapshot` mode was not affected. - New test `keeps the emulated viewport after a navigation audit` (real browser) and an updated `restoreEmulation()` unit test, plus a test that no viewport call is added when none was configured. Without the change 2 tests fail; with it `tests/McpPage.test.ts` and `tests/tools/lighthouse.test.ts` pass (84 of 84). - `eslint` and `prettier --check` on the three changed files: clean. Not done: the full test suite; Windows and macOS. The `width + 1` step briefly applies a viewport one pixel wider, which is invisible to the agent but could trigger a `resize` event in the page. --------- Co-authored-by: Musa Toktaş <mtoktas38@gmail.com>
This commit is contained in:
1 parent
382f4418ac
commit
c777ece9a8
3 files changed
+64
-3
No files matched your search
@@ -1115,6 +1115,19 @@ export class McpPage implements ContextPage {
|
||||
|
||||
async restoreEmulation(): Promise<void> {
|
||||
const currentSetting = this.emulationSettings;
|
||||
const {viewport} = currentSetting;
|
||||
if (viewport) {
|
||||
// Lighthouse's CDP session replaces the device metrics override and
|
||||
// clears it on detach, and Chrome ignores an override identical to the
|
||||
// last one this session sent. A different width first makes the real
|
||||
// call take effect without a reload (`isMobile` and `hasTouch` stay the
|
||||
// same). This also runs after snapshot audits and fires a resize event,
|
||||
// so do not remove it as redundant.
|
||||
await this.pptrPage.setViewport({
|
||||
...viewport,
|
||||
width: Math.max(1, viewport.width - 1),
|
||||
});
|
||||
}
|
||||
await this.emulate(currentSetting);
|
||||
}
|
||||
|
||||
|
||||
+21
-1
@@ -777,8 +777,18 @@ describe('McpPage', () => {
|
||||
|
||||
await mcpPage.restoreEmulation();
|
||||
|
||||
sinon.assert.calledTwice(pptrPage.setViewport);
|
||||
// A different width first, because Chrome ignores an override that is
|
||||
// identical to the last one of this session (see restoreEmulation).
|
||||
sinon.assert.calledThrice(pptrPage.setViewport);
|
||||
sinon.assert.calledWithExactly(pptrPage.setViewport.secondCall, {
|
||||
width: 399,
|
||||
height: 400,
|
||||
deviceScaleFactor: 1,
|
||||
isMobile: false,
|
||||
hasTouch: false,
|
||||
isLandscape: false,
|
||||
});
|
||||
sinon.assert.calledWithExactly(pptrPage.setViewport.thirdCall, {
|
||||
width: 400,
|
||||
height: 400,
|
||||
deviceScaleFactor: 1,
|
||||
@@ -788,6 +798,16 @@ describe('McpPage', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('does not send a scratch viewport when none was configured', async () => {
|
||||
const {mcpPage, pptrPage} = await createMcpPage();
|
||||
await mcpPage.emulate({cpuThrottlingRate: 4});
|
||||
pptrPage.setViewport.resetHistory();
|
||||
|
||||
await mcpPage.restoreEmulation();
|
||||
|
||||
sinon.assert.calledOnceWithExactly(pptrPage.setViewport, null);
|
||||
});
|
||||
|
||||
it('re-applies previously configured network and cpu throttling emulation', async () => {
|
||||
const {mcpPage, pptrPage} = await createMcpPage();
|
||||
await mcpPage.emulate({
|
||||
|
||||
@@ -28,8 +28,32 @@ describe('lighthouse', () => {
|
||||
server.addHtmlRoute('/test', html`<div>Test</div>`);
|
||||
|
||||
await withMcpContext(async (response, context, args) => {
|
||||
const page = context.getSelectedMcpPage().pptrPage;
|
||||
const mcpPage = context.getSelectedMcpPage();
|
||||
const page = mcpPage.pptrPage;
|
||||
await page.goto(server.getRoute('/test'));
|
||||
await mcpPage.emulate({
|
||||
viewport: {
|
||||
width: 390,
|
||||
height: 844,
|
||||
deviceScaleFactor: 3,
|
||||
isMobile: true,
|
||||
hasTouch: true,
|
||||
isLandscape: false,
|
||||
},
|
||||
});
|
||||
const readViewport = () =>
|
||||
page.evaluate(() => ({
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
deviceScaleFactor: window.devicePixelRatio,
|
||||
screenWidth: screen.width,
|
||||
orientation: screen.orientation.type,
|
||||
maxTouchPoints: navigator.maxTouchPoints,
|
||||
}));
|
||||
const emulated = await readViewport();
|
||||
assert.equal(emulated.width, 390);
|
||||
assert.equal(emulated.deviceScaleFactor, 3);
|
||||
assert.ok(emulated.maxTouchPoints > 0);
|
||||
|
||||
await lighthouseAudit(args).handler(
|
||||
{
|
||||
@@ -37,12 +61,16 @@ describe('lighthouse', () => {
|
||||
mode: 'navigation',
|
||||
device: 'desktop',
|
||||
},
|
||||
page: context.getSelectedMcpPage(),
|
||||
page: mcpPage,
|
||||
},
|
||||
response,
|
||||
context,
|
||||
);
|
||||
|
||||
// Lighthouse replaces and then clears the device metrics override, the
|
||||
// emulated viewport must be back afterwards.
|
||||
assert.deepEqual(await readViewport(), emulated);
|
||||
|
||||
const data = response.attachedLighthouseResult;
|
||||
assert.ok(data);
|
||||
|
||||
|
||||
Reference in new issue
Block a user