Hot questions for Using Cucumber in pageobjects

Question:

I'm trying to follow this tutorial online: https://www.youtube.com/watch?v=x5Ru0f8uOqw&list=PL_noPv5wmuO_t6yYbPfjwhJFOOcio89tI&index=14

and have coded the PageObjects, Feature file and StepDefs file exactly as per the demo. However, when I run, I get a null pointer exception at Line16, contactPage in the @When method.

public class StepDefinition {

WebDriver driver = new FirefoxDriver();
LandingPage landingPage;
ContactPage contactPage;

@Given("^I am on the zoo site$")
public void i_am_on_the_zoo_site() throws Throwable {
    LandingPage landingPage = new LandingPage(driver);
    landingPage.navigateToWebApp();
}

@When("^I click on \"(.*?)\"$")
public void i_click_on(String link) throws Throwable {
    contactPage = landingPage.navigateToContactPage(link);
}

...so I tried instantiating at the top of the class like so:-

WebDriver driver = new FirefoxDriver();
LandingPage landingPage = new LandingPage(driver);
ContactPage contactPage = new ContactPage(driver);

...and everything is happy.

Should I have to instantiate the pageobject instance in this way? What would be the best practice? And, why would the code in the demo not throw a null pointer?

For context, here are the relevant pageobjects: Abstract Page:-

public class AbstractPage {

protected WebDriver driver;

public AbstractPage (WebDriver driver){
    this.driver = driver;
}

public LandingPage navigateToWebApp(){
    driver.navigate().to("http://thetestroom.com/webapp/");
    return new LandingPage(driver);
}


public void closeDriver(){
    driver.quit();
    }
}

Landing Page:-

public class LandingPage extends AbstractPage {


public LandingPage(WebDriver driver) {
    super(driver);
}

public ContactPage navigateToContactPage(String link){
    driver.findElement(By.id(link.toLowerCase() + "_link")).click();
    return new ContactPage(driver);
    }
}

Contact Page:-

public class ContactPage extends AbstractPage{


public ContactPage(WebDriver driver) {
    super(driver);
}

public ContactPage setNameField(String value){

    driver.findElement(By.name("name_field")).sendKeys(value);
    return new ContactPage(driver);
}

//more setter methods

Answer:

Yes, you do need to create new instances of the page objects i.e.

LandingPage landingPage = new LandingPage(driver);
ContactPage contactPage = new ContactPage(driver);

This is an essential practice as just having:

LandingPage landingPage

means that your landingPage variable is implicitly assigned a null value; this is why you are getting your null pointer exception.

Question:

Please, help: I am using cucumber-jvm+WebDriver+jUnit+maven with Page Object pattern for Automation Testing.

I want to have a method which can return multiple types of objects. (different expected pages). In my past i used Generics to implement it with clear java+Webdriver. In This Post there is a good explanation of this.

But now i want to inplement it with cucumber.

My Project Structure looks next way:

Driver base class:

public class DriverBase {
    public static WebDriver driver;

    @Before
    public void setUp() {
    driver = new FirefoxDriver();

    @After
    public void tearDown() throws Exception {
    driver.quit();
    }
}

Navigator Class for interacting between page objects:

public class Navigator {
    DriverBase base;
    WebDriver driver;

    public NavigationActions(DriverBase base) {
        this.base = base;
        this.driver = base.driver;
    }

    public FirstPage openFirstPage(){
    driver.get("someUrl");
    return new FirstPage(base);
    } 
}

Page objects classes:

public class FirstPage {
    WebDriver driver;
    DriverBase base;
    //...
    //Elements locators...
    //Some methods...
    //...
        public FirstPage(DriverBase base) {
            this.base = base;
            this.driver = base.driver;
            PageFactory.initElements(driver, this);
        }

