Sauce Labs Mobile Testing: Setup and Best Practices

Sauce Labs Mobile Testing: Setup and Best Practices

Sauce Labs provides cloud-based mobile testing on real iOS and Android devices with a focus on Appium integration and enterprise-grade reliability. This guide covers setup, configuration, and best practices for running your mobile tests on Sauce Labs.

Sauce Labs Mobile Testing Platforms

Sauce Labs offers two mobile testing platforms:

Sauce Labs Real Device Cloud: Physical iOS and Android devices for E2E testing, exploratory testing, and debugging.

Sauce Labs Virtual Devices: Android emulators and iOS simulators for faster, cheaper execution — good for unit and integration tests.

The choice mirrors the broader real device vs. emulator decision: real devices for production-confidence testing, virtual devices for high-frequency CI.

Getting Started

You'll need:

  • Sauce Labs account (free trial available)
  • SAUCE_USERNAME and SAUCE_ACCESS_KEY from your account settings
  • Your app binary (.apk for Android, .ipa for iOS)
  • Appium tests written in your framework of choice

Step 1: Upload Your App

Apps can be uploaded to Sauce Labs storage for use in tests.

Using the REST API:

curl -u "$SAUCE_USERNAME:<span class="hljs-variable">$SAUCE_ACCESS_KEY" \
  -X POST <span class="hljs-string">"https://api.us-west-1.saucelabs.com/v1/storage/upload" \
  -H <span class="hljs-string">"Content-Type: application/octet-stream" \
  -H <span class="hljs-string">"Content-Disposition: attachment; filename=app.apk" \
  --data-binary @app/build/outputs/apk/debug/app-debug.apk

Response:

{
  "item": {
    "id": "a1b2c3d4-...",
    "storage_path": "storage:filename=app-debug.apk"
  }
}

Use storage:filename=app-debug.apk as the app capability in your tests.

Using the Sauce CLI (saucectl):

npm install -g saucectl
saucectl storage upload app-debug.apk

Step 2: Configure Appium Capabilities

Sauce Labs uses W3C capabilities with sauce:options for Sauce-specific configuration:

Android real device:

const capabilities = {
  platformName: 'Android',
  'appium:app': 'storage:filename=app-debug.apk',
  'appium:deviceName': 'Samsung Galaxy S24',
  'appium:platformVersion': '14',
  'appium:automationName': 'UiAutomator2',
  'sauce:options': {
    username: process.env.SAUCE_USERNAME,
    accessKey: process.env.SAUCE_ACCESS_KEY,
    deviceType: 'phone',
    appiumVersion: '2.0.0',
    name: 'Login Flow Test',
    build: `Build ${process.env.BUILD_NUMBER}`,
    tags: ['regression', 'android'],
    privateDevicesOnly: false,  // Set true for dedicated devices
    carrierConnectivityEnabled: false
  }
};

iOS real device:

const capabilities = {
  platformName: 'iOS',
  'appium:app': 'storage:filename=app.ipa',
  'appium:deviceName': 'iPhone 15 Pro',
  'appium:platformVersion': '17',
  'appium:automationName': 'XCUITest',
  'sauce:options': {
    username: process.env.SAUCE_USERNAME,
    accessKey: process.env.SAUCE_ACCESS_KEY,
    name: 'iOS Login Test',
    build: `Build ${process.env.BUILD_NUMBER}`,
    tags: ['regression', 'ios']
  }
};

Virtual device (Android emulator):

const capabilities = {
  platformName: 'Android',
  'appium:app': 'storage:filename=app-debug.apk',
  'appium:deviceName': 'Android GoogleAPI Emulator',
  'appium:platformVersion': '12.0',
  'appium:automationName': 'UiAutomator2',
  'sauce:options': {
    username: process.env.SAUCE_USERNAME,
    accessKey: process.env.SAUCE_ACCESS_KEY,
    name: 'Emulator Test',
    build: `Build ${process.env.BUILD_NUMBER}`
  }
};

