Saturday, April 1, 2017

Loading Data From External File To Feature File Using Cucumber - JVM

On previous post we learn how to read data from external file using mentioning the Row number in feature file. Now we load data in feature file from external data source file before start to execute testable features.

We need to override the feature file with data from external data source(excel, csv etc) before executing the feature file. So if we want to do that we need to follow the below steps :

Step 1: 

We need to put tag on feature file because our parser need to understand where it will put the data within the feature file. That's why we specify the tag in feature file in the below pattern

##@externaldata@Excel file Location@SheetName

e.g ##@externaldata@./src/test/resources/TestData.xlsx@Sheet1

Step 2:

Read Excel file and store it List<Map<String columnName, String columnvalue>> .

Step 3: 

Write a parser to copy all the data from specified excel sheet to feature file.

Step 4:

Create custom Annotation because we need to copy all data from excel file to feature file before start to execute.

Step 5:

Create custom cucumber runner because we need to control cucumber runner to run our custom annotation at first.

Step 6 :
Create Runner and use custom runner from there.

So Complete code given below :

pom.xml :

<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>com.blogspot.startingwithseleniumwebdriver</groupId>
    <artifactId>CucumberFeatureModification</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <dependencies>
        <!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-java -->
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-java</artifactId>
            <version>5.5.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-junit -->
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-junit</artifactId>
            <version>5.5.0</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>4.1.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>4.1.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>3.141.59</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
        </dependency>
    </dependencies>
</project>


Our Feature :

Feature: This is a sample feature file

  Background:
    Given scenario data

  Scenario Outline: This is a scenario to test datadriven test on Cucumber JVM.
    When executed from Runner Class.
    Then UserName and Password shows on console from Examples "<UserName>" and "<Password>".

    Examples:
      | UserName | Password |
          ##@externaldata@./src/test/resources/data/TestData.xlsx@Sheet1


  Scenario: This is a scenario to test datatable test on Cucumber JVM.
    Then UserName and Password shows on console from datatable.
      | UserName | Password |
      ##@externaldata@./src/test/resources/data/TestData.xlsx@Sheet2


Excel reader : 

package com.automation.cucumber.helper;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.ss.util.NumberToTextConverter;

public class ExcelReader {

    public List<Map<String, String>> getData(String excelFilePath, String sheetName)
            throws InvalidFormatException, IOException {
        Sheet sheet = getSheetByName(excelFilePath, sheetName);
        return readSheet(sheet);
    }

    public List<Map<String, String>> getData(String excelFilePath, int sheetNumber)
            throws InvalidFormatException, IOException {
        Sheet sheet = getSheetByIndex(excelFilePath, sheetNumber);
        return readSheet(sheet);
    }

    private Sheet getSheetByName(String excelFilePath, String sheetName) throws IOException, InvalidFormatException {
        Sheet sheet = getWorkBook(excelFilePath).getSheet(sheetName);
        return sheet;
    }

    private Sheet getSheetByIndex(String excelFilePath, int sheetNumber) throws IOException, InvalidFormatException {
        Sheet sheet = getWorkBook(excelFilePath).getSheetAt(sheetNumber);
        return sheet;
    }

    private Workbook getWorkBook(String excelFilePath) throws IOException, InvalidFormatException {
        return WorkbookFactory.create(new File(excelFilePath));
    }

    private List<Map<String, String>> readSheet(Sheet sheet) {
        Row row;
        int totalRow = sheet.getPhysicalNumberOfRows();
        List<Map<String, String>> excelRows = new ArrayList<Map<String, String>>();
        int headerRowNumber = getHeaderRowNumber(sheet);
        if (headerRowNumber != -1) {
            int totalColumn = sheet.getRow(headerRowNumber).getLastCellNum();
            int setCurrentRow = 1;
            for (int currentRow = setCurrentRow; currentRow <= totalRow; currentRow++) {
                row = getRow(sheet, sheet.getFirstRowNum() + currentRow);
                LinkedHashMap<String, String> columnMapdata = new LinkedHashMap<String, String>();
                for (int currentColumn = 0; currentColumn < totalColumn; currentColumn++) {
                    columnMapdata.putAll(getCellValue(sheet, row, currentColumn));
                }
                excelRows.add(columnMapdata);
            }
        }
        return excelRows;
    }


