Skip to main content
This example shows how to use Playwright’s Android support with Limrun’s sandbox feature. The Chrome DevTools Protocol (CDP) communication happens with very low latency while your test code runs wherever you’d like.

What This Example Does

  1. Creates an Android instance with Playwright sandbox enabled
  2. Enables Chrome command-line flags required by Playwright
  3. Connects Playwright to the remote Android device
  4. Launches Chrome and navigates to the Playwright GitHub repository
  5. Interacts with page elements and takes a screenshot
  6. Cleans up the instance

Prerequisites

Set API Key

export LIM_API_KEY="lim_..."

Install Dependencies

cd examples/playwright
yarn install

Running the Example

yarn run start
The example will navigate through GitHub, click on files in the repository, and save a screenshot to screenshot.png.

Complete Example Code

import { _android as android } from 'playwright';
import { Limrun } from '@limrun/api';

const apiKey = process.env['LIM_API_KEY'];

if (!apiKey) {
  console.error('Error: Missing required environment variables (LIM_API_KEY).');
  process.exit(1);
}

const limrun = new Limrun({ apiKey });

// Create instance with Playwright sandbox enabled
console.time('create');
const instance = await limrun.androidInstances.create({
  metadata: {
    labels: {
      name: 'playwright-example',
    },
  },
  spec: {
    initialAssets: [
      {
        kind: 'Configuration',
        configuration: {
          kind: 'ChromeFlag',
          chromeFlag: 'enable-command-line-on-non-rooted-devices@1',
        },
      },
    ],
    sandbox: {
      playwrightAndroid: {
        enabled: true,
      },
    },
    clues: [
      {
        kind: 'OSVersion',
        osVersion: '15',
      },
    ],
  },
  wait: true,
  reuseIfExists: true,
});
console.timeEnd('create');
console.log(`Instance created: ${instance.metadata.id}`);

if (!instance.status.sandbox?.playwrightAndroid?.url) {
  throw new Error('Playwright Android sandbox URL not found');
}

// Connect Playwright to the instance
console.log(`Connecting to instance: ${instance.metadata.id}`);
console.time('connect');
const device = await android.connect(
  `${instance.status.sandbox.playwrightAndroid.url}?token=${instance.status.token}`,
);
console.timeEnd('connect');

// Initialize Chrome (first-run setup)
await device.shell('am start com.android.chrome/com.google.android.apps.chrome.Main');
await new Promise((resolve) => setTimeout(resolve, 1_000));
await device.shell('am force-stop com.android.chrome');
console.log('Chrome is ready');

// Launch browser and automate
const browser = await device.launchBrowser();
console.log('Browser launched');

console.time('cdp.commands');
const page = await browser.newPage();
await page.goto('https://github.com/microsoft/playwright');
await page.waitForURL('https://github.com/microsoft/playwright');
console.log(await page.title());
console.log('Page title logged');

// Wait for main content to be visible
await page.waitForSelector('[data-hpc]', { state: 'visible' });
const linksCount = await page.locator('a').count();
console.log(`Links on page: ${linksCount}`);

// Navigate through repository files
console.time('click.github');
await page.locator('a[title=".github"]').first().click();
console.timeEnd('click.github');
await page.locator('a[title="workflows"]').first().click();
await page.locator('a[title="infra.yml"]').first().click();

// Scroll and take screenshot
await page.evaluate(() => {
  window.scrollTo(0, document.body.scrollHeight);
});
console.time('cdp.screenshot');
await page.screenshot({ path: 'screenshot.png' });
console.timeEnd('cdp.screenshot');
console.timeEnd('cdp.commands');

// Cleanup
await device.close();
console.log('Session closed');

await limrun.androidInstances.delete(instance.metadata.id);
console.log('Instance deleted');

How It Works

Sandbox Feature

The sandbox.playwrightAndroid.enabled option tells Limrun to set up a Playwright-compatible endpoint inside the Android instance. This enables direct CDP connections with minimal latency.
sandbox: {
  playwrightAndroid: {
    enabled: true,
  },
}

Chrome Flags

Playwright requires specific Chrome flags to be enabled. The example uses a Configuration asset to set the required flag:
initialAssets: [
  {
    kind: 'Configuration',
    configuration: {
      kind: 'ChromeFlag',
      chromeFlag: 'enable-command-line-on-non-rooted-devices@1',
    },
  },
]

Connection URL

The sandbox provides a WebSocket URL that Playwright connects to:
const device = await android.connect(
  `${instance.status.sandbox.playwrightAndroid.url}?token=${instance.status.token}`,
);

Chrome Initialization

Before automation, Chrome needs to complete first-run initialization. The example launches Chrome, waits briefly, then force-stops it:
await device.shell('am start com.android.chrome/com.google.android.apps.chrome.Main');
await new Promise((resolve) => setTimeout(resolve, 1_000));
await device.shell('am force-stop com.android.chrome');

Browser Automation

Once Chrome is ready, use standard Playwright APIs:
  • device.launchBrowser() - Start Chrome
  • browser.newPage() - Create a new tab
  • page.goto() - Navigate to URL
  • page.locator() - Find elements
  • page.screenshot() - Capture screenshots

Alternative: Local ADB Tunnel

You can also connect Playwright locally via ADB tunnel, but this is slower due to chatty CDP traffic:
// Not recommended for performance reasons
const [device] = await android.devices();
console.log(`Model: ${device.model()}`);
console.log(`Serial: ${device.serial()}`);
The sandbox approach keeps CDP traffic local to the instance for better performance.

Performance Benefits

  • Low Latency: CDP communication stays within the instance
  • Fast Execution: No round-trips for every CDP command
  • Scalable: Run many tests in parallel without network bottlenecks

Use Cases

  • Automated web testing on Android Chrome
  • WebView debugging and automation
  • Cross-browser testing workflows
  • Performance testing with real Chrome on Android

Next Steps

Android Instances

Learn about Android instance management

Tunneling

Learn about TCP tunneling and ADB connections

Build docs developers (and LLMs) love