CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-playwright

A high-level API to automate web browsers and comprehensive framework for web testing

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

mobile-device.mddocs/

Mobile & Device Testing

Mobile browser emulation, device-specific testing, Android automation, and Electron app automation for comprehensive cross-platform testing.

Capabilities

Device Emulation

Pre-configured device descriptors for mobile and desktop browser testing.

/**
 * Collection of device descriptors for browser emulation
 */
const devices: Devices;

interface Devices {
  /** Device descriptors indexed by name */
  [key: string]: DeviceDescriptor;
}

interface DeviceDescriptor {
  /** User agent string */
  userAgent: string;
  /** Screen dimensions */
  viewport: ViewportSize;
  /** Device pixel ratio */
  deviceScaleFactor: number;
  /** Mobile device flag */
  isMobile: boolean;
  /** Touch support flag */
  hasTouch: boolean;
  /** Default browser type */
  defaultBrowserType?: 'chromium' | 'firefox' | 'webkit';
}

interface ViewportSize {
  width: number;
  height: number;
}

Usage Examples:

import { devices, chromium } from 'playwright';

// Launch with device emulation
const browser = await chromium.launch();
const context = await browser.newContext({
  ...devices['iPhone 12'],
});
const page = await context.newPage();

// Common device presets
const iphone = devices['iPhone 12'];
const ipad = devices['iPad Pro'];
const pixel = devices['Pixel 5'];
const galaxy = devices['Galaxy S9+'];
const desktopChrome = devices['Desktop Chrome'];
const desktopFirefox = devices['Desktop Firefox'];

console.log('iPhone 12 viewport:', iphone.viewport);
console.log('Is mobile:', iphone.isMobile);
console.log('Has touch:', iphone.hasTouch);

// Custom device configuration
const customDevice = {
  userAgent: 'Custom Device User Agent',
  viewport: { width: 375, height: 812 },
  deviceScaleFactor: 3,
  isMobile: true,
  hasTouch: true
};

const customContext = await browser.newContext(customDevice);

Android Device Automation

Connect to and automate Android devices and emulators.

/**
 * Android automation interface
 */
const _android: Android;

interface Android {
  /** List connected Android devices */
  devices(): Promise<AndroidDevice[]>;
  /** Set default timeout */
  setDefaultTimeout(timeout: number): void;
  /** Connect to device via ADB */
  connect(wsEndpoint: string, options?: AndroidConnectOptions): Promise<AndroidDevice>;
}

interface AndroidDevice {
  /** Device model */
  model(): string;
  /** Device serial number */
  serial(): string;
  /** Launch application */
  launchBrowser(options?: AndroidLaunchOptions): Promise<BrowserContext>;
  /** Close device connection */
  close(): Promise<void>;
  /** Install APK */
  installApk(apkPath: string): Promise<void>;
  /** Get device shell */
  shell(command: string): Promise<Buffer>;
  /** Push file to device */
  push(local: string, remote: string): Promise<void>;
  /** Pull file from device */  
  pull(remote: string, local: string): Promise<void>;
  /** Take screenshot */
  screenshot(): Promise<Buffer>;
  /** Get web views */
  webViews(): Promise<AndroidWebView[]>;
  /** Get web view by selector */
  webView(selector: AndroidSelector): Promise<AndroidWebView>;
  /** Tap coordinates */
  tap(selector: AndroidSelector): Promise<void>;
  /** Fill text */
  fill(selector: AndroidSelector, text: string): Promise<void>;
  /** Press key */
  press(key: AndroidKey): Promise<void>;
  /** Scroll */
  scroll(selector: AndroidSelector, direction: 'down' | 'up' | 'left' | 'right', percent: number): Promise<void>;
  /** Wait for element */
  wait(selector: AndroidSelector, options?: AndroidWaitOptions): Promise<AndroidElementInfo>;
  /** Get element info */
  info(selector: AndroidSelector): Promise<AndroidElementInfo>;
  /** Input interface */
  input: AndroidInput;
}

interface AndroidLaunchOptions {
  /** Browser package name */
  pkg?: string;
  /** Browser activity */  
  activity?: string;
  /** Wait for network idle */
  waitForNetworkIdle?: boolean;
}

interface AndroidConnectOptions {
  /** Connection timeout */
  timeout?: number;
}

interface AndroidWaitOptions {
  /** Wait timeout */
  timeout?: number;
  /** Element state to wait for */
  state?: 'gone' | 'present';
}

Usage Examples:

import { _android as android } from 'playwright';

// Connect to Android device
const devices = await android.devices();
const device = devices[0];

