> ## Documentation Index
> Fetch the complete documentation index at: https://docs.morphllm.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Mobile App Testing

> Test iOS and Android apps with natural language on real devices

<Warning>
  **Beta Feature** — Mobile automation is currently in beta. Please report any issues to [founders@morphllm.com](mailto:founders@morphllm.com).
</Warning>

<Info>
  Mobile app testing is available on **Pro** and **Scale** plans. [Upgrade your plan](https://morphllm.com/dashboard) to get started.
</Info>

Test your mobile apps with natural language. Describe what to test and Morph runs it on real iOS and Android devices.

## Quick Start

```typescript theme={null}
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.

### GitHub Releases (Recommended)

The simplest approach for most teams:

```typescript theme={null}
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:

```yaml theme={null}
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:

```typescript theme={null}
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:

```ruby theme={null}
# 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:

```typescript theme={null}
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:**

```typescript theme={null}
{
  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:

```typescript theme={null}
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:

```typescript theme={null}
const { devices } = await mobile.listDevices();

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

## Available Devices

### iOS

| Device            | Default OS |
| ----------------- | ---------- |
| iPhone 17 Pro Max | 26         |
| iPhone 17 Pro     | 26         |
| iPhone 17         | 26         |
| iPhone 16 Pro Max | 18         |
| iPhone 16 Pro     | 18         |
| iPhone 15 Pro Max | 17         |
| iPad Pro 13 2025  | 26         |

### Android

| Device             | Default OS |
| ------------------ | ---------- |
| Samsung Galaxy S24 | 14         |
| Samsung Galaxy S23 | 14         |
| Google Pixel 8     | 14         |
| Google Pixel 7     | 13         |

## Writing Good Tasks

Be specific:

```typescript theme={null}
// 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:

```typescript theme={null}
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

<AccordionGroup>
  <Accordion title="What devices are supported?">
    Real iOS devices (iPhone 15-17, iPad Pro) and Android devices (Samsung Galaxy, Google Pixel). Use `listDevices()` to see all available options.
  </Accordion>

  <Accordion title="What URL formats work for the app parameter?">
    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.
  </Accordion>

  <Accordion title="How long do tests take?">
    * Simple tests: 30-60 seconds
    * Medium tests: 1-2 minutes
    * Complex flows: 2-5 minutes
  </Accordion>

  <Accordion title="What AI model powers the testing?">
    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.
  </Accordion>

  <Accordion title="Can I test apps not yet published to the App Store?">
    Yes. Provide a URL to your development build (`.ipa` for iOS, `.apk` for Android). This is ideal for testing PR builds before merging.
  </Accordion>
</AccordionGroup>
