Skip to content

emulate is not atomic: mobile viewport + network throttle in one call throws a navigation timeout and desyncs tracked state #2115

@decisive-engineering

Description

@decisive-engineering

Environment

  • chrome-devtools-mcp 1.0.1 (via npx -y chrome-devtools-mcp@latest)
  • Connected to a running Chrome via --browser-url=http://127.0.0.1:9222
  • Windows 11, Chrome stable

Summary

A single emulate call that sets both a mobile viewport and a network throttle (e.g. Slow 3G) reliably throws Navigation timeout of 10000ms exceeded. Because the handler throws partway through, the page's tracked emulationSettings is never updated and the per-page timeouts are never refreshed — leaving the tool's tracked emulation state out of sync with the actual CDP overrides on the page.

Repro

  1. Navigate to any page.
  2. Call emulate in one shot:
    {
      "viewport": "390x844x3,mobile,touch",
      "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1",
      "networkConditions": "Slow 3G"
    }
  3. Error: Navigation timeout of 10000ms exceeded.

A subsequent emulate call that omits userAgent (intending to clear it) then leaves a phantom mobile (Nexus 5) UA, because mobile device-metrics remained active from the partially-applied previous call.

Likely cause

In src/McpContext.js, emulate() (≈L194–271) applies settings in this order:

  1. network throttle (≈L213)
  2. … cpu / geolocation / userAgent / colorScheme …
  3. setViewport(...) (≈L264) — when isMobile/hasTouch change, Puppeteer applies this via a page reload
  4. mcpPage.emulationSettings = … (≈L267)
  5. #updateSelectedPageTimeouts() (≈L270), which is what finally calls setDefaultNavigationTimeout(NAVIGATION_TIMEOUT * networkMultiplier) (≈L338; NAVIGATION_TIMEOUT = 10_000 at L20)

So the reload triggered by step 3 runs while the network is already throttled (step 1) but the navigation-timeout multiplier hasn't been applied yet (step 5) — the reload is bounded by the default 10 s and times out under Slow 3G. The throw at step 3 means steps 4–5 never run, so the tracked state desyncs from the live page.

Suggested fix

Apply the network-condition navigation-timeout multiplier before setViewport() (e.g. set the navigation timeout right after applying networkConditions, or reorder so timeouts update before any operation that can trigger a navigation). Optionally make emulate transactional (update emulationSettings incrementally, or wrap in try/finally) so a mid-operation failure doesn't leave tracked state inconsistent.

Workaround

Split into two calls: set networkConditions in one emulate, then change viewport in a second (either order works).

Possibly related

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No fields configured for Bug.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions