Skill: Conversion Page Inspection (via Browser Run)

How the Page/UX agent uses the mcp__browser-run__* tools effectively for conversion-diagnosis on the new web. Complements PostHog replay analysis — replays show real users; browser inspection shows the rendered page.

Tool inventory

mcp__browser-run__* tools, by category:

  • Navigation: new_page, navigate_page, list_pages, close_page
  • Emulation: emulate (viewport + UA + network throttling + color scheme + geolocation), resize_page
  • Capture: take_screenshot (use fullPage: true for UX review), take_snapshot (DOM)
  • Inspection: list_console_messages, list_network_requests, get_console_message, get_network_request
  • Interaction: click, hover, fill, fill_form, type_text, press_key, select_option
  • Performance: lighthouse_audit, performance_start_trace, performance_stop_trace, performance_analyze_insight
  • JS: evaluate_script (use sparingly; prefer DOM snapshot)

Cached device snapshot

Last refreshed: 2026-05-09. Source: PostHog funnel breakdown, 30d window, ~4633 funnel page-view users.

OS share: Android 49.5% · iOS 44.0% · Windows 3.3% · macOS 2.5% · Linux <1%. Browser share: Chrome 50.9% · Mobile Safari 38.9% · Facebook Mobile (in-app) 3.8% · Safari (desktop) 1.7% · Samsung Internet 1.3% · Chrome iOS 1.0% · Firefox / Edge / Firefox iOS / Opera <1% each. Device model share: Android phone 45.3% · iPhone 42.8% · Android Tablet 4.2% · iPad 1.2% · (unknown) 6.5%.

Two implications:

  1. iOS and Android are roughly 50/50. Any UX work that only tests iPhone misses half the audience. Both go in the standard profile set.
  2. Facebook in-app browser is real. PostHog detects it as $browser = "Facebook Mobile". ~3.8% of funnel traffic. Always emulate it for Meta-attributed cohorts.

Refresh cadence: at least quarterly, or when a diagnosis run hints the device mix has shifted. Refresh command: get_posthog_funnel_summary with breakdown=os / browser / deviceModel, 30d.

Caveat: Instagram in-app is invisible in $browser. Instagram’s WebView identifies itself as Mobile Safari (iOS) or Chrome (Android), so it doesn’t surface as a distinct browser. But IG traffic exists (visible in source=ig breakdown) and the WebView has its own render quirks. Always emulate Instagram in-app explicitly for Meta cohorts even though you can’t size it from $browser.

Standard emulation profiles

Always test these for any cohort. Use the cached snapshot to weight emphasis (more iOS for iPhone-heavy cohorts, etc.).

Profile Viewport User-agent
iOS Safari (iPhone 14) 390x844x3,mobile,touch Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1
Android Chrome (Pixel-class) 412x915x2.625,mobile,touch Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36
Facebook in-app (iOS) 390x844x3,mobile,touch Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 [FBAN/FBIOS;FBAV/450.0.0.0;FBBV/0;FBDV/iPhone14,3;FBMD/iPhone;FBSN/iOS;FBSV/17.0;FBSS/3;FBID/phone;FBLC/en_US;FBOP/5]
Instagram in-app (iOS) 390x844x3,mobile,touch Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Instagram 295.0.0.21.110 (iPhone14,3; iOS 17_0; en_US; en-US; scale=3.00; 1170x2532; 484060358)
Desktop Chrome (1440 wide) 1440x900x2 (no override)

Tablet (~5%) and Samsung Internet (1.3%) are not in the standard set — add per-cohort if a question warrants.

Workflow

1. new_page about:blank
2. emulate <profile>
3. navigate_page url=<target>?af_internal=1   # opts the browser out of analytics for the session
4. take_screenshot fullPage=true
5. take_snapshot
6. (optional) list_console_messages, list_network_requests
7. close_page

Order matters: emulating before navigation means initial paint at the target viewport. Navigating first then emulating misses media-query branches that fire only on initial render.

Always append ?af_internal=1 to the first navigation on production URLs — it sets a localStorage flag that opts the browser out of every analytics fire (PostHog capture + autocapture, dataLayer pushes, third-party trackers). Without it, agent-driven inspections pollute live PostHog with bot-shaped sessions. The flag persists across navigations within the page lifetime; you don’t need to re-append it on subsequent navigate_page calls in the same session. To clear the flag, use ?af_internal=clear. See knowledge/product.md for the full opt-out spec.

If the page was loaded at a different viewport before emulation, navigate_page type=reload.

