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(usefullPage: truefor 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:
- 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.
- 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_pagewhen done. - Sequential, not parallel, unless cross-viewport simultaneous comparison is the point.
- If a tool errors, surface as a
Data gapsentry; 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
deviceModeloros. 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.