Parallel tests with NUnit, each test case executed only once

Let's say we have a TestFixture class containing 3 Test/TestCase methods, which we want to execute in parallel on 3 devices. With the approach described in Parallel test execution in Visual Studio using NUnit, each Test/TestCase method would run once per device, on each device, as shown in the following table:

device # 1

test # 1

test # 2

test # 3

device # 2

test # 1

test # 2

test # 3

device # 3

test # 1

test # 2

test # 3

If we want each Test/TestCase method in the TestFixture class to run only once in general and on only one of the devices available for parallel execution, as shown in the following table, it would not work to include the driver setup in the TestFixture class (as in the example included in Parallel test execution in Visual Studio using NUnit) because a single instance of the TestFixture class would be used for all Test/TestCase methods it contains. Therefore, it would not be possible to have the driver sessions for each Test/TestCase method run in separate threads.

device # 1

test # 1

device # 2

test # 2

device # 3

test # 3

To overcome this issue, we would need to create a separate Test Base class where we define the driver and create/destroy the driver session. Then, in each Test/TestCase method in the Parallelizable TestFixture class, you would create and use a separate instance of that Test Base class.

Following is a simple example of such a Test Base class, with Reportium included, followed by a simple example of a TestFixture class with two Test methods that would run in parallel.

The level of parallelism here is Parallelizable(ParallelScope.All).

Copy

Example of a simple Test Base class

using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Appium.Android;
using OpenQA.Selenium.Remote;
using Reportium.client;
using Reportium.model;
using OpenQA.Selenium.Appium;
using Reportium.test.Result;

namespace MobileTest_NUnit
{
    class TestSetup
    {
        private AppiumDriver<IWebElement> driver;
        private ReportiumClient reportiumClient;

        //replace <your_security_token> with your security token for the cloud
        //see ../Perfecto/automation-testing/generate_security_tokens.htm
        private const String securityToken = "<your_security_token>";
        
        //replace <your_cloud_name> with the name of your cloud
        private const String cloudName = "<your_cloud_name>";
        
        //create the driver session with the Constructor of the TestSetup class
        public TestSetup(String platformName, String platformVersion)
        {
            DesiredCapabilities capabilities = new DesiredCapabilities();

            //run the tests on devices of the specified platform
            capabilities.SetCapability("platformName", platformName);
            capabilities.SetCapability("platformVersion", platformVersion);

            //authenticate to the Perfecto cloud 
            capabilities.SetCapability("securityToken", securityToken);

            //create driver session
            var url = new Uri(string.Format("https://" + cloudName + ".perfectomobile.com/nexperience/perfectomobile/wd/hub"));
            driver = new AndroidDriver<IWebElement>(url, capabilities);
            //driver = new IOSDriver<IWebElement>(url, capabilities);

            // Reporting client. For more details, see ../Perfecto/test-analysis/test_analysis_with_smart_reporting.htm
            PerfectoExecutionContext perfectoExecutionContext = new PerfectoExecutionContext.PerfectoExecutionContextBuilder()
                    .withProject(new Project("My Project", "1.0"))
                    .withContextTags(new[] { "tag1" })
                    .withWebDriver(driver)
                    .build();
            reportiumClient = PerfectoClientFactory.createPerfectoReportiumClient(perfectoExecutionContext);
        }

        public AppiumDriver<IWebElement> GetDriver()
        {
            return driver;
        }
        
        public void Start(String name, Reportium.test.TestContextTags contextTags)
        {
            reportiumClient.testStart(name, contextTags);
        }

        public void ReportSuccess()
        {
            reportiumClient.testStop(TestResultFactory.createSuccess());
        }

        public void ReportFailure(Exception e)
        {
            reportiumClient.testStop(TestResultFactory.createFailure(e.Message, e));
        }

        public void Stop()
        {
            driver.Close();
            driver.Quit();

            // Retrieve the URL of the Single Test Report and print it to the Console
            String reportURL = reportiumClient.getReportUrl();
            Console.WriteLine(reportURL);

            // For documentation on how to export reporting PDF, see https://github.com/perfectocode/samples/wiki/reporting
            //String reportPdfUrl = (String)(driver.Capabilities.GetCapability("reportPdfUrl"));

            // For detailed documentation on how to export the Execution Summary PDF Report, the Single Test report and other attachments such as
            // video, images, device logs, vitals and network files - see ../Perfecto/test-analysis/export_reports.htm
        }
    }
}
Copy

Example of a simple TestFixture class

using System;
using System.Collections.Generic;
using Reportium.test;
using NUnit.Framework;

namespace MobileTest_NUnit
{

    //run the tests in parallel on Android 8 devices; each test would run only once
    [TestFixture("Android", "8.0.0")]
    [Parallelizable(ParallelScope.All)]
    public class AppiumTest
    {
        private String platformName, platformVersion;

        public AppiumTest(String platformName, String platformVersion)
        {
            this.platformName = platformName;
            this.platformVersion = platformVersion;
        }

        //sample test to open the Android Settings app and find the 'Connections' element (Native)
        [Test]
        public void SettingsTest()
        {
            var test = new TestSetup(this.platformName, this.platformVersion);
            test.Start("SettingsTest", new TestContextTags("NUnit", "Parallel"));

            try
            {
                Dictionary<String, Object> pars = new Dictionary<String, Object>();
                pars.Add("name", "Settings");
                test.GetDriver().ExecuteScript("mobile:application:open", pars);

                test.GetDriver().Context = "NATIVE_APP";

                test.GetDriver().FindElementByXPath("//*[@text='Connections']");

                test.ReportSuccess();
            }
            catch (Exception exception)
            {
                test.ReportFailure(exception);
            }

            test.Stop();
        }

        //sample test to open the browser, navigate to Perfecto web page, and find the brand image (Web)
        [Test]
        public void PerfectoIOTest()
        {
            var test = new TestSetup(this.platformName, this.platformVersion);
            test.Start("PerfectoIOTest", new TestContextTags("NUnit", "Parallel"));

            try
            {
                test.GetDriver().Navigate().GoToUrl("https://www.perfecto.io");

                test.GetDriver().Context = "WEBVIEW";

                test.GetDriver().FindElementById("block-perfecto-branding");

                test.ReportSuccess();
            }
            catch (Exception exception)
            {
                test.ReportFailure(exception);
            }

            test.Stop();
        }
    }
}