Delayed start of the App with Arguments

Hi,

I have a .NET MAUI App which should actually be launched after I provided a a TestDatabase File via PushFile(…) so that it can be used in the iOS-Simulator and App instance that is created for the test.

When I use the following method, the App is launched instantly when the new IOSDriver(…) instance is created:

var options = new AppiumOptions
{
    PlatformName = "iOS",
    PlatformVersion = "18.2",           // Specify the iOS version of the simulator or device
    DeviceName = "iPhone 16 Pro Max",   // Specify the device name
    AutomationName = "XCUITest",
    App = "/Users/mauioxo/Library/Caches/Xamarin/mtbs/builds/MyApp/81ca9a5b7d14b275e14ee3337e9324be32a9316344637117fb3208acc66ff091/bin/Debug/net9.0-ios/iossimulator-arm64/MyApp.app"
};

// options.AddAdditionalAppiumOption("autoLaunch", false);     // Don't start the App
                                                                                      
var processArgs = new Dictionary<string, object>
{
    { "args", new[] { "-isTestExecution", "true" } }
};
options.AddAdditionalAppiumOption("processArguments", processArgs);

driver = new IOSDriver(_appiumService.ServiceUrl, options);

In my AppDelegate.cs (used for iOS platform code), I can see the provided Argument -isTestExecution and also its value true:

private void SetTestExecutionState()
{
    // Retrieve the command-line arguments
    var args = NSProcessInfo.ProcessInfo.Arguments;            

    bool isTestExecution = false;

    // Check if the argument "-isTestExecution" exists and if
    // the argument is found check if a subsequent value exists
    int index = Array.IndexOf(args, "-isTestExecution");
    if (index >= 0 && index + 1 < args.Length)
    {
        bool.TryParse(args[index + 1], out isTestExecution);
    }


    // Obtain the AppExecutionState service and update its IsTestExecution property
    var appExecutionState = ServiceHelper.GetService<AppExecutionState>();
    if (appExecutionState != null)
    {
        appExecutionState.IsTestExecution = isTestExecution;
    }
}

Problem with this code is, that I am not able to use PushFile(...) and access the IsTestExecution flag when my App is launched.


Therfore, I tried another approach and used the following method which sets autoLaunch to false:

var options = new AppiumOptions
{
    PlatformName = "iOS",
    PlatformVersion = "18.2",           // Specify the iOS version of the simulator or device
    DeviceName = "iPhone 16 Pro Max",   // Specify the device name
    AutomationName = "XCUITest",
    App = "/Users/mauioxo/Library/Caches/Xamarin/mtbs/builds/MyApp/81ca9a5b7d14b275e14ee3337e9324be32a9316344637117fb3208acc66ff091/bin/Debug/net9.0-ios/iossimulator-arm64/MyApp.app"
};

options.AddAdditionalAppiumOption("autoLaunch", false);     // Don't start the App
                                                                                      
driver = new IOSDriver(_appiumService.ServiceUrl, options);


DeployTestDatabaseToEmulator();


var launchAppArgs = new Dictionary<string, object>
{
    { "bundleId", "com.myapp.app" },
    { "args", new[] { "-isTestExecution", "true" } }
};
driver.ExecuteScript("mobile: launchApp", launchAppArgs);

The App seems to be launched after I executed my method DeployTestDatabaseToEmulator().


But obviously, in my AppDelegate.cs method, I can’t find the argument -isTestExecution and true anymore.

I also tried using arguments instead of args, but then the driver.ExecuteScript(...) call crashes.

Is there a way to get my database file pushed to the iOS Emulator and still being able to access the launchApp arguments?

Why not get -isTestExecution directly from diver during test?

How can I do this?

Currently, I don’t understand correctly what you mean and how this has to be done:

  • The idea was, that my Test provides the TestDatabase to the App somehow: PushFile(…)
  • The App must decide which Database to take, the production database or the TestDatabase
  • Therefore, the App needs a Flag like -isTestExecution

On Android and with Android simulator I could achieve this and after creating the Driver instance, I could do the PushFile(…) and it worked switching between TestDatabase and Production Database. There, I detected the Flag in MainActivity.cs (for Android) in my .NET MAUI App

The same did not work on iOS. After creating the IOSDriver instance the App already started and did not get my TestDatabase obviously, maybe too late. With the way, I described above, I could get it copied before the App is started/displayed in the simulator, but it still was not able to load the TestDatabase and detect the Flag right away

