0

I am working with Selenium Webdriver with Cucumber. My tests work as expected with that combination. In order to achieve cross-browser testing, I added TestNG framework. To verify that my cross-browser test was working good, I ran it with TestNG alone, without Cucumber. It ran perfectly in both Chrome and Firefox browsers.

public class WebTest {


  WebDriver driver = null;
  BasePageWeb basePage;
  public String browser;


  @Parameters({ "Browser" })
  public WebTest(String browser) {
    this.browser = browser;
  }

  
  @BeforeClass
  public void navigateToUrl() {
    switch (browser) {

      case "CHROME":
        WebDriverManager.chromedriver().setup();
        driver = new ChromeDriver();
        break;

      case "FF":
        WebDriverManager.firefoxdriver().setup();
        driver = new FirefoxDriver();
        break;

      default:
        driver = null;
        break;
    }
    driver.get("https://demosite.executeautomation.com/Login.html");

  }

  @Test
  public void loginToWebApp() {

    basePage = new BasePageWeb(driver);
    basePage.enterUsername("admin")
            .enterPassword("admin")
            .clickLoginButton();

    driver.quit();
  }

}

The testng.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">

<suite name="Suite" parallel="tests" thread-count="5">

    <test name="Chrome Test">
        <parameter name="Browser" value="CHROME"/>
        <classes>
            <class name="tests.web.WebTest"/>
        </classes>
    </test>
    <test name="Firefox Test">
        <parameter name="Browser" value="FF"/>
        <classes>
            <class name="tests.web.WebTest"/>
        </classes>
    </test>
</suite>

I needed to integrate the TestNG test with my Cucumber set-up so that I can run the whole test with Cucumber. To do this, I added cucumber-testng dependency to POM and created a Cucumber runner extending the AbstractCucumberTestNG class. I specified the location of my feature file and step definition. The step definition is mapped to the TestNG test.

Cucumber runner:

@CucumberOptions(
                plugin = {"pretty", "html:target/surefire-reports/cucumber",
                        "json:target/surefire-reports/cucumberOriginal.json"},
                glue = {"stepdefinitions"},
                tags = "@web-1",
                features = {"src/test/resources/features/web.feature"})
          
   

 public class RunCucumberNGTest extends AbstractTestNGCucumberTests {
    }

Step definition:

public class WebAppStepDefinitions {
      private final WebTest webTest = new WebTest("CHROME"); //create an object of the class holding the testng test. If I change the argument to FF, the test will run only on Firefox
      static boolean prevScenarioFailed = false;
    
    
      @Before
      public void setUp() {
        if (prevScenarioFailed) {
          throw new IllegalStateException("Previous scenario failed!");
        }
    
      }
    
      @After()
      public void stopExecutionAfterFailure(Scenario scenario) throws Exception {
        prevScenarioFailed = scenario.isFailed();
      }
    
      @Given("^I have navigated to the web url \"([^\"]*)\"$")
      public void navigateToUrl(String url) {  test
        webTest.navigateToUrl(url); //calling the first method holding the testng
      }
    
    
      @When("^I log into my web account with valid credentials as specicified in (.*) and (.*)$")
      public void logintoWebApp(String username, String password) {
        webTest.loginToWebApp(username, password); //calling the second method holding the testng
      }
    }

On running the class, the test got executed only in one browser (Chrome). Somehow, Firefox got lost in the build-up. I suspect that I am calling the parameterised TestNG method wrongly from another class. How do I do the call successfully?

The man
  • 129
  • 16

1 Answers1

2

For running TestNG tests with Cucumber you have to define Test Runner classes in testng.xml.

your Test Runner class is RunCucumberNGTest.

So the xml should look like:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">

<suite name="Suite" parallel="tests" thread-count="5">

    <test name="Chrome Test">
        <parameter name="Browser" value="CHROME"/>
        <classes>
            <class name="some.package.name.RunCucumberNGTest"/>
        </classes>
    </test>
    <test name="Firefox Test">
        <parameter name="Browser" value="FF"/>
        <classes>
            <class name="some.package.name.RunCucumberNGTest"/>
        </classes>
    </test>
</suite>

From this xml I see the next requirements:

  1. Run the same set of tests but with different parameter value.

  2. This should work in parallel, so this should be thread-safe.


1 Introduce TestNG Parameter for Test Runner class

@CucumberOptions(
                plugin = {"pretty", "html:target/surefire-reports/cucumber",
                        "json:target/surefire-reports/cucumberOriginal.json"},
                glue = {"stepdefinitions"},
                tags = "@web-1",
                features = {"src/test/resources/features/web.feature"})
public class RunCucumberNGTest extends AbstractTestNGCucumberTests {

    // static thread-safe container to keep the browser value
    public final static ThreadLocal<String> BROWSER = new ThreadLocal<>();

    @BeforeTest
    @Parameters({"Browser"})
    public void defineBrowser(String browser) {
        //put browser value to thread-safe container
        RunCucumberNGTest.BROWSER.set(browser);
        System.out.println(browser);
    }

}

2 Use the value in Step Definition class

public class WebAppStepDefinitions {
      private WebTest webTest;
      static boolean prevScenarioFailed = false;
    
      @Before
      public void setUp() {
        if (prevScenarioFailed) {
          throw new IllegalStateException("Previous scenario failed!");
        }
        //get the browser value for current thread
        String browser = RunCucumberNGTest.BROWSER.get();
        System.out.println("WebAppStepDefinitions: " + browser);
        //create an object of the class holding the testng test. If I change the argument to FF, the test will run only on Firefox
        webTest = new WebTest(browser);
      }
    