Pair every screenshot with a snapshot — screenshot proves the visual; snapshot proves the DOM.

Tool hygiene

  • Always close_page when done.
  • Sequential, not parallel, unless cross-viewport simultaneous comparison is the point.
  • If a tool errors, surface as a Data gaps entry; don’t retry blindly.

Cross-referencing replays with browser inspection

The Page/UX agent should not analyze replays in isolation or inspect the page in isolation. The two are complementary: replays tell you what happened in what context; browser inspection lets you recreate that context and verify the page. Drive one with the other.

Use the structured Environment block from each replay-analyzer return (viewport, browser, os, current_url) directly as input to emulate + navigate_page. Same conditions, same rendering — no guessing.

Patterns:

  • Replay observation → reproduction. A replay-analyzer flags a friction in a specific environment. Extract the env block, emulate exactly, navigate, screenshot. Verify whether the page is actually problematic for that condition or whether the user simply left.
  • Friction localization. Replay shows the user idled 30s in section X. Browser-run navigates the same page, scrolls to section X, takes a DOM snapshot. What’s on screen at the moment of hesitation often surfaces something the user couldn’t parse.
  • WebView reality check. Aggregate replay finding (e.g. “FB-mobile in-app exits 2× higher at product page”). Emulate the FB in-app UA + viewport, compare DOM/visual to standard Mobile Safari. Differences = WebView quirks worth fixing.
  • Cohort-dimensioned page check. Funnel breakdown surfaces an anomalous drop at a specific deviceModel or os. Emulate that exact profile, navigate to the leaking step, see if the layout is broken.

When not to bother cross-referencing: friction that’s clearly client-side behaviour with no rendering component (a form field the user didn’t fill, a hesitation with no visible page change). Browser inspection adds little there; just describe the behaviour from the replay.

Diagnostic recipes

“Does the page deliver the ad’s promise above the fold?”

iOS Safari profile → navigate → screenshot viewport only (not fullPage; that’s what the visitor sees first) → check hero image, headline, primary CTA, value-prop one-liner against the dominant creative’s promise.

“Is the page broken in Facebook / Instagram in-app browser?”

iOS Safari → screenshot + snapshot (baseline). Same viewport with FB in-app UA → compare. Repeat with IG in-app UA. Differences = WebView quirks. Critical for Meta-attributed cohorts.

“How many viewports of scroll to the primary CTA?”

iOS Safari → navigate → evaluate_script:

const cta = document.querySelector('[data-cta-primary], button.primary, .hero-cta');
const ctaTop = cta.getBoundingClientRect().top + window.scrollY;
return ctaTop / window.innerHeight;

2 viewports = above-the-fold violation per conversion-patterns/SKILL.md.

“Does body text read as too thin on mobile?”

iOS Safari → product page → evaluate_script to read computed font-weight and font-size on body-text selectors. Regular weight (400) below 16px is the known regression.

“Is the page slow on mobile?”

iOS Safari + networkConditions: "Slow 3G" (or "Fast 4G" for realistic mobile sim) → lighthouse_audit. Surface First Contentful Paint, Largest Contentful Paint, Total Blocking Time.

“Does the German copy read as native, or machine-translated?”

Run on every page inspection. From the DOM snapshot, focus on visible body text + headlines + CTAs. Flag:

  • Machine-translation tells — literal English word order, calques (word-for-word translations of English idioms that don’t exist in German)
  • Register inconsistency — mixing “Sie” and “du” without editorial intent (premium-skewing DTC food typically picks one and holds it)
  • Anglicisms with native German equivalents available
  • Awkward compound nouns — German has rules; arbitrary stitching reads as foreign
  • CTA copy translated literally instead of using natural German imperative (“Jetzt bestellen” not “Order now translated”)

Translation quality is a hypothesis filter, not a hard fail. See the localization note in conversion-patterns/SKILL.md for the converse pattern.

The same check applies to any future language target (FR, IT, etc.) — adapt the heuristics to that language.

What this skill does not do

  • It does not replace PostHog replay analysis. Page-as-rendered vs. real-user-behaviour are different signals; both needed.
  • It does not produce hypotheses on its own. Observations only — the Page/UX agent forms hypotheses by combining browser observations with replay patterns and conversion-patterns/SKILL.md.
  • It does not interact with checkout / payment in production. Use staging or a non-production variant for any flow that triggers a real order.

This site uses Just the Docs, a documentation theme for Jekyll.