Advanced Chapter 10 · 10 min read

Deep Links & Web Views

Jump straight to any screen with deep links, and drive embedded web views with the full Playwright API.

Deep Links: Skip the Tapping

A deep link opens a specific screen of your app directly via a URL. In tests this is gold: instead of tapping through five screens to reach the one you want to test, you jump straight there with device.openUrl(). Each test becomes faster and more focused:

deeplink.test.ts
import { test, expect } from '@mobilewright/test';

test.describe('order history', () => {
  test.beforeEach(async ({ device }) => {
    // Jump straight to the screen under test — no tapping through menus
    await device.openUrl('myapp://account/orders');
  });

  test('shows past orders', async ({ screen }) => {
    await expect(screen.getByText('Your Orders')).toBeVisible();
  });

  test('empty state shown when no orders', async ({ screen }) => {
    await expect(screen.getByTestId('empty-orders')).toBeVisible();
  });
});
deeplink.test.js
const { test, expect } = require('@mobilewright/test');

test.describe('order history', () => {
  test.beforeEach(async ({ device }) => {
    // Jump straight to the screen under test — no tapping through menus
    await device.openUrl('myapp://account/orders');
  });

  test('shows past orders', async ({ screen }) => {
    await expect(screen.getByText('Your Orders')).toBeVisible();
  });

  test('empty state shown when no orders', async ({ screen }) => {
    await expect(screen.getByTestId('empty-orders')).toBeVisible();
  });
});

Want to verify your setup works before wiring up your own scheme? The tel: scheme exists on every device:

dialer.test.ts
import { test, expect } from '@mobilewright/test';

test('tel: scheme opens the phone dialer', async ({ device, screen }) => {
  await device.openUrl('tel:+15550001234');
  await expect(screen.getByText('Call')).toBeVisible();
});
dialer.test.js
const { test, expect } = require('@mobilewright/test');

test('tel: scheme opens the phone dialer', async ({ device, screen }) => {
  await device.openUrl('tel:+15550001234');
  await expect(screen.getByText('Call')).toBeVisible();
});

Passing Parameters

Deep links support path segments and query parameters — whatever your app's routing understands:

params.test.ts
import { test } from '@mobilewright/test';

test('deep link parameters', async ({ device }) => {
  // Path parameter
  await device.openUrl('myapp://users/99');

  // Query parameters
  await device.openUrl('myapp://search?q=headphones&sort=price');

  // Combined
  await device.openUrl('myapp://products/42?variant=blue&size=M');
});
params.test.js
const { test } = require('@mobilewright/test');

test('deep link parameters', async ({ device }) => {
  // Path parameter
  await device.openUrl('myapp://users/99');

  // Query parameters
  await device.openUrl('myapp://search?q=headphones&sort=price');

  // Combined
  await device.openUrl('myapp://products/42?variant=blue&size=M');
});
Tip: Deep links are the easiest way to skip login in tests: navigate straight to the screen under test in a beforeEach, and each test stays short and independent.

Web Views: The Full Playwright API

Many apps embed web content in a native web view (WKWebView on iOS, System WebView on Android). Mobilewright lets you drive that content with the real Playwright API — the same locators, actions, and web-first assertions. Under the hood it injects Playwright's own engine into the web view, so the Page and Locator objects you get back behave exactly like Playwright's.

webview.test.ts
import { test, expect } from '@mobilewright/test';

test('drive the in-app web view', async ({ device, screen }) => {
  await device.launchApp('com.example.app');

  // Navigate to the screen that hosts the web view
  await screen.getByText('Web View').tap();

  // Attach and get a Playwright-style Page
  const page = await screen.getByWebView().page();

  await page.goto('https://example.com/login');
  await page.getByPlaceholder('Email').fill('user@example.com');
  await page.getByRole('button', { name: 'Sign in' }).click();

  await expect(page).toHaveURL(/\/dashboard/);
  await expect(page.getByRole('heading')).toHaveText('Welcome');
});
webview.test.js
const { test, expect } = require('@mobilewright/test');

test('drive the in-app web view', async ({ device, screen }) => {
  await device.launchApp('com.example.app');

  // Navigate to the screen that hosts the web view
  await screen.getByText('Web View').tap();

  // Attach and get a Playwright-style Page
  const page = await screen.getByWebView().page();

  await page.goto('https://example.com/login');
  await page.getByPlaceholder('Email').fill('user@example.com');
  await page.getByRole('button', { name: 'Sign in' }).click();

  await expect(page).toHaveURL(/\/dashboard/);
  await expect(page.getByRole('heading')).toHaveText('Welcome');
});

One important requirement: the app must be a debug build. On Android that means android:debuggable="true"; on iOS the app needs the get-task-allow entitlement (any development or Simulator build). Release builds cannot be attached to, and getByWebView() will not find a web view in them.

Note: A few Playwright features have no equivalent inside an embedded web view and will throw if called: network interception (page.route), page.screenshot, dialogs/downloads, and multiple tabs.

Mobilewright Advanced Deep Links WebViews

Written by PV

© 2026 All Rights Reserved