Hot questions for Using Cucumber in ui automation

Top Java Programmings / Cucumber / ui automation

Question:

I am having difficulty understanding and utilizing dependency injection in my situation. I want to use Pico-container (https://cucumber.io/blog/2015/07/08/polymorphic-step-definitions).

This is my situation...I currently have one step definition class that contains all my selenium, and which is getting way too big:

public class StepDefinitions{
    public static WebDriver driver; // a driver is returned here from a static Driver Factory Class
    LoginPage loginPage = new LoginPage(driver); //Page Object Model(s)

    @Before("setup")
    @After //screen snapshot
    @After("destroy")
    @Given //many methods with this tag
    @When  //many methods with this tag
    @Then  //many methods with this tag
}

Now I want to potentially have one class that contains my driver, POMs, and Hooks:

public static WebDriver driver; //driver is returned from a static Driver Factory Class
LoginPage loginPage = new LoginPage(driver); //Page Object Model(s)

 @Before("setup")
 @After
 @After("destroy")

Another class that contains my @Given, one class that contains my @When, and one class that contains my @Then

I then need to connect everything up right, so all classes can utilize the driver, hooks, and POMs. Cucumber does not support inheritance, so interfaces or Dependency Injection (Pico Container) is the way to go. I do not know how to do this, and I have studied online, I just cannot wrap my poor brain around it all.


Answer:

You might be interested in my blog post where I use Pico Container to share state between two different Cucumber-JVM step classes, http://www.thinkcode.se/blog/2017/04/01/sharing-state-between-steps-in-cucumberjvm-using-picocontainer

Question:

I am new to automation UI testing and I am working on UI automation using Cucumber and Selenium.

So in my project, I created a Hook class to set up the web driver for testing. Something like this:

System.setProperty("webdriver.chrome.driver", "driver path");
driver = new chrome driver;

But if I want to run the same test with different browsers and different environments. For example, I want to run tests with chrome and environment A and run the same test with firefox and environment B. I plan to create two properties files for different environments

env=qa
baseURl = http://qa......
username = test
password = test

and

env=dev
baseURl = http://dev......
username = test1
password = test1

And I want to just put a maven command like

mvn clean test broswer=chrome env=qa

to pick the properties from correct files and set the web driver according to the browser parameter.

Is it possible to do that? Any example for this kind of scenarios?


Answer:

Using properties is a good idea. You are on the right track.

In order to load different properties, based on the environment, you can create multiple folders for properties files.

Let's assume that you store properties files under src/main/java/dev/properties.properties

Create multiple directories like:

src/main/java/qa
src/main/java/dev
src/main/java/prod

Create 1 properties file in each of the paths, just like I did at the beginning.

Now, you want to load correct properties with maven. You can use maven profiles to do that.

To your pom.xml add:

</dependencies>
    <profiles>
        <profile>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <id>dev</id>
            <properties>
                <configuration.path>src/main/dev</configuration.path>
            </properties>
        </profile>
        <profile>
            <activation>
                <activeByDefault>false</activeByDefault>
            </activation>
            <id>prod</id>
            <properties>
                <configuration.path>src/main/prod</configuration.path>
            </properties>
        </profile>
    </profiles>
</project>

Right under </dependencies>

As you can see, I've created 2 profiles. Their IDs are dev and prod. Both profiles have a configuration.path property pointing to your .properties file. To run the profile with maven commend you simply type -PprofileId. -Pdev for example

I hope you use maven-surefire-plugin because that's what I'll show in the example.

<plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.1</version>
                <configuration>
                    <systemPropertyVariables>
                        <configuration.path>${configuration.path}</configuration.path>
                    </systemPropertyVariables>
                </configuration>
            </plugin>

The above is only PARTIAL configuration of surefire-plugin! Let's focus on systemPropertyVariables. In order to get properties from maven profile, you can load them into system, by passing ${configuration.paths} variable into surefire-plugin. You can use the same name.

Now, you need to load properties from the .properties file into the system. I wrote a class to do that, to read configuration.path. You can use other solution.

public class Properties {
    private static java.util.Properties props;

    static {
        props = new java.util.Properties();

        String pathWithPropertiesFiles = System.getProperty("configuration.path");
        String[] paths = pathWithPropertiesFiles.split("[;]");

        Arrays.asList(paths).forEach(propertyPath -> Arrays.asList(Objects.requireNonNull(new File(propertyPath).listFiles())).forEach(propertyFile -> {
            InputStream input;
            try {
                input = new FileInputStream(propertyFile);
                props.load(input);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }));
    }

    public static String getValue(String key) {
        String envProperty = System.getenv(key);
        if (envProperty != null && !envProperty.equals("null")) {
            return envProperty;
        }

        String systemProperty = System.getProperty(key);
        if (systemProperty != null && !systemProperty.equals("null")) {
            return systemProperty;
        }

        return props.getProperty(key);
    }
}

The above solution allows you to pass multiple paths into configuration.path the property, separated with ;.

If you want to open the correct URL, you just use Properties.getValue("baseURL"); It will get the URL from the correct path, based on the profile you selected.

Now, you can do similar stuff but for the browser. I strongly suggest reading about BrowserFactory or Factory design pattern. I can only provide you with hints on how to do that because my solution will probably won't work as you want.

Create additional profiles (you can use multiple profiles with maven) but for the browser.

<profile>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <id>chrome</id>
            <properties>
                <browser.type>chrome</browser.type>
            </properties>
        </profile>

Invokation: -Pchrome

Remember to add it to surefire-plugin:

<configuration>
                    <systemPropertyVariables>
                        <configuration.path>${configuration.path}</configuration.path>
                        <browser.type>${browser.type}</browser.type>
                    </systemPropertyVariables>
                </configuration>

All you have to do now, is think about, where do you instantiate your browser.

Simple solution would be (NON-THREAD SAFE!!!):

public class Browser {

    public static WebDriver getDriver() {
        String browserType = Properties.getValue("browser.type"); //it will get the `chrome` for profile `chrome`
        switch(browserType) {
            case "chrome": return new ChromeDriver();
        }
    }
}

All you have to do now is implement the solution in your framework. Add profiles, fill in the switch statement with all of the browsers used.

Hope it helps!

Question:

Currently all my step definitions are only accepting element ids in order to take action on the webpage.

driver.findElement(By.id("id"));

But what if I wanted to pass in a css selector, tag, link or xpath? I don't want to have to re-write all my step definitions for all these scenarios (or create multiple and identical step def), not knowing which one will be passed.

driver.findElement(By.cssSelector("css"));
driver.findElement(By.link("link"));
driver.findElement(By.tagName("tag"));
driver.findElement(By.xpath("xpath"));

Is there a switch statement I can use, that will determine what kind of locator it is being passed, and then go on to perform the action accordingly?


Answer:

You can create a helper class to return By according to different locator string.

// feature file
Secnario Outline: Test user login
  Given ...
  And user input username: <value> into <element>
  And user input password: <value> into <element>

  Examples:
    | value | element |
    | user1 | id:username |
    | pwd1  | css:input.pwd |

// helper class to build Locator
public class Locator {

  public static By build(locator) {
    String[] parts = locator.split(":");
    String use = parts[0].trim().lowerCase();
    String value = parts[1].trim();

    if(use.equals("id")) {
      return By.id(value);
    }
    else if(use.equals("css")){
      return By.css(value);
    }
    .....
  }
}

// step definition
Then("^user input username: (.+) into (.+)$", 
      (String inputValue, String locatoExp) -> {

    driver.findElement(Locator.build(locatoExp)).sendKeys(inputValue);
});