-1

I have two different step classes that has common lifecycle before and after steps. So I created a common step class that has webdriver initialisation and cleanup code as a lifecycle before and after methods. This common class is extended by both the step definition classes. When running these stories, I see null pointer exception while accessing the web driver in second step class. Debugging further I noticed that the common steps are called using first step class object for each time there is a scenario in the story file.

My Story File 1.

Meta:
@author prasanta biswas
@theme UI Test

Narrative:
As a Facebook User
In order to update my status
I want to login to my Facebook account
So that I can update my status message

Lifecycle:
Before:
Scope: SCENARIO
Given I initialize my chrome browser instance
After:
Scope: SCENARIO|STORY
Outcome: ANY
Given I cleanup my browser instance

Scenario: Update Facebook status message
Meta:
@type positive
@data valid
@app facebook

Given I have a Facebook account
And I open Facebook in my web browser with the <url>
And I login to Facebook with <username> and <password>
Then I check if status message box is present in the homepage after successful login
When I put my status <message> in the status box
And I click post button
Then I should see my new status <message> in my news feeds

Examples:
|username|password|url|message|
|VALID_USERNAME|VALID_PASSWORD|https://www.facebook.com|Hi Facebook|

My Story File 2.

Meta:
@author prasanta biswas
@theme UI Test

Narrative:
As a wallethub user
I want to review a particual insurance policy
So that the review appears in my review list


Lifecycle:
Before:
Scope: SCENARIO
Given I initialize my chrome browser instance
After:
Scope: SCENARIO|STORY
Outcome: ANY
Given I cleanup my browser instance


Scenario: Create review for a particual walletHub insurance policy
Meta:
@type positive
@data valid
@app wallethub

Given I have a WalletHub test account
And I open wallethub in the web browser using <url>
And I login to my wallethub account with <email> and <password>
Then I should see my <username> on my homepage
Given I navigate to <reviewUrl> after login
And I count the current review count in my review list
And I navigate to <insuranceCompanyUrl>
When I click Write a Review button
Then I should see new review page
Given I select insurance <policy> from insurance list in new review page
And I apply rating <star>
And I put my review <text>
When I click submit button
Then I should see confirmation <message>
And I should see a my new review <text> in my <reviewUrl>

Examples:
|email|password|url|username|reviewUrl|insuranceCompanyUrl|policy|text|star|message|
|eaxmple@gmail.com|asssdd|https://wallethub.com/|pthewizard|https://wallethub.com/profile/pthewizard/reviews/|http://wallethub.com/profile/test_insurance_company/|Health|This is my first reviewHealthPolicy.|5|Awesome!|

CommonStep class:

    package io.jbehavetutorials.steps.common;

    import io.jbehavetutorials.testutility.BrowserFactory;
    import org.jbehave.core.annotations.Given;
    import org.jbehave.core.annotations.Named;
    import org.openqa.selenium.WebDriver;

    /**
     * Created by prasantabiswas on 23/05/18.
     */
    public class CommonSteps {

        protected WebDriver webDriver;

        @Given("I initialize my $browserName browser instance")
        public void setUpMethod(@Named("browserName") String browser)
        {
            webDriver = BrowserFactory.getDriver(browser);
        }

        @Given("I cleanup my browser instance")
        public void tearDownMethod()
        {
            // Cleanup
            if(webDriver != null)
                webDriver.quit();
        }
    }

My first step definition class:

package io.jbehavetutorials.steps.facebook;

import io.jbehavetutorials.appmodule.facebook.FacebookApp;
import io.jbehavetutorials.constants.Browser;
import io.jbehavetutorials.steps.common.CommonSteps;
import io.jbehavetutorials.testutility.BrowserFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jbehave.core.annotations.*;
import org.junit.Assert;
import org.openqa.selenium.WebDriver;

import java.net.MalformedURLException;

/**
 * Created by prasantabiswas on 21/05/18.
 */