console.log('Device model:', device.model());
console.log('Serial:', device.serial());

// Launch browser on device  
const context = await device.launchBrowser();
const page = await context.newPage();
await page.goto('https://example.com');

// Native Android automation
await device.tap({ text: 'Settings' });
await device.fill({ resource: 'com.android.settings:id/search' }, 'wifi');
await device.press('KEYCODE_ENTER');

// Take screenshot
const screenshot = await device.screenshot();
require('fs').writeFileSync('android-screenshot.png', screenshot);

// Work with WebViews
const webViews = await device.webViews();
if (webViews.length > 0) {
  const webView = webViews[0];
  const page = await webView.page();
  await page.click('button');
}

await device.close();

Android Input & Interaction

Handle Android-specific input methods and UI interactions.

interface AndroidInput {
  /** Tap coordinates */
  tap(point: { x: number; y: number }): Promise<void>;
  /** Swipe gesture */
  swipe(from: { x: number; y: number }, to: { x: number; y: number }, steps: number): Promise<void>;
  /** Drag gesture */
  drag(from: { x: number; y: number }, to: { x: number; y: number }, steps: number): Promise<void>;
  /** Press key */
  press(key: AndroidKey): Promise<void>;
  /** Type text */
  type(text: string): Promise<void>;
}

interface AndroidWebView {
  /** Get package name */
  pkg(): string;
  /** Get page instance */
  page(): Promise<Page>;
}

interface AndroidSocket {
  /** Write data to socket */
  write(data: Buffer | string): Promise<void>;
  /** Close socket */
  close(): Promise<void>;
}

type AndroidElementInfo = {
  /** Element bounds */
  bounds: { x: number; y: number; width: number; height: number };
  /** Element class */
  clazz: string;
  /** Element description */
  desc: string;
  /** Package name */
  pkg: string;
  /** Resource ID */
  res: string;
  /** Element text */
  text: string;
  /** Element checkable */
  checkable: boolean;
  /** Element checked */
  checked: boolean;
  /** Element clickable */
  clickable: boolean;
  /** Element enabled */
  enabled: boolean;
  /** Element focusable */
  focusable: boolean;
  /** Element focused */
  focused: boolean;
  /** Element long clickable */
  longClickable: boolean;
  /** Element scrollable */
  scrollable: boolean;
  /** Element selected */
  selected: boolean;
};

type AndroidSelector = {
  /** Select by checkable state */
  checkable?: boolean;
  /** Select by checked state */
  checked?: boolean;
  /** Select by class name */
  clazz?: string | RegExp;
  /** Select by clickable state */
  clickable?: boolean;
  /** Select by depth */
  depth?: number;
  /** Select by description */
  desc?: string | RegExp;
  /** Select by enabled state */
  enabled?: boolean;
  /** Select by focusable state */
  focusable?: boolean;
  /** Select by focused state */
  focused?: boolean;
  /** Select by long clickable state */
  longClickable?: boolean;
  /** Select by package name */
  pkg?: string | RegExp;
  /** Select by resource ID */
  res?: string | RegExp;
  /** Select by scrollable state */
  scrollable?: boolean;
  /** Select by selected state */
  selected?: boolean;
  /** Select by text content */
  text?: string | RegExp;
};

type AndroidKey = 
  | 'KEYCODE_UNKNOWN' | 'KEYCODE_SOFT_LEFT' | 'KEYCODE_SOFT_RIGHT' | 'KEYCODE_HOME'
  | 'KEYCODE_BACK' | 'KEYCODE_CALL' | 'KEYCODE_ENDCALL' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
  | 'KEYCODE_STAR' | 'KEYCODE_POUND' | 'KEYCODE_DPAD_UP' | 'KEYCODE_DPAD_DOWN' | 'KEYCODE_DPAD_LEFT' | 'KEYCODE_DPAD_RIGHT'
  | 'KEYCODE_DPAD_CENTER' | 'KEYCODE_VOLUME_UP' | 'KEYCODE_VOLUME_DOWN' | 'KEYCODE_POWER' | 'KEYCODE_CAMERA'
  | 'KEYCODE_CLEAR' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z'
  | 'KEYCODE_COMMA' | 'KEYCODE_PERIOD' | 'KEYCODE_ALT_LEFT' | 'KEYCODE_ALT_RIGHT' | 'KEYCODE_SHIFT_LEFT' | 'KEYCODE_SHIFT_RIGHT'
  | 'KEYCODE_TAB' | 'KEYCODE_SPACE' | 'KEYCODE_SYM' | 'KEYCODE_EXPLORER' | 'KEYCODE_ENVELOPE' | 'KEYCODE_ENTER'
  | 'KEYCODE_DEL' | 'KEYCODE_GRAVE' | 'KEYCODE_MINUS' | 'KEYCODE_EQUALS' | 'KEYCODE_LEFT_BRACKET' | 'KEYCODE_RIGHT_BRACKET'
  | 'KEYCODE_BACKSLASH' | 'KEYCODE_SEMICOLON' | 'KEYCODE_APOSTROPHE' | 'KEYCODE_SLASH' | 'KEYCODE_AT' | 'KEYCODE_NUM'
  | 'KEYCODE_HEADSETHOOK' | 'KEYCODE_FOCUS' | 'KEYCODE_PLUS' | 'KEYCODE_MENU' | 'KEYCODE_NOTIFICATION' | 'KEYCODE_SEARCH'
  | 'KEYCODE_MEDIA_PLAY_PAUSE' | 'KEYCODE_MEDIA_STOP' | 'KEYCODE_MEDIA_NEXT' | 'KEYCODE_MEDIA_PREVIOUS' | 'KEYCODE_MEDIA_REWIND'
  | 'KEYCODE_MEDIA_FAST_FORWARD' | 'KEYCODE_MUTE';