    private int getHeaderRowNumber(Sheet sheet) {
        Row row;
        int totalRow = sheet.getLastRowNum();
        for (int currentRow = 0; currentRow <= totalRow + 1; currentRow++) {
            row = getRow(sheet, currentRow);
            if (row != null) {
                int totalColumn = row.getLastCellNum();
                for (int currentColumn = 0; currentColumn < totalColumn; currentColumn++) {
                    Cell cell;
                    cell = row.getCell(currentColumn, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
                    if (cell.getCellType() == CellType.STRING) {
                        return row.getRowNum();

                    } else if (cell.getCellType() == CellType.NUMERIC) {
                        return row.getRowNum();

                    } else if (cell.getCellType() == CellType.BOOLEAN) {
                        return row.getRowNum();
                    } else if (cell.getCellType() == CellType.ERROR) {
                        return row.getRowNum();
                    }
                }
            }
        }
        return (-1);
    }

    private Row getRow(Sheet sheet, int rowNumber) {
        return sheet.getRow(rowNumber);
    }

    private LinkedHashMap<String, String> getCellValue(Sheet sheet, Row row, int currentColumn) {
        LinkedHashMap<String, String> columnMapdata = new LinkedHashMap<String, String>();
        Cell cell;
        if (row == null) {
            if (sheet.getRow(sheet.getFirstRowNum()).getCell(currentColumn, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK)
                    .getCellType() != CellType.BLANK) {
                String columnHeaderName = sheet.getRow(sheet.getFirstRowNum()).getCell(currentColumn)
                        .getStringCellValue();
                columnMapdata.put(columnHeaderName, "");
            }
        } else {
            cell = row.getCell(currentColumn, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
            if (cell.getCellType() == CellType.STRING) {
                if (sheet.getRow(sheet.getFirstRowNum()).getCell(cell.getColumnIndex(), Row.MissingCellPolicy.CREATE_NULL_AS_BLANK).getCellType() != CellType.BLANK) {
                    String columnHeaderName = sheet.getRow(sheet.getFirstRowNum()).getCell(cell.getColumnIndex()).getStringCellValue();
                    columnMapdata.put(columnHeaderName, cell.getStringCellValue());
                }
            } else if (cell.getCellType() == CellType.NUMERIC) {
                if (sheet.getRow(sheet.getFirstRowNum()).getCell(cell.getColumnIndex(), Row.MissingCellPolicy.CREATE_NULL_AS_BLANK).getCellType() != CellType.BLANK) {
                    String columnHeaderName = sheet.getRow(sheet.getFirstRowNum()).getCell(cell.getColumnIndex()).getStringCellValue();
                    columnMapdata.put(columnHeaderName, NumberToTextConverter.toText(cell.getNumericCellValue()));
                }
            } else if (cell.getCellType() == CellType.BLANK) {
                if (sheet.getRow(sheet.getFirstRowNum()).getCell(cell.getColumnIndex(), Row.MissingCellPolicy.CREATE_NULL_AS_BLANK).getCellType() != CellType.BLANK) {
                    String columnHeaderName = sheet.getRow(sheet.getFirstRowNum()).getCell(cell.getColumnIndex()).getStringCellValue();
                    columnMapdata.put(columnHeaderName, "");
                }
            } else if (cell.getCellType() == CellType.BOOLEAN) {
                if (sheet.getRow(sheet.getFirstRowNum()).getCell(cell.getColumnIndex(), Row.MissingCellPolicy.CREATE_NULL_AS_BLANK).getCellType() != CellType.BLANK) {
                    String columnHeaderName = sheet.getRow(sheet.getFirstRowNum()).getCell(cell.getColumnIndex()).getStringCellValue();
                    columnMapdata.put(columnHeaderName, Boolean.toString(cell.getBooleanCellValue()));
                }
            } else if (cell.getCellType() == CellType.ERROR) {
                if (sheet.getRow(sheet.getFirstRowNum()).getCell(cell.getColumnIndex(), Row.MissingCellPolicy.CREATE_NULL_AS_BLANK).getCellType() != CellType.BLANK) {
                    String columnHeaderName = sheet.getRow(sheet.getFirstRowNum()).getCell(cell.getColumnIndex()).getStringCellValue();
                    columnMapdata.put(columnHeaderName, Byte.toString(cell.getErrorCellValue()));
                }
            }
        }
        return columnMapdata;
    }
}

 

Parser To Copy Data From Excel

package com.automation.cucumber.helper;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang3.StringUtils;

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;

public class FeatureOverright {

    private static List<String> setExcelDataToFeature(File featureFile)
            throws InvalidFormatException, IOException {
        List<String> fileData = new ArrayList<String>();
        try (BufferedReader buffReader = new BufferedReader(
                new InputStreamReader(new BufferedInputStream(new FileInputStream(featureFile)), "UTF-8"))) {
            String data;
            List<Map<String, String>> excelData = null;
            boolean foundHashTag = false;
            boolean featureData = false;
            while ((data = buffReader.readLine()) != null) {
                String sheetName = null;
                String excelFilePath = null;
                if (data.trim().contains("##@externaldata")) {
                    excelFilePath = data.substring(StringUtils.ordinalIndexOf(data, "@", 2)+1, data.lastIndexOf("@"));
                    sheetName = data.substring(data.lastIndexOf("@")+1, data.length());
                    foundHashTag = true;
                    fileData.add(data);
                } if (foundHashTag) {
                    excelData = new ExcelReader().getData(excelFilePath, sheetName);
//                    String columnHeader = "";

//                    for (Entry<String, String> map : excelData.get(0).entrySet()) {
//                        columnHeader = columnHeader + "|" + map.getKey();
//                    }
//                  
//                    fileData.add(columnHeader + "|");

                    for (int rowNumber = 0; rowNumber < excelData.size()-1; rowNumber++) {
                        String cellData = "";
                        for (Entry<String, String> mapData : excelData.get(rowNumber).entrySet()) {
                            cellData = cellData + "|" + mapData.getValue();
                        }
                        fileData.add(cellData + "|");
                    }
                    foundHashTag = false;
                    featureData = true;
                    continue;
                }
                if(data.startsWith("|")||data.endsWith("|")){
                    if(featureData){
                        continue;
                    } else{
                        fileData.add(data);
                        continue;
                    }
                } else {
                    featureData = false;
                }
                fileData.add(data);
            }
        }
        return fileData;
    }

    private static List<File> listOfFeatureFiles(File folder) {
        List<File> featureFiles = new ArrayList<File>();
        for (File fileEntry : folder.listFiles()) {
            if (fileEntry.isDirectory()) {
                featureFiles.addAll(listOfFeatureFiles(fileEntry));
            } else {
                if (fileEntry.isFile() && fileEntry.getName().endsWith(".feature")) {
                    featureFiles.add(fileEntry);
                }
            }
        }
        return featureFiles;
    }

    public static void overrideFeatureFiles(String featuresDirectoryPath)
            throws IOException, InvalidFormatException {
        List<File> listOfFeatureFiles = listOfFeatureFiles(new File(featuresDirectoryPath));
        for (File featureFile : listOfFeatureFiles) {
            List<String> featureWithExcelData = setExcelDataToFeature(featureFile);
            try (BufferedWriter writer = new BufferedWriter(
                    new OutputStreamWriter(new FileOutputStream(featureFile), "UTF-8"));) {
                for (String string : featureWithExcelData) {
                    writer.write(string);
                    writer.write("\n");
                }
            }
        }
    }
}

 

Custom Annotation :

package com.automation.cucumber.helper;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target(METHOD)
public @interface BeforeSuite {

}


Custom Runner :

package com.automation.cucumber.runner;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;

import com.automation.cucumber.helper.BeforeSuite;
import cucumber.api.junit.Cucumber;


public class CustomCucumberRunner extends Runner {

    private Class<Cucumber> classValue;
    private Cucumber cucumber;

    public CustomCucumberRunner(Class<Cucumber> classValue) throws Exception {
        this.classValue = classValue;
        cucumber = new Cucumber(classValue);
    }

    @Override
    public Description getDescription() {
        return cucumber.getDescription();
    }

    private void runAnnotatedMethods(Class<?> annotation) throws Exception {
        if (!annotation.isAnnotation()) {
            return;
        }
        Method[] methods = this.classValue.getMethods();
        for (Method method : methods) {
            Annotation[] annotations = method.getAnnotations();
            for (Annotation item : annotations) {
                if (item.annotationType().equals(annotation)) {
                    method.invoke(null);
                    break;
                }
            }
        }
    }

    @Override
    public void run(RunNotifier notifier) {
        try {
            runAnnotatedMethods(BeforeSuite.class);
            cucumber = new Cucumber(classValue);
        } catch (Exception e) {
            e.printStackTrace();
        }
        cucumber.run(notifier);
    }
}

Cucumber Runner :

package com.automation.cucumber.runner;

import java.io.IOException;

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.junit.runner.RunWith;

import com.automation.cucumber.helper.BeforeSuite;
import com.automation.cucumber.helper.FeatureOverright;

import io.cucumber.junit.CucumberOptions;
import io.cucumber.junit.CucumberOptions.SnippetType;

@CucumberOptions(plugin = { "html:target/cucumber-html-report", "json:target/cucumber.json" },
                features = {"src/test/resources/features" },
                glue = {"com/automation/cucumber/steps" },
                monochrome = true, snippets = SnippetType.CAMELCASE)

@RunWith(CustomCucumberRunner.class)
public class CucumberRunnerTest {
    @BeforeSuite
    public static void test() throws InvalidFormatException, IOException {
        FeatureOverright.overrideFeatureFiles(System.getProperty("user.dir")+"/src/test/resources/features");
    }
}


Step class : 
package com.automation.cucumber.steps;

import cucumber.api.DataTable;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;

public class TestSteps {
   
    @Given("scenario data")
    public void scenario_data() {
        System.out.println("Scenario Have Some Data");
    }

    @When("executed from Runner Class.")
    public void executed_from_Runner_Class() {
        System.out.println("Executed From Runner Class");
    }

    @Then("UserName and Password shows on console from Examples {string} and {string}.")
    public void username_and_Password_shows_on_console_from_Examples_and(String userName, String password) {
        System.out.println("UserName : " + userName + " Password : " + password);
    }

    @Then("UserName and Password shows on console from datatable.")
    public void username_and_Password_shows_on_console_from_datatable(DataTable dataTable) {
        System.out.println("Data Table UserName : " + dataTable.asLists().get(1).get(0) + " Password : " + dataTable.asLists().get(1).get(1));
        System.out.println("Data Table UserName : " + dataTable.asList().get(2) + " Password : " + dataTable.asList().get(3));
        System.out.println("Data Table UserName : " + dataTable.asMaps().get(0).get("UserName") + " Password : " + dataTable.asMaps().get(0).get("Password"));
        System.out.println("Data Table UserName : " + dataTable.asMaps(String.class, String.class).get(0).get("UserName") + " Password : " + dataTable.asMaps(String.class, String.class).get(0).get("Password"));
    }
}

19 comments:

  1. Hola, la información es excelente me ha sido de gran ayuda.

    ¿Usted sabe como se puede ejecutar desde la clase runner Serenity, es decir @RunWith(CucumberWithSerenity.class)?

    Gracias

    Hello, the information is excellent has been very helpful.

    Do you know how it can be run from the Serenity runner class, ie @RunWith (CucumberWithSerenity.class)?

    Thank you

    ReplyDelete
  2. Hello
    Does the excel file need to have any specific layout?
    I followed the code above, however I get the following when I run the CucumberRunnerTest
    0 Scenarios
    0 Steps
    0m0.000s


    Process finished with exit code 0

    ReplyDelete
    Replies
    1. Yeah...! The excel should be in order same as feature file. If u need further clarification, will explain u!

      Delete
  3. Even I am facing the same issue.

    0 Scenarios
    0 Steps
    0m0.000s

    ReplyDelete
  4. Is there a way to run this code with TestNG instead?

    ReplyDelete
  5. i am getting error in step class at .aslists

    ReplyDelete
  6. can you post the how to use excel data to use in sendkeys

    ReplyDelete
  7. Hola, la información es excelente me ha sido de gran ayuda.

    ¿Usted sabe cómo se puede ejecutar desde la clase runner Serenity, es decir @RunWith (CucumberWithSerenity.class)?

    Gracias

    Hola, la información es excelente ha sido de gran ayuda.

    ¿Sabes cómo se puede ejecutar desde la clase de corredor Serenity, es decir, @RunWith (CucumberWithSerenity.class)?

    Gracias

    ReplyDelete
  8. Hi Friend,

    I am getting error using above code. Could you please help me

    ReplyDelete
  9. @Arjun Ray

    I am getting an erorr in CustomCucumberRunner.java. Please help me in resolving the issue..

    ReplyDelete
  10. You can find the code in git
    https://github.com/munisekhar1236/cucumber-external-data-load

    ReplyDelete
  11. I got this error when running
    Running myproject.MyProjectTest
    Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.102 sec <<< FAILURE! - in myproject.MyProjectTest
    initializationError(myproject.MyProjectTest) Time elapsed: 0.014 sec <<< ERROR!
    cucumber.runtime.CucumberException: Error parsing feature file H2H-3628.feature
    at cucumber.runtime.FeatureBuilder.parse(FeatureBuilder.java:133)
    at cucumber.runtime.model.CucumberFeature.load(CucumberFeature.java:52)
    at cucumber.runtime.model.CucumberFeature.load(CucumberFeature.java:33)
    at cucumber.runtime.RuntimeOptions.cucumberFeatures(RuntimeOptions.java:158)
    at cucumber.api.junit.Cucumber.(Cucumber.java:63)
    at custom.CustomCucumberRunner.(CustomCucumberRunner.java:21)
    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.junit.internal.builders.AnnotatedBuilder.buildRunner(AnnotatedBuilder.java:29)
    at org.junit.internal.builders.AnnotatedBuilder.runnerForClass(AnnotatedBuilder.java:21)
    at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
    at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:26)
    at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
    at org.junit.internal.requests.ClassRequest.getRunner(ClassRequest.java:26)
    at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:283)
    at org.apache.maven.surefire.junit4.JUnit4Provider.executeWithRerun(JUnit4Provider.java:173)
    at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:153)
    at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:128)
    at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:203)
    at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:155)
    at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:103)
    Caused by: java.lang.NullPointerException: null


    can you help me?

    ReplyDelete