Just to make clear - you need deploy DB before app starts? Restart app later after deploy DB is not a way for you?

The database is used in the App from the beginning when it starts. There, I wanted to make the decision in production code which database to use (test database or production database).

So, in the test restarting the App can be done later. But, what I saw is that iOS Simulator changes App-UDIDs or whatever they call it (the IDs right before my …./Library folder when looking on the AppDataDirectory folder).

Therefore, from the perspective of my test, the TestDatabase must be copied to the right folder so that the running App can use it.

There are 2 challenges for me:

  • first, copying the database to this folder before the App is actually executed
  • second, identifying in the App (e.g. via Flag) that we are in an Appium Test execution and selecting the right database already during startup of the App

So, restart App later after DB deploy was what I tried to do, but couldn’t get the Flag in the App anymore or when I got it, it seemed like the DB could not be switched to the Test Database.

mine code in Java

final List<String> processArgs = new ArrayList<>(Arrays.asList(arguments.split(",")));
Map<String, Object> params = new HashMap<>();
params.put("bundleId", appID);
params.put("arguments", processArgs);
driver.executeScript("mobile: launchApp", params);

not sure but mine arguments vs yours args

I also tried that, but I think something is screwed with the copying maybe?

When I use the following code with args or with arguments it sometimes crashes if I don’t use clearApp which does not sound good.

var options = new AppiumOptions
{
 PlatformName = "iOS",
 PlatformVersion = "18.2",           // Specify the iOS version of the simulator or device
 DeviceName = "iPhone 16 Pro Max",   // Specify the device name
 AutomationName = "XCUITest",
 App = "/Users/mauioxo/Library/Caches/Xamarin/mtbs/builds/MyApp/81ca9a5b7d14b275e14ee3337e9324be32a9316344637117fb3208acc66ff091/bin/Debug/net9.0-ios/iossimulator-arm64/MyApp.app"
};

options.AddAdditionalAppiumOption("autoLaunch", false);     // Don't start the App

driver = new IOSDriver(_appiumService.ServiceUrl, options);

DeployTestDatabaseToEmulator();


//var launchAppArgs = new Dictionary<string, object>
//{
//    { "bundleId", "com.myapp.app" }
//};
//driver.ExecuteScript("mobile: clearApp", launchAppArgs);


// App mit spezifischen Prozessargumenten starten
var launchAppArgs = new Dictionary<string, object>
{
 { "bundleId", "com.myapp.app" },
 { "arguments", "-isTestExecution=true" }
};

driver.ExecuteScript("mobile: launchApp", launchAppArgs);

Then I also hardcoded my isTestExecution flag to true in my production code just to see how it behaves and it was strange as it had the right TestDatabase path, but it did not load it!

I don’t know if it can work with the delayed mobile: launchApp on iOS Simulator or how I should do it.

I looked into the corresponding folder on my Mac which is used and compared it to my AppDataDirectory from my production code just be sure I am in the right directory. I can also see my TestDatabase in the right folder that is used when I switch to isTestExecution=true in my code. But, the database is not loaded in the App.

Is it too late after driver = new IOSDriver(_appiumService.ServiceUrl, options); to copy the TestDatabase to the corresponding folder and using this routine?

/// <summary>
/// Transfers the test database file to the specified directory on the iOS Simulator.
/// </summary>
private static void DeployTestDatabaseToEmulator()
{
    string localFilePath = Path.Combine("TestDatabase", "MyDataBase.db3");
    byte[] data = File.ReadAllBytes(localFilePath);
    string base64Data = Convert.ToBase64String(data);

    driver?.PushFile("@com.myapp.app:data/Library/TestDatabase/MyDataBase.db3", base64Data);
}

@Aleksei I don’t know if you have any ideas or explanations, but the behavior feels strange to me:

I tried different things and while I was experimenting, I had a situation in which, I thought, let’s also try without it with arguments and without "-" (the dash):

var launchAppArgs = new Dictionary<string, object>
{
    { "bundleId", "com.myapp.app" },
    { "arguments", new List<string> { "isTestExecution", "true" } }
};
driver.ExecuteScript("mobile: launchApp", launchAppArgs);

Surprisingly, I could see my Alert-Window in the App under test showing me that the TestExecution was true.

But, In the App, I actually still filtered the var args = NSProcessInfo.ProcessInfo.Arguments; for -isTestExecution including a dash. Therefor, during this “successful” run, I could not see that my TestDatabase was used in the test.