Step 3: Connect to Sauce Labs

Replace your local Appium server URL with the Sauce Labs endpoint:

const driver = await remote({
  protocol: 'https',
  hostname: 'ondemand.us-west-1.saucelabs.com',
  port: 443,
  path: '/wd/hub',
  capabilities: capabilities
});

Regional endpoints:

  • US: ondemand.us-west-1.saucelabs.com
  • EU: ondemand.eu-central-1.saucelabs.com
  • US East: ondemand.us-east-4.saucelabs.com

Choose the closest region to your CI infrastructure for lower latency.

Step 4: Write a Test

const { remote } = require('webdriverio');

describe('Mobile app tests', () => {
  let driver;

  before(async () => {
    driver = await remote({
      protocol: 'https',
      hostname: 'ondemand.us-west-1.saucelabs.com',
      port: 443,
      path: '/wd/hub',
      capabilities: {
        platformName: 'Android',
        'appium:app': 'storage:filename=app-debug.apk',
        'appium:deviceName': 'Samsung Galaxy S24',
        'appium:platformVersion': '14',
        'appium:automationName': 'UiAutomator2',
        'sauce:options': {
          username: process.env.SAUCE_USERNAME,
          accessKey: process.env.SAUCE_ACCESS_KEY,
          name: 'Login test'
        }
      }
    });
  });

  after(async () => {
    // Sauce Labs reads test status from the driver session
    if (driver) {
      await driver.executeScript('sauce:job-result=passed');
      await driver.deleteSession();
    }
  });

  it('completes login successfully', async () => {
    const email = await driver.$('~email-input');
    await email.setValue('user@example.com');

    const password = await driver.$('~password-input');
    await password.setValue('SecurePass123');

    const submit = await driver.$('~login-button');
    await submit.click();

    const dashboard = await driver.$('~dashboard-screen');
    await dashboard.waitForDisplayed({ timeout: 15000 });

    expect(await dashboard.isDisplayed()).toBe(true);
  });
});

Important: Call sauce:job-result=passed before ending the session. Sauce Labs uses this to mark the test pass/fail in the dashboard.

Reporting Test Status

Set test status explicitly so the Sauce Labs dashboard reflects real results:

// Mark pass
await driver.executeScript('sauce:job-result=passed');

// Mark fail  
await driver.executeScript('sauce:job-result=failed');

// In a framework with beforeEach/afterEach:
afterEach(async function() {
  if (driver) {
    const passed = this.currentTest.state === 'passed';
    await driver.executeScript(`sauce:job-result=${passed ? 'passed' : 'failed'}`);
  }
});

saucectl: Sauce Labs' Test Runner

Sauce Labs provides saucectl — a CLI tool for running tests without writing Appium boilerplate directly.

Install:

npm install -g saucectl
saucectl configure  # Enter credentials

Configure .sauce/config.yml:

apiVersion: v1alpha
kind: xcuitest
sauce:
  region: us-west-1
  concurrency: 5

xcuitest:
  app: storage:filename=MyApp.ipa
  testApp: storage:filename=MyApp-Runner.ipa

suites:
  - name: "iPhone 15 Pro - iOS 17"
    testOptions:
      class:
        - MyAppTests.LoginTests
        - MyAppTests.CheckoutTests
    devices:
      - name: iPhone 15 Pro
        platformVersion: "17"

Run:

saucectl run

saucectl handles parallelization, result collection, and CI integration.

Parallel Execution

Run across multiple devices simultaneously:

