Xamarin.UITest reset app state before running tests

Jonathan_MoosekianJonathan_Moosekian Jonathan MoosekianUSUniversity ✭✭

Does anyone have any suggestions as to how I could reset contents and settings on the iOS simulator as a prerequisite to executing tests?

Best Answer

Answers

  • TomOpgenorthTomOpgenorth Tom Opgenorth CAXamarin Team Xamurai

    Cool - thanks a bunch for figuring this out and sharing it!

  • MichaelLeatherburyMichaelLeatherbury Michael Leatherbury USMember ✭✭

    @Jonathan_Moosekian‌ Thanks for sharing! quick question. How are you getting the deviceID? since each instance is unique and as far as I know, never the same twice.

  • Jonathan_MoosekianJonathan_Moosekian Jonathan Moosekian USUniversity ✭✭

    @MichaelLeatherbury‌, you can get a list of simulators/devices by running:

    xcrun simctl list

    or

    instruments -s

    My goal is to actually automate this process and grab a list of available simulators, and execute the tests against each one, but I haven't gotten to that point yet.

    I found this answer on SO that enumerated the available options on the simctl command: http://stackoverflow.com/a/26394597/224963

  • TomOpgenorthTomOpgenorth Tom Opgenorth CAXamarin Team Xamurai

    I believe the device ID only changes from machine to machine? I'm not 100% sure on that. I've been playing with an extension method that will lookup the device ID for you. I was saving this for a recipe, but this seems like a appropriate thread. This helper method allows me to do this:

    _app = ConfigureApp.iOS.SetDeviceByName("iPhone 5s (7.1 Simulator)").AppBundle(pathToIPA);
    

    So, without explanation, here is the code behind the scenes:

    public static class TestHelpers
    {
    
        public static iOSAppConfigurator SetDeviceByName(this iOSAppConfigurator configurator, string simulatorName)
        {
            var deviceId = GetDeviceID(simulatorName);
            return configurator.DeviceIdentifier(deviceId);
        }
    
        static string GetDeviceID(string simulatorName)
        {
            if (!TestEnvironment.Platform.Equals(TestPlatform.Local))
            {
                return string.Empty;
            }
    
            // See below for the InstrumentsRunner class.
            IEnumerable<Simulator> simulators = new InstrumentsRunner().GetListOfSimulators();
    
            var simulator = (from sim in simulators
                                      where sim.Name.Equals(simulatorName)
                                      select sim).FirstOrDefault();
    
            if (simulator == null)
            {
                throw new ArgumentException("Could not find a device identifier for '" + simulatorName + "'.", "simulatorName");
            }
            else
            {
                return simulator.GUID;
            }
        }
    }
    

    The InstrumentsRunner class will execute xcrun instruments -s and parse the command line output into a bunch of Simulator objects. A quick LINQ query, and we can get the UUID associated with a given simulator name. Here is the code for InstrumentRunner, chic:

    class InstrumentsRunner
    {
        static string[] GetInstrumentsOutput()
        {
            const string cmd = "/usr/bin/xcrun";
    
            var startInfo = new ProcessStartInfo
            {
                FileName = cmd,
                Arguments = "instruments -s devices",
                RedirectStandardOutput = true,
                UseShellExecute = false
            };
    
            Process proc = new Process();
            proc.StartInfo = startInfo;
            proc.Start();
            var result = proc.StandardOutput.ReadToEnd();
            proc.WaitForExit();
    
            var lines = result.Split('\n');
            return lines;
        }
    
        public Simulator[] GetListOfSimulators()
        {
            var simulators = new List<Simulator>();
            var lines = GetInstrumentsOutput();
    
            foreach (var line in lines)
            {
                var sim = new Simulator(line);
                if (sim.IsValid())
                {
                    simulators.Add(sim);
                }
            }
    
            return simulators.ToArray();
        }
    }
    

    Here is the code for the Simulator class. It takes a single line of text and then tries to pick out the Name and the UUID:

    class Simulator
    {
        public Simulator(string line)
        {
            ParseLine(line);
        }
    
        public string Line { get; private set; }
    
        public string GUID { get; private set; }
    
        public string Name { get; private set; }
    
        public bool IsValid()
        {
            return !string.IsNullOrWhiteSpace(GUID) && !(string.IsNullOrWhiteSpace(Name));
        }
    
        public override string ToString()
        {
            return Line;
        }
    
        void ParseLine(string line)
        {
    
            GUID = string.Empty;
            Name = string.Empty;
            Line = string.Empty;
    
            if (string.IsNullOrWhiteSpace(line))
            {
                return;
            }
            Line = line.Trim();
            var idx1 = line.IndexOf(" [");
            if (idx1 < 1)
            {
                return;
            }
    
            Name = Line.Substring(0, idx1).Trim();
            GUID = Line.Substring(idx1 + 2, 36).Trim();
        }
    }
    
  • Jonathan_MoosekianJonathan_Moosekian Jonathan Moosekian USUniversity ✭✭

    Thanks for the contribution to this, @TomOpgenorth‌!

    You're correct, the simulator device IDs are different on different machines.

  • Jonathan_MoosekianJonathan_Moosekian Jonathan Moosekian USUniversity ✭✭

    Just realized my solution wasn't actually resetting the device if it was open. I had to add another command prior to the erase command:

    var process = Process.Start("xcrun", "simctl shutdown 30980165-5693-45AA-8CDB-953D9256432D"); process.WaitForExit();

  • Jonathan_MoosekianJonathan_Moosekian Jonathan Moosekian USUniversity ✭✭

    This exercise has proven very useful for targeting several different versions of simulator in a test fixture. Here's how I set up my test fixture to execute per device name:

    [TestFixture("iPhone 4s (7.1 Simulator)")] [TestFixture("iPhone 4s (8.1 Simulator)")] [TestFixture("iPhone 5 (8.1 Simulator)")] [TestFixture("iPhone 5s (7.1 Simulator)")] [TestFixture("iPhone 6 (8.1 Simulator)")] [TestFixture("iPhone 6 Plus (8.1 Simulator)")] public class LoginTests : TestBase { ... }
    Then in my TestBase:

    public TestBase(string deviceName)
    {
        this.deviceName = deviceName;
    }
    
    [SetUp]
    void Setup()
    {
        var deviceId = TestHelpers.GetDeviceID(deviceName);
    
        if (string.IsNullOrEmpty(deviceId))
        {
            Assert.Fail("No simulator found with device name [{0}]", deviceName);
        }
    
        ResetSimulator(deviceId);
    
            ....
    
        _app = ConfigureApp.iOS.AppBundle(PathToIPA).DeviceIdentifier(deviceId).StartApp();
    }
    
    static void ResetSimulator(string deviceId)
    {
        var shutdownProcess = Process.Start("xcrun", string.Format("simctl shutdown {0}", deviceId));
        shutdownProcess.WaitForExit();
        var eraseProcess = Process.Start("xcrun", string.Format("simctl erase {0}", deviceId));
        eraseProcess.WaitForExit();
    }
    

    Thanks to @TomOpgenorth‌ for the direction of the Extension method helpers.

  • AlexeyShikovAlexeyShikov Alexey Shikov UAMember ✭✭

    For those who recently experienced an issue when the list of simulators is empty.

    Specify StandardOutputEncoding when preparing ProcessStartInfo.

    So that the code would look like this:

        var startInfo = new ProcessStartInfo
        {
            FileName = cmd,
            Arguments = "instruments -s devices",
            RedirectStandardOutput = true,
            UseShellExecute = false,
            StandardOutputEncoding = Encoding.UTF8 // This is the fix!
        };
    
  • dikogadikoga Diego Koga USMember

    Guys,

    Just to let others know!

    I was trying to do it work and I found the following option:

    _app = ConfigureApp.iOS.StartApp (Xamarin.UITest.Configuration.AppDataMode.Clear);

    Probably is the new way to clean App Data but not much spread yet.

  • JasonStarnesJasonStarnes Jason Starnes USMember

    Is there a way to only reset settings for the app being tested? I'm testing on a physical device, and I assume if I did a full erase each time it would wipe out WiFi login information, etc.

  • Neil-PepperNeil-Pepper Neil Pepper GBUniversity ✭✭

    So following tips on this thread have been trying to clear data from app installed on device not having much luck.

    Ideally would like to uninstall app, but mtouch doesn't seem to have a mtouch --removedev to compliment the mtouch --installdev

    Have tried ConfigureApp.iOS.StartApp (Xamarin.UITest.Configuration.AppDataMode.Clear) too but that doesn't seem to work on device at all it just launches the app.

    Any ideas ?

    thanks

  • Neil-PepperNeil-Pepper Neil Pepper GBUniversity ✭✭

    Found this great utility has loads of helpful features and its been updated for iOS9

    https://github.com/phonegap/ios-deploy

Sign In or Register to comment.