This guide covers how to run Appium tests locally and explains the CI/CD setup for automated iOS testing.
This project uses Appium with WebDriverIO for automated iOS testing. Tests can be run locally during development or automatically in GitHub Actions CI/CD pipelines.
For the fastest testing workflow, use these three approaches based on your needs:
# 1. First time: Build fresh Release app (requires .env file in app/ directory)
./scripts/run-local-test.sh --build welcome
# 2. Iteration: Reuse last build for fast testing
./scripts/run-local-test.sh --skip-build welcome
# 3. Quick runs: Test with currently installed app
./scripts/run-local-test.sh welcome- Initial build: Use
--buildwhen you change app code (requires.envfile inapp/directory) - Fast iteration: Use
--skip-buildfor repeated testing without rebuilding - Debug mode: Add
--verboseto see detailed logs for troubleshooting
- macOS (required for iOS simulator testing)
- Xcode (latest version recommended)
- Node.js (version 20.x or later)
- iOS Simulator (installed with Xcode)
# Navigate to the app directory
cd app
# Install Node.js dependencies
npm install
# Install Appium globally
npm install -g appium@3.1.1
# Install Appium XCUITest driver for iOS testing
appium driver install xcuitest
# Verify Appium installation
appium --version
appium driver list --installedThe easiest way to build, deploy, and test the app is using the integrated local test script:
# Navigate to project root
cd /path/to/mpt-mobile-platform
# Run tests with existing app (fastest)
./scripts/run-local-test.sh welcome
# Build fresh Release app and run tests (requires .env file)
./scripts/run-local-test.sh --build welcome
# Run with verbose output for debugging
./scripts/run-local-test.sh --build --verbose welcome
# Run specific test file
./scripts/run-local-test.sh ./test/specs/welcome.e2e.js
# Get help and see all options
./scripts/run-local-test.sh --helpThe run-local-test.sh script provides a complete testing solution:
- Builds Release version of the app (optional with
--build, requires.envfile) - Sets up iOS simulator and installs the app
- Starts Appium server automatically
- Runs your specified test suite or spec file
- Cleans up Appium processes when done
This is the recommended approach for local development as it handles the entire workflow automatically.
The test script includes a guard to prefer the app/package.json Appium version where possible and will choose between a local node_modules/.bin/appium, npx appium@<version>, or a global appium binary. If you have version mismatches or want more details, see Appium Version & Binary Sync.
If you only want to build and deploy without running tests:
# Make sure you're in the project root directory
cd /path/to/mpt-mobile-platform
# Run the iOS deployment script
./scripts/deploy-ios.sh --client-id YOUR_AUTH0_CLIENT_ID
# The script will:
# 1. Build the iOS app for simulator
# 2. Boot an available iOS simulator
# 3. Install the app on the simulator
# 4. Launch the appThe deploy-ios.sh script handles all the complexity of building, installing, and launching your app automatically. Once it completes successfully, your app will be ready for Appium testing.
If you prefer manual control or need to troubleshoot, you can set up the simulator manually:
-
Open Xcode and install iOS simulators:
# List available simulators xcrun simctl list devices available # Boot a specific simulator (example) xcrun simctl boot "iPhone 16"
-
Build and install your app on the simulator:
# Build the iOS app for simulator cd ios xcodebuild -workspace SoftwareOne.xcworkspace \ -scheme SoftwareOne \ -configuration Debug \ -sdk iphonesimulator \ -destination 'generic/platform=iOS Simulator' # Install the app on simulator xcrun simctl install booted path/to/your/app.app
The run-local-test.sh script provides the most streamlined testing experience:
# Basic usage - run tests with existing app
./scripts/run-local-test.sh welcome
# Full workflow - build Release app and test (requires .env file)
./scripts/run-local-test.sh --build welcome
# Fast workflow - reuse last build and test (recommended for iteration)
./scripts/run-local-test.sh --skip-build welcome
# Available options:
# --platform, -p Target platform: ios or android (default: ios)
# --build, -b Build release version of the app before testing
# --skip-build, -s Skip build and install existing app from last build
# --build-from-artifact Download and install app from artifact URL (zip or apk)
# --list, --dry-run List all test cases without running them
# --verbose, -v Enable verbose output for debugging
# --help, -h Show help messageScript Features:
- ✅ Automatic build (optional): Builds Release configuration using Expo prebuild + xcodebuild
- ✅ Fast iteration: Reuses last build for quick testing cycles with
--skip-build - ✅ Artifact support: Download and install app from artifact URL with
--build-from-artifact - ✅ Test discovery: List all tests without running them with
--listor--dry-run - ✅ Simulator management: Boots simulator and installs app automatically
- ✅ Appium lifecycle: Starts/stops Appium server as needed
- ✅ Flexible execution: Run from project root or scripts directory
- ✅ Production parity: Uses same build process as CI/CD pipeline
- ✅ Environment config: Reads Auth0 configuration from
.envfile inapp/directory
Performance Comparison:
- Full build (
--build): ~6-8 minutes (build + test) - Skip build (
--skip-build): ~10 seconds (install + test) - No flags: ~5 seconds (test only)
# 1. Make code changes
# 2. Build once with new changes (requires .env file)
./scripts/run-local-test.sh --build welcome
# 3. Iterate quickly without rebuilding
./scripts/run-local-test.sh --skip-build welcome
./scripts/run-local-test.sh --skip-build home
./scripts/run-local-test.sh --skip-build --verbose navigation # Debug a failing test# Quick test runs during TDD
./scripts/run-local-test.sh welcome # Fastest option
./scripts/run-local-test.sh ./test/specs/new.e2e.js # Test specific new spec# Always use fresh builds for production testing (requires .env file)
./scripts/run-local-test.sh --build welcome
# Or download and test from CI artifact URL
./scripts/run-local-test.sh --build-from-artifact https://github.com/.../app.zip welcomeIf you need more control over the testing process:
# Start Appium server with logging
appium --log-level info --log appium.log
# Or start in background
appium --log-level info --log appium.log &The server will start on http://localhost:4723 by default.
The project includes a pre-configured wdio.conf.js file with iOS-specific settings. Key configuration:
// app/wdio.conf.js
exports.config = {
port: 4723,
capabilities: [{
platformName: 'iOS',
'appium:deviceName': 'iPhone 16',
'appium:platformVersion': '26.0',
'appium:automationName': 'XCUITest',
'appium:bundleId': 'com.softwareone.marketplaceMobile'
}],
// ... other config
};After setting up your app using the deploy script or manual method:
# Navigate to app directory
cd app
# Run all tests
npx wdio run wdio.conf.js
# Run specific test suite
npx wdio run wdio.conf.js --suite welcome
# Run with specific spec file
npx wdio run wdio.conf.js --spec test/specs/welcome.spec.jsFor complete control over the testing process:
# 1. Deploy app to simulator (from project root)
./scripts/deploy-ios.sh --client-id YOUR_AUTH0_CLIENT_ID
# 2. Start Appium server in background
cd app
appium --log-level info --log appium.log &
# 3. Wait for Appium to start, then run tests
sleep 3
npx wdio run wdio.conf.js --suite welcome
# 4. Stop Appium when done
pkill -f appiumIf you want to use the deploy script separately:
# 1. Build and deploy app manually
./scripts/deploy-ios.sh --client-id YOUR_AUTH0_CLIENT_ID
# 2. Start Appium server
cd app
appium --log-level info --log appium.log &
# 3. Run tests
npx wdio run wdio.conf.js --suite welcome
# 4. Clean up
pkill -f appium- Appium logs: Check
appium.logfor server-side issues - Test results: Results are saved to
app/test-results/ - Screenshots: Automatic screenshots on failure in
screenshots/ - Simulator logs: Use
xcrun simctl spawn <device_udid> log stream
- Use
--verboseflag withrun-local-test.shfor detailed logging - Check script exit codes and error messages
- Verify
.envfile exists inapp/directory with Auth0 configuration
- No existing build error: If
--skip-buildfails, run with--buildfirst or use./scripts/deploy-ios.sh - Build conflicts: Cannot use
--buildand--skip-buildtogether - Missing .env file:
--buildrequires.envfile inapp/directory with Auth0 configuration - Stale builds: Use
--buildto rebuild if app behavior seems outdated
- If simulator boot fails, try manually booting:
xcrun simctl boot "iPhone 16" - Clean simulator data if needed:
xcrun simctl erase "iPhone 16" - Check available simulators:
xcrun simctl list devices available
- Use
--skip-buildfor test development and debugging - Use
--buildonly when app code or configuration changes - Keep simulator running between test sessions for faster startup
Create a .env file in the app directory with the following configuration (see app/.env.example for a template):
# app/.env
# Auth0 Configuration (required for app builds)
AUTH0_DOMAIN=login-test.pyracloud.com
AUTH0_CLIENT_ID=your_client_id_here
AUTH0_AUDIENCE=https://api-test.pyracloud.com/
AUTH0_SCOPE=openid profile email offline_access
AUTH0_API_URL=https://api.s1.show/public/
AUTH0_OTP_DIGITS=6
AUTH0_SCHEME=com.softwareone.marketplaceMobile
# Airtable Configuration for OTP Testing (required for OTP automation)
AIRTABLE_EMAIL=marketplaceplatformemailtest@gmail.com
AIRTABLE_API_TOKEN=your_airtable_personal_access_token
AIRTABLE_BASE_ID=your_airtable_base_id
AIRTABLE_TABLE_NAME=your_airtable_table_id
AIRTABLE_FROM_EMAIL=no-reply.test@softwareone.com
# Optional Appium Configuration (auto-detected if not set)
APPIUM_HOST=localhost
APPIUM_PORT=4723
DEVICE_NAME=iPhone 16
PLATFORM_VERSION=26.0
APP_BUNDLE_ID=com.softwareone.marketplaceMobileUse the automated environment setup script for easy configuration:
# Load configuration from .env file and set up test environment (iOS default)
source ./scripts/setup-test-env.sh
# Setup for Android
source ./scripts/setup-test-env.sh --platform android
# Start a simulator/emulator by name
source ./scripts/setup-test-env.sh --start-emulator "iPhone 16"
source ./scripts/setup-test-env.sh --platform android --start-emulator "Pixel_8_API_34"
# Start Appium server with inspector plugin
source ./scripts/setup-test-env.sh --start-appium
# Stop Appium server
source ./scripts/setup-test-env.sh --stop-appium
# List available simulators and emulators
source ./scripts/setup-test-env.sh --list-emulatorsAvailable Options:
--platform <ios|android>: Set the target platform (default: ios)--start-emulator <name>: Start emulator/simulator by name--start-appium: Start Appium server with inspector plugin--stop-appium: Stop all running Appium processes--list-emulators: List available emulators/simulators--help: Show help message
List all available tests without running them:
# List all tests
./scripts/run-local-test.sh --list all
# List tests for a specific suite
./scripts/run-local-test.sh --dry-run welcome
./scripts/run-local-test.sh --list spotlight
# List tests for a specific spec file
./scripts/run-local-test.sh --list ./test/specs/navigation.e2e.jsThis is useful for discovering available tests before running them, or for CI/CD pipelines that need to know test counts.
The setup script will automatically:
- Load values from
app/.env - Detect and optionally start iOS simulators or Android emulators
- Configure platform-specific Appium variables
- Set up Airtable OTP testing variables
- Display current configuration
The project includes automated CI/CD workflows in .github/workflows/:
- File:
.github/workflows/ios-build-and-test.yml - Triggers: Pull requests to
mainbranch, manual dispatch - Process:
- Build Job: Compiles iOS app for simulator
- Install and Test Job: Downloads app, sets up simulator, runs Appium tests
- File:
.github/workflows/ios-external-test-example.yml - Purpose: Test pre-built apps from external URLs (e.g., Azure blob storage)
- Use case: Testing builds from other CI systems
Reusable components in .github/actions/:
- Path:
.github/actions/ios-install/ - Purpose: Download, extract, and install iOS app on simulator
- Inputs:
artifact_name: GitHub artifact namedownload_url: External download URL (alternative)ios_version: iOS simulator versiondevice_name: Simulator device name
- Path:
.github/actions/ios-test/ - Purpose: Set up Appium and run test suite
- Inputs:
device_uuid: iOS simulator UUIDapp_bundle_id: App bundle identifiernode_version: Node.js versionappium_version: Appium version
- Single Runner: Install and test run on same GitHub Actions runner, preserving simulator state
- Reusable Components: Composite actions can be used across multiple workflows
- Artifact Management: Automatic handling of build artifacts and test results
- Error Handling: Comprehensive logging and artifact uploads for debugging
# .github/workflows/my-test.yml
jobs:
test-ios:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Install iOS App
uses: ./.github/actions/ios-install
with:
artifact_name: "my-ios-build"
- name: Test iOS App
uses: ./.github/actions/ios-test
with:
device_uuid: ${{ steps.install-ios.outputs.device_udid }}
app_bundle_id: ${{ steps.install-ios.outputs.bundle_id }}- name: Install External Build
uses: ./.github/actions/ios-install
with:
download_url: "https://example.com/build.zip"
ios_version: "26.0"
device_name: "iPhone 16"app/
├── test/
│ ├── specs/ # Test specification files
│ │ ├── welcome.spec.js
│ │ └── auth.spec.js
│ └── pageobjects/ # Page object models
│ ├── base/ # Base classes (base.page.js, list.page.js, footer.page.js)
│ ├── utils/ # Utilities (selectors.js, constants.js)
│ ├── welcome.page.js
│ └── ...
├── test-results/ # Test execution results
└── wdio.conf.js # WebDriverIO configuration
screenshots/ # Failure screenshots (project root)
// test/specs/example.spec.js
describe('My App', () => {
it('should display welcome screen', async () => {
// Test implementation
await expect($('~welcome-screen')).toBeDisplayed();
});
});-
Deploy script fails:
# Check script permissions chmod +x ./scripts/deploy-ios.sh # Run with verbose output bash -x ./scripts/deploy-ios.sh
-
Simulator not booting (Manual setup):
# Reset simulator xcrun simctl erase "iPhone 16" xcrun simctl boot "iPhone 16"
-
App installation fails:
- Verify app is built for simulator (not device)
- Check app bundle path is correct
- Ensure simulator is booted
- Try using the deploy script instead of manual installation
-
Appium connection issues:
- Verify Appium server is running on correct port
- Check firewall settings
- Ensure XCUITest driver is installed
-
Test timeouts:
- Increase timeout values in
wdio.conf.js - Check simulator performance
- Verify app is launching correctly
- Increase timeout values in
- Appium logs:
appium.log - WebDriverIO logs: Console output during test run
- Simulator logs:
xcrun simctl spawn <udid> log stream - CI logs: GitHub Actions workflow logs and uploaded artifacts
- Test Isolation: Each test should be independent and clean up after itself
- Page Objects: Use page object pattern for maintainable tests
- Explicit Waits: Use WebDriverIO's built-in wait methods
- Error Handling: Include proper error handling and cleanup
- CI Integration: Design tests to work reliably in CI environment