Skip to content

WebView

Test hybrid apps that embed WebViews (login screens, payment flows, in-app browsers, Cordova/Ionic).

  • Android: The app must enable WebView debugging: WebView.setWebContentsDebuggingEnabled(true). React Native WebView provides a webviewDebuggingEnabled prop.
  • iOS: The WKWebView must set isInspectable = true (required since iOS 16.4). React Native WebView provides a webviewDebuggingEnabled prop which sets this automatically. Tapsmith connects directly to the simulator’s WebKit Inspector — no external tools needed.

device.webview(packageName?: string): Promise<WebViewHandle>

Section titled “device.webview(packageName?: string): Promise<WebViewHandle>”

Switch to a WebView context. Discovers available WebViews on the device and connects via CDP (Chrome DevTools Protocol).

const webview = await device.webview()
// or target a specific package when multiple WebViews are present
const webview = await device.webview("com.example.myapp")

Auto-waits for the WebView to appear up to the device timeout. Throws if no WebView is found.

Switch back to native context, closing the active WebView connection.

await device.native()
// Now you can interact with native elements again
await device.getByRole("button", { name: "Continue" }).tap()

Returned by device.webview(). All methods use CSS selectors and auto-wait for elements.

webview.click(selector: string): Promise<void>

Section titled “webview.click(selector: string): Promise<void>”

Click an element in the WebView.

await webview.click("#login-button")
await webview.click(".submit-btn")

webview.fill(selector: string, value: string): Promise<void>

Section titled “webview.fill(selector: string, value: string): Promise<void>”

Fill an input element with text. Dispatches input and change events.

await webview.fill("#email", "user@test.com")
await webview.fill("#password", "secret123")

webview.textContent(selector: string): Promise<string>

Section titled “webview.textContent(selector: string): Promise<string>”

Get the text content of an element.

const heading = await webview.textContent("h1")

webview.innerHTML(selector: string): Promise<string>

Section titled “webview.innerHTML(selector: string): Promise<string>”

Get the inner HTML of an element.

webview.inputValue(selector: string): Promise<string>

Section titled “webview.inputValue(selector: string): Promise<string>”

Get the current value of an input element.

webview.getAttribute(selector: string, name: string): Promise<string | null>

Section titled “webview.getAttribute(selector: string, name: string): Promise<string | null>”

Get an attribute value from an element.

webview.isVisible(selector: string): Promise<boolean>

Section titled “webview.isVisible(selector: string): Promise<boolean>”

Check if an element is visible: it must be rendered (have layout boxes — an ancestor with display: none counts as hidden) and not have display: none, visibility: hidden, or opacity: 0.

webview.evaluate<T>(expression: string): Promise<T>

Section titled “webview.evaluate<T>(expression: string): Promise<T>”

Execute arbitrary JavaScript in the WebView and return the result.

const count = await webview.evaluate<number>("document.querySelectorAll('li').length")
const title = await webview.evaluate<string>("document.title")

Navigate the WebView to a URL.

Get the document title.

Get the current URL.

webview.locator(cssSelector: string): WebViewLocator

Section titled “webview.locator(cssSelector: string): WebViewLocator”

Create a lazy locator for a CSS selector. Returns a WebViewLocator that can be used with expect() assertions.

const loginBtn = webview.locator("#login-button")
await loginBtn.click()
await expect(loginBtn).toBeVisible()

webview.getByText(text, options?) and friends

Section titled “webview.getByText(text, options?) and friends”

Playwright-style locators for DOM elements, mirroring the native device.getBy* API:

MethodMatches
webview.getByText(text, { exact? })Leaf elements by visible text (substring by default)
webview.getByRole(role, { name? })ARIA/HTML role, optionally filtered by accessible name
webview.getByPlaceholder(text)placeholder attribute
webview.getByTestId(testId)data-testid attribute
webview.getByLabel(text)aria-label attribute
await webview.getByRole("button", { name: "Login" }).click()
await webview.getByPlaceholder("Enter your email").fill("user@test.com")
await expect(webview.getByText("Welcome back")).toBeVisible()

Close the WebView connection. Usually called via device.native() instead.

Lazy reference to an element within a WebView, created by webview.locator() or the webview.getBy* methods. Supports actions and assertions.

Actions & queries (strict): click(), fill(value), textContent(), innerHTML(), inputValue(), getAttribute(name), isVisible()

Narrowing & multi-element (strict-mode exempt):

  • .first() / .last() / .nth(index) — narrow to one match positionally (negative nth counts from the end). Returns a new WebViewLocator.
  • .count() — number of elements currently matching (no auto-wait).
  • .all() — one positionally-narrowed locator per current match.

WebView locators are strict: acting or asserting on a locator that resolves to more than one DOM element throws a StrictModeViolationError listing the matches.

const rows = webview.locator("li.result")
await expect(rows.first()).toBeVisible()
console.log(await rows.count())
for (const row of await rows.all()) {
console.log(await row.textContent())
}

expect(webview.locator(selector)) returns WebView-specific assertions:

await expect(webview.locator(".header")).toBeVisible()
await expect(webview.locator(".header")).toHaveText("Welcome")
await expect(webview.locator(".header")).toContainText("Welc")
await expect(webview.locator(".error")).toBeHidden()
await expect(webview.locator("#email")).toExist()
await expect(webview.locator("#email")).toHaveValue("user@test.com")
await expect(webview.locator("a")).toHaveAttribute("href", "/about")

All assertions support .not and a { timeout } option:

await expect(webview.locator(".spinner")).not.toBeVisible()
await expect(webview.locator(".loaded")).toBeVisible({ timeout: 10_000 })

Tapsmith includes a built-in MCP server that lets AI coding agents interact with devices, run tests, and inspect results. It supports two transport modes:

  • SSE mode (via tapsmith test --ui) — agent shares the UI session with full test tree, results, watch mode, and mutual exclusion. 16 tools available.
  • Stdio mode (client-launched tapsmith mcp-server) — standalone agent with its own headless test session, daemon, and device. Includes test discovery, results, watch mode, stop, and session info. 16 tools available.

See the MCP Server Guide for setup instructions, the full tool reference, and the recommended workflow.