Fully Automating iOS Automation Tests using Gauntlet on Horde

Hi,

I’m using Gauntlet to run automation tests using an iOS game build.

I’d like to be able to run iOS tests from Horde in a completely automated manner, similar to how Android works.

I’ve been able to get the following command to successfully run automation tests on an iPhone, however it’s not able to run properly without user input:

./RunUAT.sh RunUnreal -project=<project> -Platform=iOS -Build=./Projects/<project>/Binaries -Configuration=Test -Test="UE.TargetAutomation" -RunTest="StartsWith:System.Core.Math" -HostIP=$(ipconfig getifaddr en0)This works locally but needs interaction with the iPhone:

  1. There’s a permission popup that requires me to press on the [Allow] button: “<project>” would like to find and connect to devices on your local network
  2. The iOS Client is unable to initially connect to the Editor running on the Mac.
    1. I can get the iOS Client to connect by pressing the home button on the iPhone, waiting 10 seconds, then pressing the <project> icon to bring it back to the foreground, where it retries and successfully creates a connection. The Editor will start communicating with the Client, gathering the list of available tests and running them one by one.

I’m wondering if you’ve hit either of these issues, and if you have, do you have any workarounds or fixes for them?

Does Epic Games have a fully automated process of iOS testing, for example using Horde?

I added a Verbose LogSockets message to help gather the connection error code (used by the following section)

bool FSocketBSD::Connect(const FInternetAddr& Addr)
{
    if (Addr.GetProtocolType() != GetProtocol())
    {
       UE_LOG(LogSockets, Warning, TEXT("Tried to connect with an address with protocol %s using a socket with protocol %s"), 
          *Addr.GetProtocolType().ToString(), *GetProtocol().ToString());
       return false;
    }
 
    const FInternetAddrBSD& BSDAddr = static_cast<const FInternetAddrBSD&>(Addr);
    int32 Return = connect(Socket, (const sockaddr*)&(BSDAddr.Addr), BSDAddr.GetStorageSize());
    
    check(SocketSubsystem);
    ESocketErrors Error = SocketSubsystem->TranslateErrorCode(Return);
 
    UE_LOG(LogSockets, Verbose, TEXT("Connect attempt - Return Code: %i Error Code: %i"), 
       Return, static_cast<int>(Error));
 
    // EWOULDBLOCK is not an error, and EINPROGRESS is fine on initial connection as it may still be creating for nonblocking sockets
    return ((Error == SE_NO_ERROR) || (Error == SE_EWOULDBLOCK) || (Error == SE_EINPROGRESS));
}

Steps to Reproduce

I’m using a MacBook Pro running Sequoia 15.6.1 connected via USB to an iPhone SE running iOS 17.6.1.

The Gauntlet tests are running via xcode 16.1

After clearing item 1, by allowing the app to connect to devices on the local network

Further details of item 2:

  • The Gauntlet test will fail without any further interaction. The Gauntlet logs repeatedly show the following messages:
LogAutomationCommandLine: Can't find any workers! Searching again
LogAutomationWorker: Received FindWorkersMessage from 9B4C79C7E14F19015B5580922024F11D
Client default: LogGauntlet: Display: GauntletHeartbeat: Idle 
Status: Completed:0, Running:1, Starting: 0, Waiting:0

It will then fail after reaching the timeout

--------------------------------------------------------------------------------
 # Gauntlet summary events:
 --------------------------------------------------------------------------------
 ### Errors:
 --------------------------------------------------------------------------------
 ###### Condition failed
 
 
 ###### Condition failed
 
 
 ###### Condition failed
 
 
 ###### Failed to find workers after 300.00 seconds. Giving up
 
 
 ###### Test Failure Encountered
 * Test encountered a failure. See above for more info.
 
 
 
UE.TargetAutomation(RunTest=StartsWith:System.Core.Math) (IOS Test Client) result=Failed
How to run locally: (RunUAT RunUnreal -Test="UE.TargetAutomation" -project="<project>" -Build="./Projects/<project>/Binaries" -Configuration="Test" -RunTest="StartsWith:System.Core.Math" -HostIP="10.0.0.29")
  
AutomationTool executed for 0h 6m 43s
AutomationTool exiting with ExitCode=152 (Error_TestFailure)
RunUAT ERROR: AutomationTool was unable to run successfully. Exited with code: 152

The client logs show it failing to connect. The client doesn’t re-attempt connection while still in the foreground.