Usage Examples:

// Android input interactions
await device.input.tap({ x: 100, y: 200 });
await device.input.swipe(
  { x: 100, y: 500 }, 
  { x: 100, y: 200 }, 
  10
);

// Press hardware keys
await device.input.press('KEYCODE_HOME');
await device.input.press('KEYCODE_BACK');
await device.input.press('KEYCODE_VOLUME_UP');

// Type text
await device.input.type('Hello Android');

// Complex selector
const element = await device.wait({
  pkg: /com\.android\.settings/,
  text: /Wi.*Fi/,
  clickable: true
});

console.log('Found element:', element.text);
console.log('Element bounds:', element.bounds);

// WebView automation
const webViews = await device.webViews();
for (const webView of webViews) {
  console.log('WebView package:', webView.pkg());
  const page = await webView.page();
  const title = await page.title();
  console.log('WebView title:', title);
}

Electron Application Automation

Automate Electron desktop applications.

/**
 * Electron automation interface
 */
const _electron: Electron;

interface Electron {
  /** Launch Electron application */
  launch(options: ElectronLaunchOptions): Promise<ElectronApplication>;
}

interface ElectronApplication {
  /** Get BrowserWindow instance */
  browserWindow(page: Page): Promise<JSHandle>;
  /** Close application */
  close(): Promise<void>;
  /** Get application context */
  context(): BrowserContext;
  /** Evaluate in main process */
  evaluate<R, Arg>(pageFunction: PageFunction<Arg, R>, arg: Arg): Promise<R>;
  evaluate<R>(pageFunction: PageFunction<void, R>, arg?: any): Promise<R>;
  /** First window */
  firstWindow(options?: ElectronApplicationFirstWindowOptions): Promise<Page>;
  /** Get all windows */
  windows(): Page[];
  /** Wait for event */
  waitForEvent(event: string, optionsOrPredicate?: WaitForEventOptions): Promise<any>;
  /** Get process */
  process(): ChildProcess;
}

interface ElectronLaunchOptions {
  /** Path to Electron app */
  executablePath?: string;
  /** Application arguments */
  args?: string[];
  /** Working directory */
  cwd?: string;
  /** Environment variables */
  env?: { [key: string]: string | number | boolean };
  /** Connection timeout */
  timeout?: number;
  /** Accept downloads */
  acceptDownloads?: boolean;
  /** Bypass CSP */
  bypassCSP?: boolean;
  /** Color scheme */
  colorScheme?: 'light' | 'dark' | 'no-preference';
  /** Extra HTTP headers */
  extraHTTPHeaders?: { [key: string]: string };
  /** Geolocation */
  geolocation?: Geolocation;
  /** HTTP credentials */
  httpCredentials?: HTTPCredentials;
  /** Ignore HTTPS errors */
  ignoreHTTPSErrors?: boolean;
  /** Locale */
  locale?: string;
  /** Offline mode */
  offline?: boolean;
  /** Permissions */
  permissions?: string[];
  /** Proxy */
  proxy?: ProxySettings;
  /** Record HAR */
  recordHar?: { omitContent?: boolean; path: string };
  /** Record video */
  recordVideo?: { dir: string; size?: ViewportSize };
  /** Reduced motion */
  reducedMotion?: 'reduce' | 'no-preference';
  /** Service workers */
  serviceWorkers?: 'allow' | 'block';
  /** Storage state */
  storageState?: string | { cookies: Cookie[]; origins: any[] };
  /** Strict selectors */
  strictSelectors?: boolean;
  /** Timezone */
  timezoneId?: string;
  /** User agent */
  userAgent?: string;
  /** Viewport */
  viewport?: ViewportSize | null;
}

