Site icon Vinsguru

Selenium WebDriver – Scriptless Page Object Design Pattern – Part 3

Overview:

We had already covered 2 articles on the script-less page object design.  If you have not read them already, I would suggest you to read them first in the below order.

In the Part 1, when we created the JSON file, we had hard-coded the test data. Now in this part, we are going to make this data-driven by referring to a CSV sheet for test data.

Sample CSV Data:

Lets assume I have test data as shown here. I would like to access the page and enter the data on the page for each row of the CSV file to make this data-driven.

Lets also assume that first row in the CSV is the column header.

Page Object JSON:

In order to do that, We should NOT want our JSON Page Object to be created in the below format as it hard codes the data.

{
  "q71_patientGender":"Male",
  "q45_patientName[first]":"test",
  "q45_patientName[last]":"automation",
  "q46_patientBirth[month]":"January",
  "q46_patientBirth[day]":"6",
  "q46_patientBirth[year]":"1960"
  ...
}

Instead, we need to refer to the column name of the CSV as shown here.

{
  "q71_patientGender":"${gender}",
  "q45_patientName[first]":"${firstname}",
  "q45_patientName[last]":"${lastname}",
  "q46_patientBirth[month]":"${dob-month}",
  "q46_patientBirth[day]":"${dob-day}",
  "q46_patientBirth[year]":"${dob-year}"
  ...
 }

You could directly update the JSON yourself with the appropriate column name or you could inject below script in the Chrome console.

//inject JQuery
var jq = document.createElement('script');
jq.src = "//code.jquery.com/jquery-3.2.1.min.js";
document.getElementsByTagName('head')[0].appendChild(jq);

//create alias
var $j = jQuery.noConflict();

//inject function for page object model
var eles = {};
var pageObjectModel = function(root){
    $j(root).find('input, select, textarea').filter(':enabled:visible:not([readonly])').each(function(){
        let eleName = this.name;
        eles[eleName] = "${" + eleName + '}';
    });
    console.log(JSON.stringify(eles, null, 4));
}

//invoke the function
pageObjectModel(document)

It would create the Page Object JSON in the below format assuming your CSV file has those columns.

Note: If you do not want to refer to CSV for all the fields, you can use the ${…} expression only for those elements which need to be retrieved from the CSV file

For example, below JSON is perfectly valid. In this case, we want only the Gender to be retrieved from the CSV. All other fields would use the hard coded data.



{
  "q71_patientGender":"${gender}",
  "q45_patientName[first]":"test",
  "q45_patientName[last]":"automation",
  "q46_patientBirth[month]":"January",
  "q46_patientBirth[day]":"6",
  "q46_patientBirth[year]":"1960"
  ...
}

Page Object Parser:

We already have seen this in Part 2 of this article. We have Page Object Parser as shown here which reads the Page Object json file and gives the map of the element names and value to be entered.

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.File;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;

public class PageObjectParser {

    public static Map<String, String> parse(String filename) {
        Map<String, String> result = null;
        try {
            result = new ObjectMapper().readValue(new File(filename), LinkedHashMap.class);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

}

CSV Reader:

We need to read the given CSV file and get all the records. So I create below script using apache-commons csv lib.

import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVRecord;

import java.io.*;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

public class CSVReader {

    public static Stream<CSVRecord> getRecords(final String filename) throws IOException {
        Reader in = new InputStreamReader(new FileInputStream(new File(filename)));
        Iterable<CSVRecord> records = CSVFormat.RFC4180.withFirstRecordAsHeader().parse(in);
        Stream<CSVRecord> stream = StreamSupport.stream(records.spliterator(), false);
        return stream;
    }

}

Data Map:

Our Page Object parse gives a map as shown here.

[
  "q71_patientGender"="${gender}",
  "q45_patientName[first]"="${firstname}",
  "q45_patientName[last]"="${lastname}",
]

Our CSV Reader can give us a map for each row as shown here.

[
    "gender"="Male",
    "firstname"="Michael",
    "lastname"="jackson"
]

But what we really need is a map as shown here

[
  "q71_patientGender"="Male",
  "q45_patientName[first]"="Michael",
  "q45_patientName[last]"="Jackson",
]

So, I am creating below class which is responsible for replacing the ${csvcolmn} in the JSON Page Object with the actual data.

public class DataMap {

    private static final String TEMPLATE_EXPRESSION = "\\$\\{([^}]+)\\}";
    private static final Pattern TEMPLATE_EXPRESSION_PATTERN = Pattern.compile(TEMPLATE_EXPRESSION);

    //json element names and values with ${..}
    final Map<String, String> elementsNameValueMap;
    
    //csv test data for a row
    final Map<String, String> csvRecord;

    public DataMap(Map<String, String> elementsNameValueMap, Map<String, String> csvRecord){
        this.elementsNameValueMap = elementsNameValueMap;
        this.csvRecord = csvRecord;
        
        //this replaces ${..} with corresponding value from the CSV
        this.updateMapWithValues(elementsNameValueMap);
    }

    //this map contains elements names and actual values to be entered
    public Map<String, String> getElementsNameValueMap(){
        return this.elementsNameValueMap;
    }

    private void updateMapWithValues(final Map<String, String> variablesMapping){
        variablesMapping
                .entrySet()
                .stream()
                .forEach(e -> e.setValue(updateTemplateWithValues(e.getValue())));
    }

    private String updateTemplateWithValues(String templateString){
        Matcher matcher = TEMPLATE_EXPRESSION_PATTERN.matcher(templateString);
        while(matcher.find()){
            templateString = this.csvRecord.get(matcher.group(1));
        }
        return templateString;
    }

}

Now you could call CSVreader to get the list of csv records

//get csv data
Stream<CSVRecord> records = CSVReader.getRecords(TEST_DATA_CSV_PATH);

//csv records
records
        .map(CSVRecord::toMap)  //convert each row to a map of key value pair - contains column name and row value
        .map(csvMap -> new DataMap(PageObjectParser.parse(PAGE_OBJECT_JSON_PATH), csvMap)) //feed page object map and csv row map to get the page object element name and actual value
        .map(DataMap::getElementsNameValueMap) // get the map which contains elements name and actual test data from csv
        .forEach(ScriptlessFramework::accessPageEnterData); //this method is responsible for accessing the page and calling elements handler

Below is the method which is responsible for accessing the page and entering the data for each Map it receives.

private static void accessPageEnterData(Map<String, String> map){

    //start webdriver
    WebDriver driver = DriverFactory.getDriver(CHROME);
    driver.get("https://form.jotform.com/81665408084158");

    //enter data
    map.entrySet()
            .stream()
            .forEach(entry -> {
                List<WebElement> elements = driver.findElements(By.name(entry.getKey()));
                ((JavascriptExecutor) driver).executeScript("arguments[0].scrollIntoView(true);", elements.get(0));
                ElementsHandler.handle(elements, entry.getValue());
            });


    //quit
    driver.quit();
    
}

I tried to run with 2 records in a CSV file which works just as we expected.

 

GitHub:

I have uploaded the project here for your reference.

Summary:

By injecting JQuery on the chrome console, We are able to quickly create page objects within few seconds. We modified only those field names in the JSON for which we would be referring the data from the CSV file. Now the ScriptlessFramework engine parses the given CSV file and Page object json ans enters the data for each row of the CSV file. You could further enhance this approach to create a workflow / include page validation etc.

 

Happy Testing & Subscribe 🙂

 

 

 

 

Share This:

Exit mobile version