npm.io
0.1.2 • Published 23h ago

@playwrightmobile/fixtures

Licence
MIT
Version
0.1.2
Deps
7
Size
14 kB
Vulns
0
Weekly
0

PlaywrightMobile

Playwright-native Android automation. Zero configuration. Zero mobile expertise required.

Installation

npm install -D @playwrightmobile/test @playwright/test

Environment Check

npx playwrightmobile doctor
✔ Android SDK found
✔ ADB found
✔ Device detected
✔ Environment ready

Your First Test

import { test, expect } from '@playwrightmobile/test';

test('login flow', async ({ mobile }) => {
  await mobile.launch({ app: './app.apk' });

  await mobile.getByPlaceholder('Email').fill('admin@test.com');
  await mobile.getByPlaceholder('Password').fill('password');
  await mobile.getByRole('button', { name: 'Login' }).click();

  await expect(mobile.getByText('Dashboard')).toBeVisible();
});
npx playwright test

That's it. No Appium. No desired capabilities. No server startup.


Configuration

Create playwright.config.ts:

import { defineMobileConfig } from '@playwrightmobile/test';

export default defineMobileConfig({
  mobileDefaults: {
    screenshot: 'only-on-failure',
    trace: 'retain-on-failure'
  },
  projects: [
    { name: 'android-emulator' },
    { name: 'android-real-device', use: { deviceId: 'R3CN90XXXXX' } },
    {
      name: 'browserstack',
      use: {
        playwrightmobile: {
          cloud: {
            provider: 'browserstack',
            username: process.env.BS_USERNAME,
            accessKey: process.env.BS_ACCESS_KEY,
            deviceName: 'Google Pixel 9',
            platformVersion: '14.0'
          }
        }
      }
    }
  ]
});

Locator API

Method Example
getByText() mobile.getByText('Login')
getByRole() mobile.getByRole('button', { name: 'Submit' })
getByPlaceholder() mobile.getByPlaceholder('Email')
getByLabel() mobile.getByLabel('Username')
getByTestId() mobile.getByTestId('login-btn')
getByAccessibilityId() mobile.getByAccessibilityId('loginButton')
locator() mobile.locator('//android.widget.Button')

Gestures

// Tap / Click
await mobile.getByText('Submit').click();
await mobile.getByText('Item').dblclick();
await mobile.getByText('Hold').longPress();

// Text input
await mobile.getByPlaceholder('Search').fill('query');
await mobile.getByPlaceholder('Search').clear();

// Gestures
await mobile.swipe({ direction: 'up' });
await mobile.swipe({ direction: 'left', distance: 0.8 });
await mobile.scroll({ direction: 'down' });

// Pinch / Zoom
await mobile.pinch({ scale: 0.5 });                    // pinch-in (zoom out)
await mobile.pinch({ scale: 2.0, centerX: 400, centerY: 600 }); // pinch-out (zoom in)

// Keys
await mobile.pressKey('enter');
await mobile.pressKey('back');

Assertions

await expect(mobile.getByText('Dashboard')).toBeVisible();
await expect(mobile.getByText('Loading')).toBeHidden();
await expect(mobile.getByRole('button', { name: 'Submit' })).toBeEnabled();
await expect(mobile.getByRole('checkbox')).toBeChecked();
await expect(mobile.getByRole('checkbox')).toBeChecked({ checked: false }); // unchecked
await expect(mobile.getByText('Hello')).toHaveText('Hello World');
await expect(mobile.getByRole('button')).toHaveCount(3);
await expect(mobile.getByRole('option')).toBeSelected();
await expect(mobile.getByRole('list')).toBeScrollable();
await expect(mobile.getByTestId('avatar')).toHaveAttribute('content-desc', 'User Avatar');

Auto-Wait & Auto-Scroll

Every action automatically:

  1. Waits for the element to be visible
  2. Waits for the element to be enabled
  3. Scrolls to find the element if not in viewport
  4. Retries intelligently on transient failures

You never need waitForElement(), sleep(), or explicit scrolls.


App Lifecycle

// Launch with APK (auto-installs)
await mobile.launch({ app: './my-app.apk' });

// Launch already-installed app
await mobile.launch({ appPackage: 'com.example.app' });

// Launch with options
await mobile.launch({
  appPackage: 'com.example.app',
  appActivity: '.MainActivity',
  clearData: true,
  permissions: ['android.permission.CAMERA']
});

