Skip to main content
Beta Feature — Mobile automation is currently in beta. Please report any issues to founders@morphllm.com.
Mobile app testing is available on Pro and Scale plans. Upgrade your plan to get started.
Test your mobile apps with natural language. Describe what to test and Morph runs it on real iOS and Android devices.

Quick Start

import { MobileClient } from '@morphllm/morphsdk/tools/mobile';

const mobile = new MobileClient({ apiKey: process.env.MORPH_API_KEY });

const result = await mobile.execute({
  task: "Tap the login button and verify the login form appears",
  app: "https://github.com/myorg/myapp/releases/download/v1.0/app.ipa",
  platform: "ios",
  device: "iPhone 16 Pro"
});

console.log(result.success ? 'Passed' : 'Failed');
console.log(result.trace_url); // GIF of the test execution

Providing Your App

Pass a publicly accessible URL to your .ipa (iOS) or .apk (Android) file. Morph downloads and provisions it automatically. The simplest approach for most teams:
await mobile.execute({
  task: "Test the checkout flow",
  app: "https://github.com/myorg/myapp/releases/download/v1.2.3/MyApp.ipa",
  platform: "ios"
});

GitHub Actions Artifacts

Upload your build artifact and pass the download URL:
name: Mobile Tests
on: [push]

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build iOS App
        run: |
          # Your build command (Xcode, Fastlane, etc.)
          xcodebuild -scheme MyApp -sdk iphoneos -configuration Release

      - name: Upload to GitHub Release
        id: upload
        uses: softprops/action-gh-release@v1
        with:
          files: build/MyApp.ipa
          tag_name: build-${{ github.run_number }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Run Mobile Tests
        env:
          MORPH_API_KEY: ${{ secrets.MORPH_API_KEY }}
        run: |
          curl -X POST https://api.morphllm.com/v1/mobile-task \
            -H "Authorization: Bearer $MORPH_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
              "task": "Test the login flow with test@example.com and password123",
              "app": "${{ steps.upload.outputs.assets[0].browser_download_url }}",
              "platform": "ios",
              "device": "iPhone 16 Pro",
              "external_id": "PR-${{ github.event.pull_request.number }}",
              "commit_id": "${{ github.sha }}"
            }'

S3 or Cloud Storage

Generate a presigned URL and pass it:
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const s3 = new S3Client({ region: 'us-east-1' });
const command = new GetObjectCommand({ Bucket: 'my-builds', Key: 'app.ipa' });
const appUrl = await getSignedUrl(s3, command, { expiresIn: 3600 });

await mobile.execute({
  task: "Verify onboarding flow",
  app: appUrl,
  platform: "ios"
});

Fastlane Integration

After building with Fastlane, upload to a release:
# Fastfile
lane :test do
  build_app(scheme: "MyApp")

  # Upload to GitHub Release
  set_github_release(
    repository_name: "myorg/myapp",
    tag_name: "v#{get_version_number}",
    upload_assets: ["MyApp.ipa"]
  )
end

API

execute()

Run a test synchronously:
const result = await mobile.execute({
  // Required
  task: "Test the checkout flow",
  app: "https://github.com/myorg/myapp/releases/download/v1.0/app.ipa",

  // Optional
  platform: "ios",              // "ios" | "android" (default: "ios")
  device: "iPhone 16 Pro",      // Device name (default: "iPhone 16 Pro")
  os_version: "18",             // OS version (auto-detected if omitted)
  max_steps: 50,                // Max agent steps (default: 50)
  record_trace: true,           // Generate GIF trace (default: true)

  // CI/CD tracking
  external_id: "PR-123",
  repo_id: "myorg/myapp",
  commit_id: "abc123"
});
Response:
{
  success: boolean,
  result?: string,           // Test findings
  error?: string,            // Error if failed
  task_id?: string,
  steps_taken?: number,
  execution_time_ms?: number,
  trace_url?: string,        // GIF trace URL
  trace_status?: "PENDING" | "PROCESSING" | "COMPLETED" | "ERROR"
}

createTask()

Run tests asynchronously:
const task = await mobile.createTask({
  task: "Complete the onboarding flow",
  app: "https://github.com/myorg/myapp/releases/download/v1.0/app.ipa",
  device: "iPhone 16 Pro"
});

console.log('Task started:', task.task_id);

// Poll for completion
const result = await task.complete();
console.log('Result:', result.result);

listDevices()

Get available devices:
const { devices } = await mobile.listDevices();

devices.forEach(d => {
  console.log(`${d.device_name} (${d.platform})`);
});

Available Devices

iOS

DeviceDefault OS
iPhone 17 Pro Max26
iPhone 17 Pro26
iPhone 1726
iPhone 16 Pro Max18
iPhone 16 Pro18
iPhone 15 Pro Max17
iPad Pro 13 202526

Android

DeviceDefault OS
Samsung Galaxy S2414
Samsung Galaxy S2314
Google Pixel 814
Google Pixel 713

Writing Good Tasks

Be specific:
// Good
"Tap the login button, enter test@example.com in email, enter password123, and tap Sign In"

// Bad
"test login"
max_steps guide:
  • Simple tasks (tap, verify): 10-20 steps
  • Form submissions: 20-30 steps
  • Complex flows (checkout, onboarding): 30-50 steps

GIF Traces

Every test generates a GIF trace showing what happened:
const result = await mobile.execute({
  task: "Navigate through settings",
  app: "https://...",
  record_trace: true
});

if (result.trace_url) {
  console.log('Watch the test:', result.trace_url);
}

FAQ

Real iOS devices (iPhone 15-17, iPad Pro) and Android devices (Samsung Galaxy, Google Pixel). Use listDevices() to see all available options.
Any publicly accessible URL that returns the raw .ipa or .apk file:
  • GitHub Releases: https://github.com/org/repo/releases/download/v1.0/app.ipa
  • S3 presigned URLs: https://bucket.s3.amazonaws.com/app.ipa?...
  • Direct download links: Any URL that downloads the file directly
The URL must be accessible without authentication, or use presigned/temporary credentials.
  • Simple tests: 30-60 seconds
  • Medium tests: 1-2 minutes
  • Complex flows: 2-5 minutes
Morph uses Gemini 3 Flash for understanding your app’s UI and executing actions. The model analyzes screenshots, identifies elements, and performs taps, swipes, and text input.
Yes. Provide a URL to your development build (.ipa for iOS, .apk for Android). This is ideal for testing PR builds before merging.