Why won't my cross platform test automation fr

2019-08-23 12:55发布

问题:

I am currently rewriting the automated testing framework for my company's mobile testing. We are attempting to use an interface which is implemented by multiple Page Object Models dependent on the Operating System of the mobile device the application is being run on. I can get this framework to run sequentially and even create multiple threads but it will not run in parallel no matter what I do. Of Note, we use Appium and something called the DeviceCart/DeviceConnect which allows me to physically remote into multiple devices, thus this isn't running on a grid. With that said I will link my pertinent code (this is my second version of this same code, I wrote one with and one without using ThreadLocal)

This should instantiate a new driver with a new thread for each Test

public class TLDriverFactory {

    private ThreadLocal < AppiumDriver < MobileElement >> tlDriver = new ThreadLocal <>();

    public synchronized void setTLDriver(OS platform, String server, String udid, String bundleID) {

        switch (platform) {
        case IOS:
            tlDriver = ThreadLocal.withInitial(() -> {
                try {
                    return new IOSDriver < MobileElement > (new URL(server), DesiredCapsManager.getDesiredCapabilities(OS.IOS, udid, bundleID));
                } catch(MalformedURLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                return null;
            });
            break;
        case ANDROID:
            tlDriver = ThreadLocal.withInitial(() -> {
                try {
                    return new AndroidDriver < MobileElement > (new URL(server), DesiredCapsManager.getDesiredCapabilities(OS.ANDROID, udid, bundleID));
                } catch(MalformedURLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                return null;
            });
            break;
        default:
            break;
        }
    }

    public synchronized ThreadLocal < AppiumDriver < MobileElement >> getTLDriver() {
        return tlDriver;
    }
}

This handles browser capbilities

public class DesiredCapsManager {

    public static DesiredCapabilities getDesiredCapabilities(OS platform, String udid, String bundleID) {
        //Set DesiredCapabilities
        DesiredCapabilities capabilities = new DesiredCapabilities();

        capabilities.setCapability("deviceConnectUserName", "User@Name.com");
        capabilities.setCapability("deviceConnectApiKey", "API-Token-Here");
        capabilities.setCapability("udid", udid);
        capabilities.setCapability("platformName", platform);
        capabilities.setCapability("bundleID", bundleID);
        //IOS only Settings
        if (platform.equals(OS.IOS)) {
            capabilities.setCapability("automationName", "XCUITest");
        }
        else {
            //Android only Settings
            capabilities.setCapability("automationName", "appium");
        }

        return capabilities;
    }
}

This is the Base Test class from which every test inherits

public class BaseTest {

    protected AppiumDriver < MobileElement > driver;
    protected AppiumSupport.TLDriverFactory TLDriverFactory = new AppiumSupport.TLDriverFactory();

    public enum OS {
        ANDROID,
        IOS
    }

    @AfterMethod
    public synchronized void tearDown() throws Exception {
        driver.quit();
        TLDriverFactory.getTLDriver().remove();
    }
}

Here is the test case itself

public class Test_SignIn extends BaseTest {

    protected SignInPage signInPage;

    @Parameters(value = {
        "udid",
        "bundleID",
        "platform",
        "server"
    })
    @BeforeMethod
    public void setup(String udid, String bundleID, OS platform, String server) throws MalformedURLException,
    InterruptedException {
        //Set & Get ThreadLocal Driver
        TLDriverFactory.setTLDriver(platform, server, udid, bundleID);
        driver = TLDriverFactory.getTLDriver().get();
        Thread.sleep(5000);
        switch (platform) {
        case IOS:
            signInPage = new SignInPageIOS(driver);
            break;

        case ANDROID:
            signInPage = new SignInPageAndroid(driver);
            break;

        default:
            break;
        }

        System.out.println("Current Thread ID BeforeTest: " + Thread.currentThread().getName());
    }

    @Test
    public synchronized void Authenticate() throws Exception {
        System.out.println("Current Thread ID Test 1: " + Thread.currentThread().getName());
        signInPage.Login("Username", "Password");

    }
}

Here is the testng.xml file

   < !DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="Test" parallel="tests" thread-count="4">
   <test name="SignIn" parallel ="instances" thread-count="2">
          <parameter name="udid" value="DeviceIdGoesHere" />
          <parameter name="bundleID" value="Environment.address.here" />
          <parameter name="platform" value="ANDROID" />
          <parameter name="server" value="http://deviceconnect/appium" />
          <classes>
              <class name="Test.Test_SignIn">
              </class>
          </classes>
   </test>
   <test name="SignIn2" parallel="instances" thread-count="2">
          <parameter name="udid" value="DeviceIdGoesHere" />
          <parameter name="bundleID" value="Environment.address.here" />
          <parameter name="platform" value="IOS" />
          <parameter name="server" value="http://deviceconnect/appium" />
          <classes>
              <class name="Test.Test_SignIn">
              </class>
          </classes>
   </test>
</suite>

What I'm looking for is if anyone can determine what mistake I've made or what the bottleneck is preventing the tests from running in parallel

回答1:

Based on what you have shared so far, here's the cleaned-up and fixed code that should support your concurrency requirements.

The Driver factory class which is responsible for creation and clean-up of Appium driver instances for each and every thread, looks like below:

import io.appium.java_client.AppiumDriver;
import io.appium.java_client.MobileElement;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.ios.IOSDriver;

import java.net.MalformedURLException;
import java.net.URL;

public class TLDriverFactory {

    private static final ThreadLocal<AppiumDriver<MobileElement>> tlDriver = new ThreadLocal<>();

    public static void setTLDriver(BaseTest.OS platform, String server, String udid, String bundleID) throws MalformedURLException {

        System.out.println("Current Thread ID Driver Instantiation: " + Thread.currentThread().getName());

        AppiumDriver<MobileElement> driver;
        switch (platform) {
            case IOS:
                driver = new IOSDriver<>(new URL(server), DesiredCapsManager.getDesiredCapabilities(BaseTest.OS.IOS, udid, bundleID));
                break;

            default:
                driver = new AndroidDriver<>(new URL(server), DesiredCapsManager.getDesiredCapabilities(BaseTest.OS.ANDROID, udid, bundleID));
                break;
        }

        tlDriver.set(driver);
    }

    public static AppiumDriver<MobileElement> getTLDriver() {
        return tlDriver.get();
    }

    public static void cleanupTLDriver() {
        tlDriver.get().quit();
        tlDriver.remove();
    }
}

Here's how the BaseTest which I am guessing is supposed to be the base class for all tests, would look like

import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Parameters;

public class BaseTest {

    private static final ThreadLocal<SignInPage> signInPage = new ThreadLocal<>();

    public enum OS {
        ANDROID,
        IOS
    }

    @Parameters(value = {"udid", "bundleID", "platform", "server"})
    @BeforeMethod
    public void setup(String udid, String bundleID, OS platform, String server) throws Exception {
        //Set & Get ThreadLocal Driver
        TLDriverFactory.setTLDriver(platform, server, udid, bundleID);

        Thread.sleep(5000);
        SignInPage instance;
        switch (platform) {
            case IOS:
                instance = new SignInPageIOS(TLDriverFactory.getTLDriver());
                break;

            default:
                instance = new SignInPageAndroid(TLDriverFactory.getTLDriver());
                break;
        }

        System.out.println("Current Thread ID BeforeTest: " + Thread.currentThread().getName());
        signInPage.set(instance);
    }

    @AfterMethod
    public void tearDown() {
        System.out.println("Current Thread ID AfterTest: " + Thread.currentThread().getName());
        TLDriverFactory.cleanupTLDriver();
    }

    protected static SignInPage getPageForTest() {
        return signInPage.get();
    }

}

Here's how the constructor of your page classes would look like

import io.appium.java_client.AppiumDriver;
import io.appium.java_client.MobileElement;

public class SignInPageIOS extends SignInPage {
    public SignInPageIOS(AppiumDriver<MobileElement> tlDriver) {
        super(tlDriver);
    }
}

Here's how a typical test case could look like

import org.testng.annotations.Test;

public class Test_SignIn extends BaseTest {
    @Test
    public void authenticate() {
        //Get the instance of "SignInPage" for the current thread and then work with it.
        getPageForTest().Login("Username", "Password");
    }
}