// wdio.conf.js
exports.config = {
  user: process.env.SAUCE_USERNAME,
  key: process.env.SAUCE_ACCESS_KEY,
  hostname: 'ondemand.us-west-1.saucelabs.com',
  port: 443,
  path: '/wd/hub',
  maxInstances: 10,  // 10 parallel sessions
  capabilities: [
    {
      platformName: 'Android',
      'appium:deviceName': 'Samsung Galaxy S24',
      'appium:platformVersion': '14',
      'appium:automationName': 'UiAutomator2',
      'appium:app': 'storage:filename=app.apk',
      'sauce:options': { build: process.env.BUILD_NAME }
    },
    {
      platformName: 'iOS',
      'appium:deviceName': 'iPhone 15 Pro',
      'appium:platformVersion': '17',
      'appium:automationName': 'XCUITest',
      'appium:app': 'storage:filename=app.ipa',
      'sauce:options': { build: process.env.BUILD_NAME }
    },
    // Add more devices...
  ]
};

GitHub Actions Integration

# .github/workflows/sauce-labs.yml
name: Sauce Labs Mobile Tests

on:
  push:
    branches: [main]

jobs:
  mobile-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build Android app
        run: ./gradlew assembleDebug
      
      - name: Upload app to Sauce Labs
        id: upload
        run: |
          response=$(curl -s -u "${{ secrets.SAUCE_USERNAME }}:${{ secrets.SAUCE_ACCESS_KEY }}" \
            -X POST "https://api.us-west-1.saucelabs.com/v1/storage/upload" \
            -H "Content-Type: application/octet-stream" \
            -H "Content-Disposition: attachment; filename=app-debug.apk" \
            --data-binary @app/build/outputs/apk/debug/app-debug.apk)
          echo "app_id=$(echo $response | jq -r '.item.id')" >> $GITHUB_OUTPUT
      
      - name: Run Sauce Labs tests
        env:
          SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
          SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
          BUILD_NUMBER: ${{ github.run_number }}
        run: npm run test:mobile

Debugging with Sauce Labs

Live session view: During test execution, open the Sauce Labs dashboard to watch your test run in real-time.

Video replay: Every session records a full video. Available in the dashboard after the session completes.

Appium logs: Complete Appium server logs, filterable by command.

Device logs: System logs (logcat for Android, device console for iOS) captured during the session.

Network capture: Enable with 'sauce:options': { capturePerformance: true }.

Sauce Connect: Testing Private Apps

If your app accesses staging environments or internal APIs not accessible from the internet, use Sauce Connect — a secure tunnel.

# Download Sauce Connect
wget https://saucelabs.com/downloads/sauce-connect/5.0/sauce-connect-5.0-linux.tar.gz

<span class="hljs-comment"># Start tunnel
./sc -u <span class="hljs-variable">$SAUCE_USERNAME -k <span class="hljs-variable">$SAUCE_ACCESS_KEY --tunnel-name my-tunnel

Add to capabilities:

'sauce:options': {
  tunnelName: 'my-tunnel'
}

Sauce Connect proxies device traffic through your local network, enabling devices to reach internal resources.

Best Practices

Use descriptive test names and builds. The Sauce Labs dashboard is easier to use when tests have meaningful names. Include branch name, build number, and feature name.

Always set job-result. Failing tests that are marked as passed in the dashboard erode trust in your testing infrastructure.

Clean up sessions. Always call driver.deleteSession() — orphaned sessions count against your concurrency limit.

Use private devices for flaky scenarios. Shared device pools have device state from previous sessions. Private devices are freshly provisioned for each session.

Set appropriate timeouts. Real device sessions have network latency. Set waitForDisplayed timeouts to 10-15 seconds minimum.

Group related tests in suites. Session startup takes 30-60 seconds. Tests that are too short amortize startup overhead poorly.

Conclusion

Sauce Labs provides a production-grade mobile testing infrastructure with strong Appium integration, good debugging tools, and enterprise features like Sauce Connect for private environments. The saucectl runner simplifies the boilerplate for common frameworks.

The key workflow: upload app → configure capabilities → run parallel suites → review dashboard for failures. Once CI is configured, it runs without maintenance unless the app substantially changes.

For continuous production monitoring between scheduled test runs, HelpMeTest provides 24/7 automated checks that complement your Sauce Labs test coverage.

Read more