public class FacebookStatusPost extends CommonSteps{
    FacebookApp facebookApp;
    Logger logger = LogManager.getLogger(FacebookStatusPost.class);

    @Given("I have a Facebook account")
    @Pending
    public void iHaveAFacebookAccount()
    {
        try {
            facebookApp = new FacebookApp(webDriver); // Working perfectly here
            Assert.assertTrue("Facebook object creation failed",facebookApp!=null);
        }
        catch (Exception e)
        {
            logger.error("Error",e);
        }
    }
}

My second step definition class:

package io.jbehavetutorials.steps.wallethub;

import io.jbehavetutorials.appmodule.wallethub.WalletHubApp;
import io.jbehavetutorials.constants.Browser;
import io.jbehavetutorials.steps.common.CommonSteps;
import io.jbehavetutorials.testutility.BrowserFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jbehave.core.annotations.*;
import org.junit.Assert;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;

import java.net.MalformedURLException;

/**
 * Created by prasantabiswas on 21/05/18.
 */
public class WalletHubReviewInsurance extends CommonSteps {
    WalletHubApp wallethubApp;
    int reviewCount = 0;
    Logger logger = LogManager.getLogger(WalletHubReviewInsurance.class);

    @Given("I have a WalletHub test account")
    public void iHaveAWallerHubTestAccount()
    {
        try{
            wallethubApp = new WalletHubApp(webDriver);//Getting null pointer here
        }
        catch (Exception e)
        {
            logger.error("Error",e);
        }
    }
}

Story map class:

package io.jbehavetutorials;

import io.jbehavetutorials.steps.facebook.FacebookStatusPost;
import io.jbehavetutorials.steps.wallethub.WalletHubReviewInsurance;
import io.jbehavetutorials.steps.weatherapi.WeatherAPITest;
import org.jbehave.core.embedder.StoryControls;
import org.jbehave.core.junit.JUnitStories;

import java.text.SimpleDateFormat;
import java.util.List;

import org.jbehave.core.Embeddable;
import org.jbehave.core.configuration.Configuration;
import org.jbehave.core.configuration.MostUsefulConfiguration;
import org.jbehave.core.i18n.LocalizedKeywords;
import org.jbehave.core.io.CodeLocations;
import org.jbehave.core.io.LoadFromClasspath;
import org.jbehave.core.io.StoryFinder;
import org.jbehave.core.model.ExamplesTableFactory;
import org.jbehave.core.model.TableTransformers;
import org.jbehave.core.parsers.RegexStoryParser;
import org.jbehave.core.reporters.StoryReporterBuilder;
import org.jbehave.core.reporters.SurefireReporter;
import org.jbehave.core.steps.*;
import org.jbehave.core.steps.ParameterConverters.DateConverter;
import org.jbehave.core.steps.ParameterConverters.ExamplesTableConverter;

import static org.jbehave.core.io.CodeLocations.codeLocationFromClass;
import static org.jbehave.core.reporters.Format.CONSOLE;
import static org.jbehave.core.reporters.Format.HTML;
import static org.jbehave.core.reporters.Format.TXT;
import static org.jbehave.core.reporters.Format.XML;

/**
 * <p>
 * {@link Embeddable} class to run multiple textual stories via JUnit.
 * </p>
 * <p>
 * Stories are specified in classpath and correspondingly the {@link LoadFromClasspath} story loader is configured.
 * </p> 
 */
public class StoryMap extends JUnitStories {

    public StoryMap() {
        configuredEmbedder().embedderControls().doGenerateViewAfterStories(true).doIgnoreFailureInStories(true)
                .doIgnoreFailureInView(true).useThreads(1);
//        configuredEmbedder().useMetaFilters(Arrays.asList("+type negative"));
    }