interface ElectronApplicationFirstWindowOptions {
  /** Wait timeout */
  timeout?: number;
}

Usage Examples:

import { _electron as electron } from 'playwright';

// Launch Electron app
const electronApp = await electron.launch({ 
  args: ['./my-electron-app'] 
});

// Get first window
const window = await electronApp.firstWindow();
await window.waitForLoadState();

// Interact with main process
const version = await electronApp.evaluate(async ({ app }) => {
  return app.getVersion();
});

console.log('App version:', version);

// Interact with renderer process (window)
await window.click('#menu-file');
await window.click('#menu-open');

// Get BrowserWindow handle
const browserWindow = await electronApp.browserWindow(window);
const isMaximized = await browserWindow.evaluate(win => win.isMaximized());

console.log('Window maximized:', isMaximized);

// Handle multiple windows
const allWindows = electronApp.windows();
console.log('Window count:', allWindows.length);

// Wait for new window
electronApp.on('window', async (page) => {
  console.log('New window opened:', await page.title());
});

await electronApp.close();

Device-Specific Testing Patterns

Common patterns for mobile and device testing.

interface BrowserContext {
  /** Add geolocation override */
  setGeolocation(geolocation: Geolocation | null): Promise<void>;
  /** Set offline mode */
  setOffline(offline: boolean): Promise<void>;
  /** Override permissions */
  grantPermissions(permissions: string[], options?: BrowserContextGrantPermissionsOptions): Promise<void>;
  /** Clear permissions */
  clearPermissions(): Promise<void>;
}

interface Page {
  /** Emulate media features */
  emulateMedia(options?: PageEmulateMediaOptions): Promise<void>;
  /** Set viewport size */
  setViewportSize(viewportSize: ViewportSize): Promise<void>;
}

interface PageEmulateMediaOptions {
  /** Media type */
  media?: 'screen' | 'print' | null;
  /** Color scheme */
  colorScheme?: 'light' | 'dark' | 'no-preference' | null;
  /** Reduced motion */
  reducedMotion?: 'reduce' | 'no-preference' | null;
  /** Forced colors */
  forcedColors?: 'active' | 'none' | null;
}

interface BrowserContextGrantPermissionsOptions {
  /** Origin for permissions */
  origin?: string;
}

Usage Examples:

// Test responsive design
const context = await browser.newContext({
  ...devices['iPhone 12'],
  viewport: { width: 375, height: 812 }
});

const page = await context.newPage();
await page.goto('https://example.com');

// Test different orientations
await page.setViewportSize({ width: 812, height: 375 }); // Landscape
await page.screenshot({ path: 'landscape.png' });

await page.setViewportSize({ width: 375, height: 812 }); // Portrait  
await page.screenshot({ path: 'portrait.png' });

// Test geolocation
await context.setGeolocation({ 
  latitude: 37.7749, 
  longitude: -122.4194 
});
await context.grantPermissions(['geolocation']);

// Test offline behavior
await context.setOffline(true);
await page.reload();
await context.setOffline(false);

// Test dark mode
await page.emulateMedia({ colorScheme: 'dark' });
await page.screenshot({ path: 'dark-mode.png' });

// Test print media
await page.emulateMedia({ media: 'print' });
await page.screenshot({ path: 'print-view.png' });

// Test reduced motion
await page.emulateMedia({ reducedMotion: 'reduce' });

Types

Mobile Testing Configuration

interface Geolocation {
  /** Latitude coordinate */
  latitude: number;
  /** Longitude coordinate */
  longitude: number;
  /** Location accuracy in meters */
  accuracy?: number;
}

interface ProxySettings {
  /** Proxy server URL */
  server: string;
  /** Bypass proxy for these patterns */
  bypass?: string;
  /** Username for authentication */
  username?: string;
  /** Password for authentication */
  password?: string;
}

interface WaitForEventOptions {
  /** Event timeout */
  timeout?: number;
  /** Event predicate */
  predicate?: Function;
}

type PageFunction<Arg, R> = string | ((arg: Arg) => R | Promise<R>);

Install with Tessl CLI

npx tessl i tessl/npm-playwright

docs

browser-control.md

index.md

mobile-device.md

network-api.md

page-interaction.md

testing-framework.md

visual-debugging.md

tile.json