Running Android and iOS Appium Tests in Parallel on a Single Mac (WDIO + Appium)

This post documents our experience setting up parallel mobile automation on a single macOS machine, running:

  • Android tests on a real device

  • iOS tests on a simulator

using WebdriverIO, Appium, and Cucumber, with a shared test codebase.

The intention is to highlight the challenges, root causes, and final approach that worked reliably both locally and in CI.

Objective

  • Execute Android and iOS tests simultaneously

  • Maintain one automation codebase

  • Support:

    • Android-only execution

    • iOS-only execution

    • Android + iOS parallel execution

  • Ensure the setup is pipeline-friendly

Key Challenges Encountered

1. Appium Port Conflicts

Running both platforms against a single Appium server caused session creation conflicts.

Resolution

Two separate Appium servers were used:

  • iOS → port 4723

  • Android → port 4725

appium -p 4723 --base-path /
appium -p 4725 --base-path /

2. Locator Strategy Mismatch Across Platforms

When using Promise.all() with shared page objects, Android-specific locators were executed on iOS sessions and vice versa.

Typical error:

Locator Strategy '-android uiautomator' is not supported for this session

Root Cause

Using driver.isAndroid does not work correctly in MultiRemote mode, because multiple sessions exist simultaneously.

Resolution

Explicitly scope commands to the correct session:

  • browser.android

  • browser.ios

3. Failures When Only One Platform Is Available

When running only iOS (Android device disconnected), tests still attempted to connect to the Android Appium server, resulting in connection failures.

Resolution

Execution was made platform-aware, so only available platforms are used.

Final Working Architecture

Platform Selection via Environment Variable

PLATFORM=android npx wdio run wdio.conf.js
PLATFORM=ios npx wdio run wdio.conf.js
PLATFORM=both npx wdio run wdio.conf.js

This allows:

  • Local execution

  • CI pipeline execution

  • Selective platform runs

MultiRemote Configuration (Conditional)

Only the required platforms are initialized based on PLATFORM.

This prevents attempts to connect to non-running Appium servers.

Page Object Design (Platform Explicit)

class LoginPage {
    android = {
        nextButton: () =>
            browser.android.$('android=new UiSelector().description("Next")')
    };

    ios = {
        nextButton: () =>
            browser.ios.$('~Next')
    };
}

This avoids conditional logic inside element definitions and keeps platform handling explicit.

Safe Parallel Execution Helper

async function runOnPlatforms(actions) {
    const tasks = [];

    if (browser.android && actions.android) {
        tasks.push(actions.android());
    }
    if (browser.ios && actions.ios) {
        tasks.push(actions.ios());
    }

    await Promise.all(tasks);
}

Cucumber Step Implementation

When('User clicks Next', async () => {
    await runOnPlatforms({
        android: () => LoginPage.android.nextButton().click(),
        ios: () => LoginPage.ios.nextButton().click()
    });
});

This works safely for:

  • Android only

  • iOS only

  • Android + iOS together

Key Learnings

  • driver.isAndroid should not be used in MultiRemote scenarios

  • Always scope commands using browser.android and browser.ios

  • Never assume both platforms are available during execution

  • Platform-aware execution is essential for CI stability

  • A single Mac can reliably run Android (real device) and iOS (simulator) in parallel

Outcome

  • Stable local parallel execution

  • Single shared automation framework

  • CI-ready configuration

  • Easy to extend to cloud providers in the future

Discussion

Curious to hear how others are approaching mobile automation at scale:

  • How are you handling parallel execution across Android and iOS?

  • Are you running real devices and simulators in CI, or using cloud providers?

  • How do you manage Appium server orchestration in parallel setups?

  • Do you prefer a single unified framework or separate platform-specific pipelines?

Would love to exchange ideas and learn from different approaches.

1 Like