Okay, I changed my App to filter for isTestExecution and deployed it again to iOS Simulator. But, then everytime I try to launch the App, it crashed while it was trying to start.

Then, I tried again using -isTestExecution as well as isTestExecution in the launch arguments, but the App could not be launched successfully again in the Test.

One time, I had it and I switched from using -isTestExecution" to isTestExecution` and back and it launched the App just from switching from it including the dash to not including the dash and vice versa.

But now, none of these two possibilities works and the App just does not launch.

I also tried this after mobile:launchApp or before when I did a new deployment of the App in the iOS Simulator, but nothing helped:

// App mit spezifischen Prozessargumenten starten
var terminateAppArgs = new Dictionary<string, object>
{
    { "bundleId", "com.myapp.app" }
};
driver.ExecuteScript("mobile:terminateApp", terminateAppArgs);

Any explanation and things I could try?

this looks more correct for me. I use similar:

                    String arguments = "-featureFlag.XY: Disable leak alerts,YES,-featureFlag.XY: Zendesk,NO,-featureFlag.XY: Disable custom accessibility,YES,GENERATE_IDENTIFIERS,YES,...";
                    final List<String> processArgs = new ArrayList<>(Arrays.asList(arguments.split(",")));
                    Map<String, Object> params = new HashMap<>();
                    params.put("bundleId", appID);
                    params.put("arguments", processArgs);

Other crash or filtering flags in app is pure your app issues (I believe).

@Aleksei Well, I am currently not sure if the problem is really inside the App.

I just changed the test (not the App) with the slight differences on how you did it:

string arguments = "-isTestExecution: true";

// Split the arguments string into a list
List<string> processArgs = new List<string>(arguments.Split(','));

// Create a dictionary for parameters
Dictionary<string, object> parameters = new Dictionary<string, object>
{
    { "bundleId", "com.myapp.app" },
    { "arguments", processArgs }
};

// Execute script to launch the app
driver.ExecuteScript("mobile: launchApp", parameters);

Then, I just started my Test and surprisingly I could see the output in the output window of my App and it also listedthe whole argument -isTestExecution: true.
I closed the test and started the test again and it showed it again.

When I adapted the parsing of arguments in my app accordingly so that it recognizes the entire string -isTestExecution: true and started the test again, the App just closed during driver.ExecuteScript("mobile: launchApp", parameters);.

Then, I removed argument parsing again and surprisingly, it still crashed during driver.ExecuteScript("mobile: launchApp", parameters);.

It felt like Appium is cashing something or recognizes when I change something at my parameters, but after that it is up in the air if the App can be launched.

I still was struggeling with the syntax, as in the Appium docu für mobile:launchApp it showd an example with ['-s',' -m']:

https://appium.github.io/appium-xcuitest-driver/4.16/execute-methods/#mobile-launchapp

I also just tried with a simple parameter -t and it also did no launch the App anymore when I added parsing for that.

Right now, it really looks more like a problem with Appium or Driver or so.

On the App side the parsing was done like

protected override MauiApp CreateMauiApp()
{
    var args = NSProcessInfo.ProcessInfo.Arguments;

    if (args.Contains("-t") || args.Contains("isTestExecution") || args.Contains("-isTestExecution") || args.Contains("-isTestExecution: true"))
    {
        IsTestExecution = true;
    }

    return MauiProgram.CreateMauiApp();
}

I don’t know if there can be things done wrong. When I start the App outside the Appium test it just runs fine.

Are there other things I could change like providing more memory to the test or other things?

I also have a method which allows me to use a TestDatabase, but only when I deploy it with my App as an MauiAsset in my Resources\Raw\... folder. But, I really do not like to deploy a TestDatabase with the production app. This really is bad and should be avoided in my opinion.

Funny thing is on Android side it works good when I provide my TestDatabase with driver?.PushFIle(...). But, on iOS I face these strange problems

Crash is always app issue regardless what you send or doing with Appium commands.

With “crash” I do not mean an Exception like crash, it’s more like the App can not be opened in the iOS Simulator. It tries, but after 1-2 seconds closed and when I move forward in Debugger it is on the next line.

For me in the Code I showed you there is not rational explanation how it would just crash and sometimes get it done and show all the arguments.

Is there a way how I could debug my App code from the running Appium test, so that I can see what is going on there?

check Capabilities - Appium XCUITest Driver
enable something like showXcodeLog, simulatorLogLevel, showIOSLog.