Zucchini Concurrent cucumber testing Android Appium Example

397 views Asked by At

I am currently working on cucumber automation parallel execution of the Runner class and I came across this nice framework http://comcast.github.io/zucchini/ and I am facing some issues with running my AndroidDriver test cases in parallel. When I run my code using via pom file it shows all the two tests has started in browser stack and always latest one complete and previous one times out. Here is my code. Glad if anyone can help.

Pom File

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>net.dimaj</groupId>
    <artifactId>zucchini-sample</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>Zucchini Sample Project</name>
    <description>A sample project that demonstrates how to use Zucchini against multiple web browsers</description>

    <contributors>
        <contributor>
            <name>Dmitry Jerusalimsky</name>
            <url>http://blog.dimaj.net</url>
        </contributor>
    </contributors>

    <properties>
        <cucumber.version>1.2.4</cucumber.version>
    </properties>

    <dependencies>


        <!-- https://mvnrepository.com/artifact/com.comcast.zucchini/zucchini -->
<dependency>
    <groupId>com.comcast.zucchini</groupId>
    <artifactId>zucchini</artifactId>
    <version>2.3.1</version>
</dependency>



        <!-- <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-firefox-driver</artifactId>
            <version>2.46.0</version>
            <scope>test</scope>
        </dependency> -->

        <dependency>
            <groupId>io.appium</groupId>
            <artifactId>java-client</artifactId>
            <version>5.0.4</version>
            <!--<exclusions>-->
            <!--<exclusion>-->
            <!--<groupId>com.google.guava</groupId>-->
            <!--<artifactId>guava</artifactId>-->
            <!--</exclusion>-->
            <!--</exclusions>-->
        </dependency>
          <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>3.0.1</version>
            <scope>test</scope>
        </dependency>

          <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-java</artifactId>
            <version>${cucumber.version}</version>
        </dependency>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-junit</artifactId>
            <version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-picocontainer</artifactId>
            <version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>

         <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-junit</artifactId>
            <version>1.2.5</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/info.cukes/cucumber-testng -->
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-testng</artifactId>
            <version>${cucumber.version}</version>
        </dependency>


        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <configuration>
                     <!--  <parallel>classes</parallel> -->
                      <threadCount>2</threadCount>
                        <reuserForks>false</reuserForks>
                        <includes>
                            <include>**/*Test.java</include>
                        </includes>
                    </configuration>

                </plugin>
            </plugins>
        </pluginManagement>
    </build>

</project>

Runner Class :

 package net.dimaj.zucchini.tests;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import com.comcast.zucchini.AbstractZucchiniTest;
import com.comcast.zucchini.TestContext;
import com.comcast.zucchini.ZucchiniOutput;

import cucumber.api.CucumberOptions;
import cucumber.api.Scenario;
import cucumber.api.java.Before;
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.MobileElement;
import io.appium.java_client.android.AndroidDriver;
import net.dimaj.zucchini.utils.Constants;

import org.openqa.selenium.remote.DesiredCapabilities;

@CucumberOptions(features = { "src/test/resources/StartPage.feature" }, glue = { "net.dimaj.zucchini.sample.glue" })
public class FirstZucchiniTest extends AbstractZucchiniTest {

    String USERNAME = "<>";
    String ACCESS_KEY = "<>";
    String url = "https://" + USERNAME + ":" + ACCESS_KEY + "@hub.browserstack.com/wd/hub";

      private TestContext createContext(String name, String device, String os) throws MalformedURLException {
          DesiredCapabilities caps = new DesiredCapabilities();
          caps.setCapability("device", device);
          caps.setCapability("os_version", os);
          caps.setCapability("deviceName", "Android Device");
          caps.setCapability("build", "Android_Browser Stack Pharellel");
          caps.setCapability("name", "Pharellel Testing");
          caps.setCapability("app", "bs://dlslldlfll");
          caps.setCapability("networkLogs", true);

          AndroidDriver<MobileElement> driver = new AndroidDriver<MobileElement>(new java.net.URL(url), caps);
          TestContext context = new TestContext(name);
          context.set(Constants.CONTEXT_ANDROID, driver);

          return context;
        }

        @Override
        public List<TestContext> getTestContexts() {
            List<TestContext> driverList = new ArrayList<TestContext>();
            try {
              driverList.add(createContext("Remote1", "Google Pixel", "7.1"));
              driverList.add(createContext("Remote2", "Google Nexus 6", "6.0"));
            }
            catch (MalformedURLException e) {
              throw new RuntimeException("Couldn't create driver", e);
            }

            return driverList;
        }

        @Before
        public void setupTests(Scenario scenario) {
            TestContext.getCurrent().set(Constants.CONTEXT_SCENARIO, scenario);
        }

        @Override
        public void cleanup(TestContext out) {
            AndroidDriver<MobileElement> driver = out.get(Constants.CONTEXT_ANDROID);
            if (null != driver) {
                driver.quit();
            }
        }

}

My step Definition class

 package net.dimaj.zucchini.sample.glue;

import java.util.concurrent.TimeUnit;

import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;

import com.comcast.zucchini.TestContext;
import cucumber.api.Scenario;
import cucumber.api.java.Before;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.When;
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.MobileElement;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.pagefactory.AndroidBy;
import io.appium.java_client.pagefactory.AndroidFindBy;
import io.appium.java_client.pagefactory.WithTimeout;
import io.appium.java_client.pagefactory.iOSFindBy;
import net.dimaj.zucchini.utils.Constants;

public class StartPageSteps{
    AppiumDriver<MobileElement> androidDriver;


    @AndroidFindBy(id = "intro_login")
    @WithTimeout(time = 5, unit = TimeUnit.SECONDS)
    private MobileElement logInButton;

    public StartPageSteps(){
        androidDriver = TestContext.getCurrent().get(Constants.CONTEXT_ANDROID);
    }

    @Before
    public void setup(Scenario scenario) {
        TestContext.getCurrent().set("scenario", scenario);
    }


    private By loginBtn = By.id("intro_login");

    @Given("Sign Up is displayed")
    public void sign_Up_is_displayed() {

        androidDriver.findElement(loginBtn).isDisplayed();

    }

    @When("user taps Sign Up button")
    public void user_taps_Sign_Up_button() {
        //androidDriver.findElement(loginBtn).click();
        assert(androidDriver.findElement(loginBtn).isDisplayed());
    }
}

Here is my Log :

    Session ID: ebed1cfaf948d20a2c715e39711b672e6feca130
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at org.openqa.selenium.remote.ErrorHandler.createThrowable(ErrorHandler.java:214)
    at org.openqa.selenium.remote.ErrorHandler.throwIfResponseFailed(ErrorHandler.java:166)
    at org.openqa.selenium.remote.http.JsonHttpResponseCodec.reconstructValue(JsonHttpResponseCodec.java:40)
    at org.openqa.selenium.remote.http.AbstractHttpResponseCodec.decode(AbstractHttpResponseCodec.java:82)
    at org.openqa.selenium.remote.http.AbstractHttpResponseCodec.decode(AbstractHttpResponseCodec.java:45)
    at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:164)
    at io.appium.java_client.remote.AppiumCommandExecutor.execute(AppiumCommandExecutor.java:89)
    at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:586)
    at io.appium.java_client.DefaultGenericMobileDriver.execute(DefaultGenericMobileDriver.java:46)
    at io.appium.java_client.AppiumDriver.execute(AppiumDriver.java:1)
    at io.appium.java_client.android.AndroidDriver.execute(AndroidDriver.java:1)
    at io.appium.java_client.HasSessionDetails.getSessionDetails(HasSessionDetails.java:38)
    at io.appium.java_client.HasSessionDetails.getSessionDetail(HasSessionDetails.java:55)
    at io.appium.java_client.HasSessionDetails.getPlatformName(HasSessionDetails.java:62)
    at io.appium.java_client.DefaultGenericMobileDriver.toString(DefaultGenericMobileDriver.java:156)
    at io.appium.java_client.AppiumDriver.toString(AppiumDriver.java:1)
    at io.appium.java_client.android.AndroidDriver.toString(AndroidDriver.java:1)
    at java.lang.String.valueOf(String.java:2994)
    at java.lang.StringBuilder.append(StringBuilder.java:131)
    at java.util.AbstractMap.toString(AbstractMap.java:559)
    at com.comcast.zucchini.TestContext.toString(TestContext.java:107)
    at com.comcast.zucchini.AbstractZucchiniTest.runParallel(AbstractZucchiniTest.java:181)
    at com.comcast.zucchini.AbstractZucchiniTest.run(AbstractZucchiniTest.java:129)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:84)
    at org.testng.internal.Invoker.invokeMethod(Invoker.java:714)
    at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:901)
    at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1231)
    at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127)
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111)
    at org.testng.TestRunner.privateRun(TestRunner.java:767)
    at org.testng.TestRunner.run(TestRunner.java:617)
    at org.testng.SuiteRunner.runTest(SuiteRunner.java:348)
    at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:343)
    at org.testng.SuiteRunner.privateRun(SuiteRunner.java:305)
    at org.testng.SuiteRunner.run(SuiteRunner.java:254)
    at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
    at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
    at org.testng.TestNG.runSuitesSequentially(TestNG.java:1224)
    at org.testng.TestNG.runSuitesLocally(TestNG.java:1149)
    at org.testng.TestNG.run(TestNG.java:1057)
    at org.apache.maven.surefire.testng.TestNGExecutor.run(TestNGExecutor.java:77)
    at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.execute(TestNGDirectoryTestSuite.java:110)
    at org.apache.maven.surefire.testng.TestNGProvider.invoke(TestNGProvider.java:106)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
    at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
    at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
    at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
    at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)

07:27:38.000 [Thread-1] WARN  c.c.zucchini.ZucchiniShutdownHook - There are 0 features run

Results :

Failed tests:   run(net.dimaj.zucchini.tests.FirstZucchiniTest): Session not started or terminated (WARNING: The server did not provide any stacktrace information)(..)

Tests run: 1, Failures: 1, Errors: 0, Skipped: 0
1

There are 1 answers

10
dimaj On

What happens if you change your FirstZucchiniTest to this:

package net.dimaj.zucchini.tests;

import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;
import com.comcast.zucchini.AbstractZucchiniTest;
import com.comcast.zucchini.TestContext;
import cucumber.api.CucumberOptions;
import cucumber.api.Scenario;
import cucumber.api.java.Before;
import io.appium.java_client.MobileElement;
import io.appium.java_client.android.AndroidDriver;
import net.dimaj.zucchini.utils.Constants;
import org.openqa.selenium.remote.DesiredCapabilities;

@CucumberOptions(features = { "src/test/resources/StartPage.feature" }, glue = { "net.dimaj.zucchini.sample.glue" })
public class FirstZucchiniTest extends AbstractZucchiniTest {

    String USERNAME = "<username>";
    String ACCESS_KEY = "<access key>";
    String url = "https://" + USERNAME + ":" + ACCESS_KEY + "@hub.browserstack.com/wd/hub";

    private TestContext createContext(String name, String device, String os) throws MalformedURLException {
      DesiredCapabilities caps = new DesiredCapabilities();
      caps.setCapability("device", device);
      caps.setCapability("os_version", os);
      caps.setCapability("deviceName", "Android Device");
      caps.setCapability("build", "Android_Browser Stack Pharellel");
      caps.setCapability("name", "Pharellel Testing");
      caps.setCapability("app", "bs://48da5beeef475d0c1716b7d2eaad3e18d2e8ebf8");
      caps.setCapability("networkLogs", true);

      AndroidDriver<MobileElement> driver = new AndroidDriver<MobileElement>(new java.net.URL(url), caps);
      TestContext context = new TestContext(name) {
         @Override
         public String toString() {
           return this.name;
         }
      };
      context.set(Constants.CONTEXT_ANDROID, driver);

      return context;
    }

    @Override
    public List<TestContext> getTestContexts() {
        List<TestContext> driverList = new ArrayList<TestContext>();
        try {
          driverList.add(createContext("Remote1", "Google Pixel", "7.1"));
          driverList.add(createContext("Remote2", "Google Nexus 6", "6.0"));
        }
        catch (MalformedURLException e) {
          throw new RuntimeException("Couldn't create driver", e);
        }

        return driverList;
    }

    @Before
    public void setupTests(Scenario scenario) {
        TestContext.getCurrent().set(Constants.CONTEXT_SCENARIO, scenario);
    }

    @Override
    public void cleanup(TestContext out) {
        AndroidDriver<MobileElement> driver = out.get(Constants.CONTEXT_ANDROID);
        if (null != driver) {
            driver.quit();
        }
    }

}

The main thing that was changed here was the handling of exception if AndroidDriver fails to initialize.

My thoughts here are such that your second driver fails to initialize and it is added to TestContext as null. Then, when TestContext.toString() is being called, it calls toString() on every Object that has been added to TestContext and, if that fail, you end up with exceptions. Here's an example of what I mean: http://tpcg.io/EonKtS

In the updated response above, I am overriding definition of the toString function to avoid printing out contents of the current context. If you will still be failing, then I would look into browserstack.