     public <T> T openSecondOrThirdPage(String secondPgUrl, Class<T> expectedPage) {
     driver.get("secondPgUrl");
     return PageFactory.initElements(driver, expectedPage);
}

and

public class SecondPage {
    WebDriver driver;
    DriverBase base;
    //...
    //Elements locators...
    //Some methods...
    //...
        public SecondPage(DriverBase base) {
            this.base = base;
            this.driver = base.driver;
            PageFactory.initElements(driver, this);
        }
}

My StepsDefinition class:

public class MyTestStepsDefs {
    DriverBase base;
    Navigator navigator;

@Given("^bla-bla$"){
    public void go_from_first_to_second_page() {
    navigator.openFirstPage().openSecondOrThirdPage("http://urlOfMyPage.com", SecondPage.class); 
}
@When("^blu-blu$")
    public void login_with_selected_role() {
System.out.println("Some log");
}
@Then("^blo-blo$")
    public void check_links_available(List<String> availableLinks) {
System.out.println("Some log");
}

So, when i run this cucumber test - on openSecondOrThirdPage method error appears:

java.lang.RuntimeException: java.lang.InstantiationException: myprjct.pages.SecondPage
    at org.openqa.selenium.support.PageFactory.instantiatePage(PageFactory.java:136)
    at org.openqa.selenium.support.PageFactory.initElements(PageFactory.java:66)
    at myprjct.pages.FirstPage.openSecondOrThirdPage(FirstPage.java:31)
    .......
Caused by: java.lang.InstantiationException: myprjct.pages.SecondPage
    at java.lang.Class.newInstance(Class.java:359)
    at org.openqa.selenium.support.PageFactory.instantiatePage(PageFactory.java:133)
    at org.openqa.selenium.support.PageFactory.initElements(PageFactory.java:66)
    ........

Please, suggest me, what i am doind wrong?


Answer:

Your problem might be that your Page Object does not have a constructor that matches one of these criteria:

  • Takes "WebDriver" as its sole argument (public SomePage(WebDriver driver)).
  • Default constructor.

Your constructor is something like public SecondPage(DriverBase base) instead, and given that if you write a constructor with arguments for your class then Java does not generate a default constructor, that might be the problem. PageFactory cannot instantiate your page object because it cannot find a suitable constructor.

You can find more information about the PageFactory here. https://code.google.com/p/selenium/wiki/PageFactory

Hope this helps.

Question:

I would like to verify subsequent pages after my cucumber test clicks on the links that lead to different pages.

I would like to do verification of the links on a webpage. My Cucumber test is parsing all links and navigates to each of them. I want to create a generic cucumber step that will accept some argument by which I will verify that the correct page is opened.

So far I created a generic cucumber step that accepts page heading string by which I verify if navigation leads to the correct page.

My Cucumber test:

Scenario Outline: As a not registered User I can visit "<page>"
    When user looks at the webpage
    And user clicks on "<link>" 
    Then user is navigated to "<page>"
    Examples:
      | link                                | page                                                               
      | How to register                     | Registration page                                
      | Questions & Answers                 | Login FAQ's                                    
      | Register Now                        | Create account 
      | Forgot your name or password?       | Reset Password 

My Step looks like this:

@Then("^user is navigated to \"([^\"]*)\"$")
    public void userIsNavigatedTo(final String pageHeading) throws IOException {
        switch (pageHeading) {
            case "Registration page":
                Assert.assertTrue(
                        howToRegisterPage.getPageHeading().getText()
                                .equalsIgnoreCase(pageHeading)
                );
                break;
            case "Login FAQ's":
                Assert.assertTrue(
                    questionsAndAnswersPage.getPageHeading().getText()
                            .equalsIgnoreCase(pageHeading)
                );
                break;
   //.....//
}

This switch is looking awful to me, moreover, I understand that soon it will be too long to read and too difficult to maintain.

P.S. All my PageObjects are Spring Beans and can be autowired into StepDefinition class, but I have no idea how I can make my "userIsNavigatedTo" generic, what I need to pass as an argument and how I can take correct PageObject for every iteration.


Answer:

To avoid switch-case you can create a Map<String, YourPageObject> that maps page heading to your page object.

public class StepDefinition{
  private final Map<String, YourPageObject> pageObjectsIndex;

  @Autowired
  public StepDefinition(List<YourPageObject> yourPageObjects){
    pageObjectsIndex = yourPageObjects.stream().collect(Collectors.toMap(page -> page.getPageHeading().getText().toUpper(), Function.identity());
  }

  @Then("^user is navigated to \"([^\"]*)\"$")
   public void userIsNavigatedTo(final String pageHeading) throws IOException {
     Assert.assertTrue(pageObjectsIndex.get(pageHeading.toUpper()).isLoaded());
   }
}

The side-effect of these idea is that each page needs to have its own class that implements YourPageObject interface. I wrote "side-effect" because I'm not sure if it is a drawback or an advantage (probably it is case dependent ;) ).

Question:

In our web application, logging into a certain page requires ages. Our features and scenario sets are written from that particular page. We do not have any validations before that page. Takes about 10 minutes just to reach that page.

Before I explain further. This is in Cucumber and we are using Selenium with Java.

So the login action should be done and that webdriver session should stay intact and then the scenario validations start. The Features below has the below scenarios. Each subsequent scenario is dependent on the previous one. Meaning, these scenarios need to be executed sequentially.

1st Feature should execute the scenarios sequentially. Then the 2nd Feature should execute the scenarios sequentially. In between I don't want a new session created for the 2nd scenario. These are all dependent sequentially.

How can I implement this?

Feature: Add a product to the quote

    So that the quotation can be built on the added product

    Scenario: Add a Hub
        Given User A has logged in
        Then Add button is available
        Then Add Hub
        Then Enter correct Address details
        Then select correct bandwidth
        And Save to Quote

    Scenario: Add a Spoke
        Given User A has logged in
        Then Add button is available
        Then Add Spoke
        Then Enter correct Address details
        Then select correct bandwidth
        And Save to Quote


Feature: Give discount to the quote for Hub

    So that the quotation can be sent to the customer

    Scenario: Give 10% discount 
        Given Hub has already been added
        Then Go to the Quote Tab
        Then Enter the discount
        Then Save the Quote

Answer:

Sequential Running of features - Two ways of doing this. First one, depend on the fact that cucumber runs features in alphabetic order. So name them accordingly. But this approach is easily broken by a new feature file name which slots in between the ones you created earlier. Second, specify the running order in the cucumberoptions feature value eg features = "second.feature,first.feature" in the runner file. This will run the second.feature first. Refer to this for more details.

Keeping driver alive across feature files - Use the concept of a SharedWebDriver which has a static WebDriver inside it and a JVM shutdown hook which closes the driver at the end. Though you might need to add appropriate before and after hooks to cleanup cookies etc.

Question:

I using POM model and using Cucumber for my Automation.

I am trying to implement a negative scenario for login and I have used the below strategy.

I need to know whether this is the correct approach or I am messing it up somewhere.

I am using xpath to assert Login.

login.feature

    Given User navigates to Site
    And User enters a "<Username>" username
    And User enters a "<Password>" password
    When User clicks on the login button
    Then User should see the failure "<message>"

    Examples:
      | Username | Password   | message|
      | User | Pwd  | //DIV[@class=''][text()='Your login name or password is incorrect.']/../..] |

login.steps

    @Then("^User should see the failure \"([^\"]*)\"$")
    public void user_should_see_the_failure(String arg1 ) throws Throwable {
login_page.assertLoginFailure(arg1);
    }


login.page

    public @FindBy(xpath = "//DIV[@class=''][text()='Your login name or password is incorrect.']/../..")
    WebElement assert_LoginFailure;


    public login_page assertLoginFailure(String arg1) throws Exception {
        Thread.sleep(5000);
        org.testng.Assert.assertEquals(assert_LoginFailure,arg1);
        return new login_page();


    }

Answer:

The value under message in the DataTable should be the plain text expected when the login is unsuccessful. Ideally should not include any xpath or any selector.

The selector should be defined in the pageobject. Also the selector should be modified to not include the text itself.

This way when the test fails, instead of getting an AssertionError you will get a NoSuchElementException. There is no way of telling if the test failed due to an invalid credential turning out to be valid or the message has been changed on the site.

Thread.sleep() is kind of frowned upon. Rather look at an implicit or explicit wait. Refer to this - http://toolsqa.com/selenium-webdriver/implicit-explicit-n-fluent-wait/