Emulation
Emulate color scheme, geolocation, offline mode, network throttling, viewport, user agent, device scale factor and device presets, with what each backend does natively and what it fakes in JavaScript.
page.emulateMedia(colorScheme = ColorScheme.Dark)
page.setGeolocation(Geolocation(latitude = 48.8584, longitude = 2.2945, accuracy = 10.0))
page.setViewportSize(390, 844)
page.goto("https://example.com/store-locator")
page.setOffline(true)
page.getByRole("button", name = "Refresh").click()
expect(page.getByText("You are offline")).toBeVisible()
// Reset
page.setOffline(false)
page.setGeolocation(null)
page.emulateMedia(colorScheme = null)Emulation is per page. Settings stay in effect across navigations. A tab opened with page.openNewTab() inherits them.
Native or JavaScript
Each backend applies an override natively when it can. When it can't, KMPwright patches the page's JavaScript APIs in every document: it installs a document-start script and also patches the current document. Page scripts can't switch the patch off, but it only changes what JavaScript sees. CSS @media rules, the network stack and native permission prompts are not affected.
| Feature | Android WebView | iOS WKWebView | JVM (CDP) |
|---|---|---|---|
emulateMedia(colorScheme) | JavaScript | Native for Light and Dark when the web view is in a window; JavaScript otherwise and for NoPreference | Native (Emulation.setEmulatedMedia) |
setGeolocation | JavaScript | JavaScript | Native (Emulation.setGeolocationOverride) |
setOffline | JavaScript | JavaScript | Native (Network.emulateNetworkConditions) |
emulateNetworkConditions with latency or throughput | Throws KmpwrightUnsupportedException | Throws KmpwrightUnsupportedException | Native |
setViewportSize | Resizes the WebView's layout (CSS px times display density) | Sets the WKWebView frame | Native (Emulation.setDeviceMetricsOverride) |
setDeviceScaleFactor | Throws KmpwrightUnsupportedException | Throws KmpwrightUnsupportedException | Native |
setUserAgent | WebSettings.userAgentString | customUserAgent | Network.setUserAgentOverride |
emulateDevice | WebView size and user agent (see below) | WKWebView frame and user agent (see below) | Viewport, scale factor, mobile, touch and user agent |
PageCapability.NetworkEmulation is Supported on JVM and Partial on Android and iOS (offline only). The notes from page.capabilities() explain the color scheme and geolocation fallbacks in plain text. Check them at runtime:
page.capabilities().notes.forEach(::println)What the JavaScript fallback patches:
- Color scheme:
window.matchMedia('(prefers-color-scheme: …)')answers with the emulated value, and the root element gets acolor-schemestyle. Stylesheet@media (prefers-color-scheme)rules don't change. On Android with targetSdk 33 or higher, WebView takes that media feature from the app theme. - Geolocation:
navigator.geolocation.getCurrentPositionandwatchPositionreturn the emulated position asynchronously.watchPositionreports once. - Offline:
navigator.onLinereturns false,fetchrejects,XMLHttpRequest.sendthrows,navigator.sendBeaconreturns false, and theoffline/onlineevent fires. Navigations, images, iframes and other requests the browser makes itself are not blocked.
Color scheme
public suspend fun emulateMedia(colorScheme: ColorScheme?)ColorScheme is Light, Dark or NoPreference. null removes the override.
Playwright's page.emulateMedia also takes media, reducedMotion and forcedColors. KMPwright only takes colorScheme.
On iOS, KMPwright sets overrideUserInterfaceStyle and then waits up to a short timeout for matchMedia to confirm the change. If it isn't confirmed (for example, the web view isn't in a window), it undoes the native override and uses the JavaScript fallback.
Geolocation
public suspend fun setGeolocation(geolocation: Geolocation?)
public data class Geolocation(val latitude: Double, val longitude: Double, val accuracy: Double = 0.0)- Latitude must be in -90..90 and longitude in -180..180. Accuracy must be finite and at least 0. Otherwise it throws
IllegalArgumentException. nullremoves the override.- JVM: KMPwright also grants the
geolocationpermission to the page's browser context, like Playwright'spermissions: ["geolocation"]. The permission stays granted after you reset. - Android and iOS: only the JavaScript API is patched. Native location permission prompts are not affected.
Offline and network conditions
page.setOffline(true)
page.emulateNetworkConditions(NetworkConditions.Slow3G) // JVM only
page.emulateNetworkConditions(NetworkConditions(latencyMs = 400)) // JVM only
page.emulateNetworkConditions(NetworkConditions.Offline) // all backends
page.emulateNetworkConditions(null) // remove throttlingProp
Type
| Preset | Latency | Download | Upload |
|---|---|---|---|
NetworkConditions.Slow3G | 100 ms | 750 kbit/s (93,750 B/s) | 250 kbit/s (31,250 B/s) |
NetworkConditions.Fast3G | 56 ms | 1.6 Mbit/s (200,000 B/s) | 750 kbit/s (93,750 B/s) |
NetworkConditions.Offline | offline |
- Negative values throw
IllegalArgumentException. - On Android and iOS, any non-zero latency or throughput throws
KmpwrightUnsupportedException. Onlyofflineis emulated there. setOffline(x)andemulateNetworkConditions(...)set the same state. The last call wins.- Playwright has
context.setOffline()but no throttling API. KMPwright'semulateNetworkConditionsmatches CDP'sNetwork.emulateNetworkConditions.
Viewport and device scale factor
page.setViewportSize(1280, 800) // CSS pixels
val size: ViewportSize? = page.viewportSize()
page.setDeviceScaleFactor(2.0) // JVM onlysetViewportSize(width, height)requires positive values.viewportSize()is notsuspend. It returns the current size in CSS pixels. Android converts device pixels with the display density. iOS reports the web view's bounds. JVM reports the emulated size or, without an override, the last size it observed.setDeviceScaleFactor(scale)requires a finite positive value. Android and iOS throwKmpwrightUnsupportedException, because the host display sets the scale there.- On Android and iOS, the WebView is a native view. If it's laid out inside your UI, your layout may resize it again.
KmpwrightConfig.viewport and KmpwrightConfig.userAgent set the initial size and user agent for WebViews that KMPwright creates on Android and iOS. The iOS default frame is 1280×800. The JVM backend doesn't read these fields, so call setViewportSize/setUserAgent on the page there.
User agent
page.setUserAgent("Mozilla/5.0 (Linux; Android 14) MyTestAgent/1.0")The user agent applies to requests made after the call.
Device presets
page.emulateDevice(Devices.iPhone15) // JVM: viewport, scale, mobile, touch + UA. WebViews: size + UA
val pixel = Devices.all.getValue("Pixel 7")emulateDevice(device) first validates the descriptor (positive width/height, a finite positive deviceScaleFactor, a non-blank userAgent) and throws IllegalArgumentException without changing anything if it is invalid. Then it applies the viewport and then device.userAgent. Pages the page opens later (popups) inherit the device.
| Backend | Viewport step |
|---|---|
| JVM (CDP) | Playwright-style: one Emulation.setDeviceMetricsOverride with width, height, deviceScaleFactor and mobile = isMobile (plus a portrait or landscape screenOrientation from isLandscape when isMobile), then touch emulation with 5 touch points when hasTouch, or disabled otherwise. |
| Android, iOS | The WebView size only, as setViewportSize(width, height). The scale factor, mobile layout and touch support come from the device and are not emulated (listed in capabilities().notes). It does not throw for the scale factor. |
| Property | Name (Devices.all key) | Viewport | Scale |
|---|---|---|---|
Devices.iPhone15 | iPhone 15 | 393×852 | 3.0 |
Devices.iPhone15ProMax | iPhone 15 Pro Max | 430×932 | 3.0 |
Devices.iPhone14 | iPhone 14 | 390×844 | 3.0 |
Devices.iPhoneSE | iPhone SE | 375×667 | 2.0 |
Devices.iPadAir | iPad Air | 820×1180 | 2.0 |
Devices.iPadPro11 | iPad Pro 11" | 834×1194 | 2.0 |
Devices.iPadPro12_9 | iPad Pro 12.9" | 1024×1366 | 2.0 |
Devices.Pixel7 | Pixel 7 | 412×915 | 2.625 |
Devices.Pixel7Pro | Pixel 7 Pro | 412×915 | 2.625 |
Devices.GalaxyS23 | Galaxy S23 | 360×780 | 3.0 |
Devices.MacBook14 | MacBook Pro 14" | 1512×982 | 2.0 |
Devices.DesktopHD | Desktop 1920x1080 | 1920×1080 | 1.0 |
Devices.DesktopLaptop | Desktop 1366x768 | 1366×768 | 1.0 |
Phones and tablets are portrait with isMobile = true and hasTouch = true. iOS presets use Safari user agents; Android and desktop presets use Chrome 116 user agents.
To add your own device, build a DeviceDescriptor(name, viewport = Viewport(width, height, deviceScaleFactor, isMobile, hasTouch, isLandscape), userAgent). Viewport also has DEFAULT (1280×800), MOBILE (390×844, touch) and TABLET (768×1024, touch) presets.
Playwright's devices["iPhone 15"] is a context option. KMPwright applies the preset to a page that already exists.
Policies
Every emulation setter is checked against ActionType.Emulation in the page's action policy. viewportSize() is not checked.
Related
- Capabilities:
NetworkEmulationand backend notes - Network: routing and the domain allowlist
- Page reference
Dialogs and frames
Answer alert, confirm, prompt and beforeunload dialogs with onDialog, wait for dialogs without races, and locate elements inside iframes with frameLocator.
Accessibility audits
Run the bundled axe-core 4.14.0 against the current page with accessibilityAudit and read violations, incomplete checks and frame coverage.