[2025.09.26-08.10.18:664][ 0]LogTcpMessaging: Initializing TcpMessaging bridge for 1 outgoing connections
[2025.09.26-08.10.18:664][ 0]LogIOS: FSocketSubsystemIOS::InternalBSDSocketFactory
[2025.09.26-08.10.18:664][ 0]LogSockets: Verbose: Connect attempt - Return Code: -1 Error Code: 37
[2025.09.26-08.10.18:664][ 0]LogTcpMessaging: Error: Connect failed on outgoing socket for 10.0.0.29:6666
[2025.09.26-08.10.18:664][ 0]LogUdpMessaging: Display: Work queue size set to 1024.

Error Code: 37 indicates the connection failed due to SE_ENOBUFS (No buffer space available.)

  • Pressing the home button to background the app, waiting, then pressing the <project> icon to bring it back into the foreground

The client logs show the app being backgrounded and returning. It is then able to connect to the Editor, allowing the test to continue and run normally:

[2025.09.26-07.46.25:322][822]LogTemp: Display: Stopping TCPConsoleListener.
[2025.09.26-07.46.26:372][822]LogCore: Display: AppLifetime: Application will enter background
[2025.09.26-07.46.26:373][822]LogTcpMessaging: Initializing TcpMessaging bridge for 1 outgoing connections
[2025.09.26-07.46.26:374][822]LogIOS: FSocketSubsystemIOS::InternalBSDSocketFactory
[2025.09.26-07.46.26:374][822]LogSockets: Verbose: Connect attempt - Return Code: -1 Error Code: 8
[2025.09.26-07.46.26:375][822]LogCore: Display: AppLifetime: Application has reactivated
[2025.09.26-07.46.26:377][823]LogTemp: Display: Initializing TCPConsoleListener.
[2025.09.26-07.46.26:378][823]LogIOS: FSocketSubsystemIOS::InternalBSDSocketFactory
[2025.09.26-07.46.26:378][823]LogCore: Display: AppLifetime: Application has entered foreground
[2025.09.26-07.46.27:376][854]LogTcpMessaging: Started Connection to '10.0.0.29:6666'
[2025.09.26-07.46.27:376][854]LogTcpMessaging: Discovered node 'C39797A74F41AD57692FCE8456392EB0' on connection '10.0.0.29:6666'...
[2025.09.26-07.46.31:606][981]LogAutomationWorker: Received FindWorkersMessage from 6ADC85FDFF487813077FC9A473F258A7
[2025.09.26-07.46.31:672][983]LogAutomationWorker: Received RequestTestsMessage from 6ADC85FDFF487813077FC9A473F258A7
[2025.09.26-07.46.31:682][983]LogAutomationWorker: Set 647 tests to 6ADC85FDFF487813077FC9A473F258A7

Error Code: 8 indicates SE_EINPROGRESS

Hi David,

Apple has locked local networking down pretty tightly. We are experiencing the same issue internally and are working on an Appium based solution to automate the dismissal of this prompt and allow for continued full automation in test. Once this solution is available in mainline, I’ll follow up with CLs and further instructions if needed.

Best regards.

Thanks Stéphane,

Do you have a date (or UE release version) where you expect to have something working?

Hi David,

The change in question landed yesterday and will be part of the 5.7 rel ease.

The CL in question is 46490028 on //UE5/Main (https://github.com/EpicGames/UnrealEngine/commit/9fcf684b02dc2c6bc8cec88c26bdac4b66fdcf1c on GitHub).

Here is the docs comment from the source code:

AppiumContainer is a wrapper for both an Appium server instance and an AppiumDriver.

It’s primary use in Gauntlet is for automating dismissal of blocking system notifications that cannot be managed by an MDM profile.

Provided you configure your environment correctly, the container will automatically initialize as part of an IOS app instance execution.

Steps for configuring your environment:

1. Download appium on your host. It’s recommended you use the global npm installation ‘npm install -g appium’.

2. For real device testing, you will need a WebDriverAgentRunner.app which is signed with a mobile provision that includes your device.

For this you have two options:

- Build the app from source. This lets you configure bundle id’s if your signing cert only allows for certain identifiers.

- Download the precompiled app and re-sign after replacing the embedded mobileprovision.

In either case, you can find both the source and precompiled app on this page https://github.com/appium/WebDriverAgent/releases

3. Create a JSON file that can be de-serialized to the ‘AppiumContainer.Config’ type. This file is used to configure the driver with information

that is specific to your team. Place this file in a location that can be read by your host.

4. Point the container to the location of the json file you created in step 4 by doing one of the following:

- Setting the UE-AppiumConfigPath EnvVar to a qualified path or a relative path to your UE root.

- Run UAT with -AppiumConfigPath=/path set to a qualified path or a relative path to your UE root.

Once all these steps are completed, before TargetDeviceIOS.Run starts the app process, it will execute these actions:

1. Start an appium server on an available loopback port

2. Install the WebDriverAgent app

3. Start the driver with your configured settings

From there appium will automatically accept/dismiss any system prompts it encounters.

Best regards