      @After
      public void stopExecutionAfterFailure(Scenario scenario) throws Exception {
        prevScenarioFailed = scenario.isFailed();
      }
    
      @Given("^I have navigated to the web url \"([^\"]*)\"$")
      public void navigateToUrl(String url) {
        webTest.navigateToUrl(url); //calling the first method holding the testng
      }
    
    
      @When("^I log into my web account with valid credentials as specicified in (.*) and (.*)$")
      public void logintoWebApp(String username, String password) {
        webTest.loginToWebApp(username, password); //calling the second method holding the testng
      }

}

NOTE: All TestNG annotations should be removed from WebTest class, they won't work and not required. WebTest used explicitly by WebAppStepDefinitions class, all the methods invoked explicitly and not by TestNG.

So, based on your initial requirements:

public class WebTest {

  WebDriver driver = null;
  BasePageWeb basePage;
  public String browser;

  public WebTest(String browser) {
    this.browser = browser;
  }

  public void navigateToUrl(String url) {
    switch (browser) {

      case "CHROME":
        WebDriverManager.chromedriver().setup();
        driver = new ChromeDriver();
        break;

      case "FF":
        WebDriverManager.firefoxdriver().setup();
        driver = new FirefoxDriver();
        break;

      default:
        driver = null;
        break;
    }
    driver.get(url);

  }

  public void loginToWebApp(String username, String password) {

    basePage = new BasePageWeb(driver);
    basePage.enterUsername(username)
            .enterPassword(password)
            .clickLoginButton();

    driver.quit();
  }
Max Daroshchanka
  • 2,698
  • 2
  • 10
  • 14
  • Thanks. I applied your implementation and then ran the test via `RunCucumberNGTest` class and also via the `testng.xml` file. The test got skipped and I got the error: `org.testng.TestNGException: Can inject only one of into a @BeforeTest annotated defineBrowser.` Any ideas why? Would it help if I grant you access to the repo for easier debugging? Your time will help greatly and will be forever appreciated. – The man Feb 12 '22 at 21:05
  • 1
    If you run from `RunCucumberNGTest` this won't work, since `@Parameters({"Browser"})` won't be passed. If you run from `testng.xml` it should work, if there are no any typos.. I've checked this code with TestNG v `7.4.0`. I've reproduced the error: `Can inject only one of into a @BeforeTest annotated defineBrowser`, when I just commented the `@Parameters({"Browser"})` line for `defineBrowser` method. Do you have `@Parameters` defined for the `defineBrowser` method? – Max Daroshchanka Feb 12 '22 at 21:14
  • No, I don't have `@Parameters` defined for the 'defineBrowser' method. I used exactly the same implementation you provided. The only thing I updated was the class name in `testng.xml`. I can't find the line you commented on to make the implementation work – The man Feb 12 '22 at 21:28
  • In my example `@Parameters({"Browser"})` annotation is added for `defineBrowser(String browser)` and this is essential to make it work. I've mentioned that the error appeared for me when I removed this annotation. So `@Parameters({"Browser"})` should be in the code. – Max Daroshchanka Feb 12 '22 at 21:33
  • I'm able to look throw your code if you able to share it somehow.. But, please, first confirm that you have `@Parameters({"Browser"})` for `defineBrowser(String browser)`. – Max Daroshchanka Feb 12 '22 at 21:35
  • 1
    Fantastic. I have added `@Parameters({"Browser"})` for `defineBrowser(String browser)`. Its working now. You are a real genius. The issue is now considered resolved. I have 2 follow-up questions though if you don't mind. First, since the test works by running it via `testng.xml` which has the `RunCucumberNGTest` class embedded in it, should it not be possible to pass `@Parameters({"Browser"})` to the `RunCucumberNGTest` so that it can run from there also? Second, it's a maven project so I will be running in a CI tool. So, how can I run it via mvn? I just tried `mvn test` now, it failed. – The man Feb 12 '22 at 22:11
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/241962/discussion-between-max-daroshchanka-and-the-man). – Max Daroshchanka Feb 12 '22 at 22:13
  • 1) Adding @Optional value: `public void defineBrowser(@Optional("CHROME") String browser)` will allow to run `RunCucumberNGTest` directly, but with hardcoded default argument. – Max Daroshchanka Feb 12 '22 at 22:15
  • 2) `how can I run it via mvn` - I think you have to define `maven-surefire-plugin` in your `pom.xml`. And in plugin configuration set `suiteXmlFiles` value. Look at this question: https://stackoverflow.com/questions/10391312/testng-surefire-run-suite-with-maven-command-line . If you'll hardcode the xml, the command will be `mvn clean test`. Or if you'll define some suiteName argument: `mvn clean test -DsuiteName=".../testng.xml"` – Max Daroshchanka Feb 12 '22 at 22:21
  • Adding `@Optional value: public void defineBrowser(@Optional("CHROME") String browser)` allows the test to run via `RunCucumberNGTest ` directly, but only on Chrome. If I change it to `FF`, it runs only on Firefox. The 2 browsers are only running together if I run via `testng.xml` – The man Feb 13 '22 at 12:18
  • 1
    Yes, it's not possible to run one class for both browsers without testng.xml – Max Daroshchanka Feb 13 '22 at 12:53
  • It works, thanks. – AbiSaran Jul 21 '23 at 04:50