    @Override
    public Configuration configuration() {
        Class<? extends Embeddable> embeddableClass = this.getClass();
        // Start from default ParameterConverters instance
        ParameterConverters parameterConverters = new ParameterConverters();
        // Start from default ParameterControls instance
        ParameterControls parameterControls = new ParameterControls();
        // factory to allow parameter conversion and loading from external resources (used by StoryParser too)
        ExamplesTableFactory examplesTableFactory = new ExamplesTableFactory(new LocalizedKeywords(), new LoadFromClasspath(embeddableClass),parameterConverters,parameterControls, new TableTransformers());
        // add custom converters
        parameterConverters.addConverters(new DateConverter(new SimpleDateFormat("yyyy-MM-dd")),
                new ExamplesTableConverter(examplesTableFactory));
        return new MostUsefulConfiguration()
                //.usePendingStepStrategy(new FailingUponPendingStep())
                .useStoryControls(new StoryControls().doDryRun(false))
                .useStoryLoader(new LoadFromClasspath(embeddableClass))
                .useStoryParser(new RegexStoryParser(examplesTableFactory))
//                .useStoryParser(new GherkinStoryParser())
                .useStoryReporterBuilder(new StoryReporterBuilder()
                .withSurefireReporter(new SurefireReporter(embeddableClass))
                    .withCodeLocation(CodeLocations.codeLocationFromClass(embeddableClass))
                    .withDefaultFormats()
                    .withFormats(CONSOLE, TXT, HTML, XML)
                    .withFailureTrace(true)
                    .withFailureTraceCompression(true))
            .useParameterConverters(parameterConverters)
            .useParameterControls(parameterControls);
    }

    @Override
    public InjectableStepsFactory stepsFactory() {
        return new InstanceStepsFactory(configuration(), new FacebookStatusPost(), new WalletHubReviewInsurance(), new WeatherAPITest());
//        return new ScanningStepsFactory(configuration(),"io.jbehavetutorials.steps"); //requires reflection dependency
    }

    @Override
    protected List<String> storyPaths() {
        return new StoryFinder().findPaths(codeLocationFromClass(this.getClass()), "**/*.story", "**/excluded*.story");

    }

}

Is there any way to call these common lifecycle methods separately from different step definition class objects so that a separate webdriver instance is created for different step definition classes.

Prasanta Biswas
  • 761
  • 2
  • 14
  • 26
  • the issue is that each of classes extending `CommonSteps` has own instance of `webDriver`. You should create some storage for WebDriver instance, that will be shared across all step implementations. – VaL May 25 '18 at 09:05
  • But isn't that a bug. It should create separate object of each step class to call it's parent class methods. But instead it is calling by other class object just for the sake of calling the lifecycle methods. – Prasanta Biswas May 26 '18 at 09:48
  • No, it's not a bug. These are basic principles of OOP, you can simulate the same behavior without JBehave: parent class state is not shared across child classes unless it's static – VaL May 28 '18 at 07:59
  • It is not about sharing the state but calling the parent class init functions from it's child classes so that each child object has it's own state. For example, FacebookStatusPost and WalletHubReviewInsurance classes should call setUp and tearDown methods in CommonSteps separately from their own instances so that each object can use it's own initialised webDriver instance. But in JBehave, it is different. The parent class methods are only called from object of of only one of it's child class for both child class steps execution. So webDriver is null for other step class object. – Prasanta Biswas May 30 '18 at 08:43
  • you declare 2 steps class instances that have the same step declared: `new FacebookStatusPost(), new WalletHubReviewInsurance()`. When you run scenario, the first matching step is invoked. In your case step (Given I initialize my chrome browser instance) from `FacebookStatusPost` is always invoked, so `webDriver` from this instance is always initialized, but never from `WalletHubReviewInsurance` – VaL May 30 '18 at 11:41
  • shortly: using inheritance you declare steps from parent class as many times as you extends this class – VaL May 30 '18 at 11:43

1 Answers1

0

A workaround is to make the driver instance static in the CommonSteps class. This is not a concrete solution as it cannot be used with multiple threads running stories concurrently as it would make the driver instance vulnerable.

Prasanta Biswas
  • 761
  • 2
  • 14
  • 26