// Deep links
await mobile.openDeepLink('example://dashboard');

// Terminate / relaunch
await mobile.terminate('com.example.app');
await mobile.activate('com.example.app');

Device Features

// Permissions
await mobile.grantPermission('com.example.app', 'android.permission.CAMERA');
await mobile.denyPermission('com.example.app', 'android.permission.LOCATION');

// GPS
await mobile.setGps({ latitude: 37.7749, longitude: -122.4194 });

// Network
await mobile.setNetworkCondition({ offline: true });
await mobile.setNetworkCondition({ offline: false });
await mobile.setNetworkCondition({ latency: 200, downloadSpeed: 1000, uploadSpeed: 500 }); // emulator only

// Biometrics
await mobile.authenticateBiometric(true);   // success
await mobile.authenticateBiometric(false);  // failure

// Notifications
await mobile.openNotifications();

// Orientation
await mobile.setOrientation('landscape');
await mobile.setOrientation('portrait');
const orientation = await mobile.getOrientation(); // 'portrait' | 'landscape'

// Keyboard
await mobile.hideKeyboard();
const shown = await mobile.isKeyboardShown();

// Clipboard
await mobile.setClipboard('hello world');
const text = await mobile.getClipboard();

// File transfer
await mobile.pushFile('./local/file.txt', '/sdcard/file.txt');
await mobile.pullFile('/sdcard/file.txt', './local/file.txt');

// App state
const state = await mobile.getAppState('com.example.app');
// 'foreground' | 'background' | 'not_running' | 'not_installed'
const installed = await mobile.isAppInstalled('com.example.app');

// WebView / hybrid apps
const contexts = await mobile.getContexts();
// ['NATIVE_APP', 'WEBVIEW_com.example.app']
await mobile.switchContext('WEBVIEW_com.example.app');
await mobile.switchContext('NATIVE_APP');

// Device info
const info = await mobile.deviceInfo();
console.log(info.model, info.platformVersion);

Code Generation

Record interactions and generate test code:

npx playwrightmobile codegen

Interact with your device. Press Ctrl+C to stop and print generated code.


Device Management

npx playwrightmobile devices
📱 Connected Devices

  ✔ 🖥  Android SDK built for x86_64 (Android 14) [emulator-5554]
  ✔ 📱 Pixel 9 (Android 14) [R3CN90XXXXX]

Failure Diagnostics

On test failure, PlaywrightMobile automatically collects:

test-results/
  login_flow/
    screenshot.png
    logcat.txt
    video.mp4
    trace.json.gz

Cloud Providers

The same test code runs on any provider. Only the config changes.

// playwright.config.ts
projects: [
  {
    name: 'browserstack',
    use: { playwrightmobile: { cloud: { provider: 'browserstack', ... } } }
  },
  {
    name: 'perfecto',
    use: { playwrightmobile: { cloud: { provider: 'perfecto', ... } } }
  },
  {
    name: 'lambdatest',
    use: { playwrightmobile: { cloud: { provider: 'lambdatest', ... } } }
  },
  {
    name: 'saucelabs',
    use: {
      playwrightmobile: {
        cloud: {
          provider: 'saucelabs',
          username: process.env.SAUCE_USERNAME,
          accessKey: process.env.SAUCE_ACCESS_KEY,
          deviceName: 'Google Pixel 9 GoogleAPI Emulator',
          platformVersion: '14.0',
          datacenter: 'us-west-1'
        }
      }
    }
  }
]

Architecture

@playwrightmobile/test
  ├── @playwrightmobile/fixtures       ← Playwright fixture integration
  ├── @playwrightmobile/assertions     ← Extended expect matchers
  └── @playwrightmobile/sdk            ← MobilePage + MobileLocator API
        ├── @playwrightmobile/android-driver    ← ADB + UIAutomator2
        ├── @playwrightmobile/locator-engine    ← Selector translation
        ├── @playwrightmobile/device-manager    ← Device discovery
        ├── @playwrightmobile/session-manager   ← Session lifecycle
        ├── @playwrightmobile/cloud-adapters    ← BrowserStack/Perfecto/LambdaTest
        └── @playwrightmobile/trace             ← Trace recording

Docker

# Run tests in Docker with USB device passthrough
docker-compose run test-real-device

# Run tests against emulator
docker-compose up emulator
docker-compose run test-emulator