ReportPortalAgent is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'ReportPortal'and install it:
cd <project>
pod installAlso available through SPM by name "agent-swift-xctest" or URL of current repo
The properties for Report Portal configuration should be set in the Info.plist file of your Test Target. If you Test Target does't have an Info.plist, follow these steps to add:
- In your Test Target Folder, create a Property List named
Info.plist. - In Test Target Settings, configure 'Info.plist File' with the path
TestTargetFolderName/Info.plist.
Now, you can specify the Report Portal properties:
- ReportPortalURL - Base URL of your ReportPortal instance (example: https://report-portal.company.com). The agent automatically appends
/api/v2/{project}to construct the full API URL. - ReportPortalToken - token for authentication which you can get from RP account settings.
- ReportPortalLaunchName - name of launch.
- Principal class - use
ReportPortalAgent.RPListenerfrom ReportPortalAgent lib for SPM orReportPortal.RPListenerfor CocoaPods. You can also specify your own Observer which should conform to XCTestObservation protocol. - PushTestDataToReportPortal - can be used to switch off/on reporting
- ReportPortalProjectName - project name from Report Portal
- ReportPortalTags(optional) - can be used to specify tags, separated by comma.
To include test plan names in ReportPortal, add the TEST_PLAN_NAME environment variable to your .xctestplan file manually:
{
"defaultOptions": {
"environmentVariableEntries": [
{
"key": "TEST_PLAN_NAME",
"value": "Example Test Plan"
}
]
}
}Result: Launch names will appear as YourLaunchName: Example_Test_Plan in ReportPortal.
Note: Spaces in test plan names are automatically replaced with underscores for better compatibility.
CI Override: CI can override this value: TEST_PLAN_NAME="Nightly Tests" xcodebuild test ...
Starting with v4.0, the agent fully supports parallel test execution, allowing you to dramatically reduce CI/CD pipeline times. Tests can be executed across multiple simulator instances simultaneously while maintaining proper test hierarchy and reporting in ReportPortal.
- iOS 15.0+ / macOS 14.0+ (required for Swift Concurrency)
- Swift 5.5+
- Xcode 13+
Parallel execution is controlled via xcodebuild command-line arguments. No .xctestplan modifications are required.
Option A: Single Device Type (Multiple Clones)
This approach clones the same simulator multiple times. Best for consistent test environments:
xcodebuild test \
-scheme YourScheme \
-testPlan YourTestPlan \
-destination 'platform=iOS Simulator,name=iPhone 16' \
-parallel-testing-enabled YES \
-maximum-parallel-testing-workers 4This will create 4 simulator clones: iPhone 16 - Clone 1, iPhone 16 - Clone 2, etc.
Option B: Multiple Device Types (Explicit Devices)
Run tests across different device models simultaneously. Great for device coverage:
xcodebuild test \
-scheme YourScheme \
-testPlan YourTestPlan \
-destination 'platform=iOS Simulator,name=iPhone 16' \
-destination 'platform=iOS Simulator,name=iPhone 15 Pro' \
-destination 'platform=iOS Simulator,name=iPhone 15' \
-destination 'platform=iOS Simulator,name=iPhone 14' \
-parallel-testing-enabled YESOption C: GitHub Actions Example
- name: Run Tests in Parallel
run: |
xcodebuild test \
-scheme MyApp \
-testPlan MyTestPlan \
-parallel-testing-enabled YES \
-maximum-parallel-testing-workers 2 \
-resultBundlePath TestResults.xcresultOption D: Bitrise Example
- xcode-test@4:
inputs:
- scheme: MyApp
- test_plan: MyTestPlan
- simulator_device: iPhone 16By default, parallel execution creates separate launches per worker (e.g., 4 workers = 4 launches in ReportPortal). In local development, this is acceptable (you can manually merge launches in ReportPortal UI).
For CI/CD pipelines, you can configure all workers to report to a single shared launch using the RP_LAUNCH_UUID environment variable:
# Generate UUID and run tests with shared launch
export RP_LAUNCH_UUID=$(uuidgen)
xcodebuild test \
-scheme YourScheme \
-testPlan YourTestPlan \
-destination 'platform=iOS Simulator,name=iPhone 16' \
-parallel-testing-enabled YES \
-maximum-parallel-testing-workers 4How it works:
export RP_LAUNCH_UUID=$(uuidgen)sets a shared UUID before running tests- All parallel workers read the same UUID from environment
- First worker creates the launch in ReportPortal
- Other workers join the existing launch (409 Conflict handled automatically)
- Result: Single launch in ReportPortal containing all test results
Why NOT use build phase scripts for UUID generation:
Build phase scripts only run when source files change (Xcode incremental build). If you re-run tests without code changes, the UUID stays stale and workers join the previous test run's launch, causing data corruption.
# ❌ DON'T DO THIS - Build phases don't run on every test execution!
# Build Phase → Run Script:
UUID=$(uuidgen)
/usr/libexec/PlistBuddy -c "Set :RP_LAUNCH_UUID $UUID" Info.plistTimeline showing the problem:
10:00 - Run tests → Build runs → UUID-AAA generated → Launch created ✅
10:05 - Re-run tests → NO BUILD → Still UUID-AAA → Joins old launch ❌
10:10 - Re-run tests → NO BUILD → Still UUID-AAA → ERROR: Launch already finalized ❌
10:15 - Change code → Build runs → UUID-BBB generated → New launch ✅
Solution: Use environment variables set by CI/CD pipeline (always fresh, always unique per test run).
In local development (Xcode IDE), each parallel worker creates a separate launch:
ReportPortal Dashboard:
├── MyApp Tests - iPhone 16 Clone 1 (Worker 1)
├── MyApp Tests - iPhone 16 Clone 2 (Worker 2)
├── MyApp Tests - iPhone 16 Clone 3 (Worker 3)
└── MyApp Tests - iPhone 16 Clone 4 (Worker 4)
To run with a single shared launch locally (via script):
#!/bin/bash
# run_tests_shared_launch.sh
# Generate UUID once for this test run
export RP_LAUNCH_UUID=$(uuidgen)
echo "Running tests with shared launch UUID: $RP_LAUNCH_UUID"
xcodebuild test \
-scheme MyApp \
-testPlan MyTestPlan \
-destination 'platform=iOS Simulator,name=iPhone 16' \
-parallel-testing-enabled YES \
-maximum-parallel-testing-workers 4To merge launches manually (when running from Xcode IDE):
- Go to ReportPortal → Launches
- Select your launches
- Click "Merge" → Enter merged launch name
- All test results combined into single launch
Why environment variables from Xcode don't work:
Environment variables set in Xcode Scheme → Pre-Actions don't propagate to parallel workers (they run in isolated processes). The script approach above works because export sets the variable in the shell session before launching xcodebuild.
Choose worker count based on your CI/CD environment:
| Environment | Recommended Workers | Reasoning |
|---|---|---|
| Local Development (8+ cores) | 4 | Balanced performance without overloading machine |
| Local Development (4-6 cores) | 2-3 | Prevents resource contention |
| GitHub Actions | 2 | Limited CI resources (7GB RAM, 2 cores) |
| Bitrise | 3-4 | Better resource availability |
| Jenkins (self-hosted) | CPU count / 2 | Scale with available hardware |
| GitLab CI | 2-3 | Standard runner specs |
When tests run in parallel, you should see output like:
Testing started on 'iPhone 16 - Clone 1'
Testing started on 'iPhone 16 - Clone 2'
Testing started on 'iPhone 16 - Clone 3'
Testing started on 'iPhone 16 - Clone 4'
In ReportPortal, all test results will appear under a single launch with proper test hierarchy maintained.
Example: ExampleUITests
| Configuration | Execution Time | Improvement |
|---|---|---|
| Sequential (v3.x) | ~40 minutes | Baseline |
| Sequential (v4.x) | ~30 minutes | 25% faster |
| Parallel - 2 workers | ~15 minutes | 160% faster |
| Parallel - 3 workers | ~10 minutes | 300% faster |
@rusel95, ruslanpopesku95@gmail.com
ReportPortal Team, support@reportportal.io
@DarthRumata, stas.kirichok@windmill.ch (Windmill Smart Solutions)
@SergeVKom, sergvkom@gmail.com (original library)
Licensed under the Apache 2.0 license (see the LICENSE file).


