Custom Elements in Protractor

What is Custom WebElements in Protractor ?
Custom web elements in protractor are some elements present in the webpage but to handle them we don't have any standard feature in protractor, these elements are Custom WebElements.

Sometimes we may want access few webelement like tables, bootstrap dropdowns and so on, during those time we tend to write the complete code every time, or we call some method to perform the operation but in the longer run, this way doing becomes more and more difficult to handle with the protractor.

But if we create Custom Webelements, it helps us to ease the process and as well as narrows down the learning curve.

This tutorial focuses on how to handle dynamic table which changes its rows position on every refresh, you might have a table which is populated using databases

Promise in Protractor

Dynamic Table shuffles on refresh

Please do perform a refresh to confirm the table is dynamic

Select Website Field

Protractor Architecture

Custom WebTable in Selenium

WebElement interface provides methods to perform operations on the various webelements, we have protractor layer over webdriver to handle the ajax and angular applications

Protractor provides helper classes to work with the web element which are formed HTML tags. Anyways Protractor does not support the tables on a webpage or the <table> elements

We will be using functions from the WebElement to perform operations on Webtable, we will be considering the web table is formed using <table>, <tr>, <td> HTML tags.

WebTable Methods :

To access the details of the WebTable we may need to implement a few methods like:

  • Accept WebTable element
  • Get number of rows
  • Get number of columns
  • Get the size of the table
  • Get all the data from a row
  • Get all the date from column
  • Get the total data
  • Verify Presence of Data
  • Get data from Specific cell
  • Click the checkbox based on the value given (your favorite)

Headless Chrome with Protractor

Accept WebTable Element :

We need to accept the table element on which we are going to perform the operations. We will be accepting the web table as a parameter for the WebTable() class constructor in Typescript, if you are using pure JS then you might accept it to a method.

Constructor assigns the Webtable element into global non-static variable webTable, we are assigning the value into global as we want to use the value across the class.


// webtable 
webTable:ElementFinder; 
//constructor  accepts dropdown as element
constructor(webTableElement:ElementFinder) { 
	this.webTable = webTableElement; 
} 

Get number of Rows :

Sometimes a user may request for a number of rows present in the table, in such cases, we should be able to give details. tr tags are used to form the table rows in HTML, so if we can get the number of tr present in our table element then we got our number of rows.

We have subtracted 1 from the total size because all the rows including header are also formed using tr HTML tags, so we subtracted 1 to exclude the headers. We are returning the size to the calling method.


// get the number of rows present
public  getRowCount(){
	console.log("Fetching number rows")
	return this.webTable.all(by.css("tr")).count()
}

Get number of Columns :

We can give the count of columns present in the table by counting the number of td HTML tags present in the table, tdHTML tags are used to form the cells.

We have used xpath //tr[2]/td for find the number of columns, If we use //tr[1] then we may need to use th HTML tags as tr[1] point to headers and header will not have the td tags.


// get the number of columns present
public getColumnCount(){
	return this.webTable.all(by.css("th")).count()
	// if you donot have header then above will not work
	// use this if no headre is there
	// return this.webTable.all(by.xpath("//tr[0]/td")).count()
}

Get the size of the table :

Few people may expect out WebTable element to give the details about the total size of the table instead of giving number of rows, number of columns separately. In this case we may need to return the Map of Key Value pairs which contains count of rows and columns.

Instead of creating new method implementation we can use the getRowCount, getColumnCount to get the row count and column count. This method returns a Map of values, Map consists of String as keys and Integer as Value.


 // get the number of rows and columns and return it as Map
public getTableSize(){
	return {row: this.webTable.all(by.css("tr")).count(),
	 columns: this.webTable.all(by.css("th")).count()}
}

Array of Elements

Get all the data from a Row :

Sometimes user may request the WebTable class to get a particular row values, we can get the row values by iterating the all the td HTML elements present under that row.

This method gets all the td HTML element from a specific row and iterates that element to store the text from it. We would be adding the text into a list, so we can return the list at the end of the method.

We want to exclude the header and xpath index starts from 1, so we are adding +1 to the user passed value. We have to throw the exception when the user passes 0 as value as all rows start from 1.


// get row data and return it as list
public rowData(rowNumber:number){
	if(rowNumber == 0){
		throw new Error("Row number starts from 1");
	}
	rowNumber = rowNumber + 1;
	return this.webTable.all(by.xpath("//tr["+rowNumber+"]/td")).getText()   
}

Get all the data from a Column :

Similar to a row, user may need the values from a particular column. To get the values from column we need to get all the td HTML elements based on the column number passed in the method.

In this case, we will be just iterating only cell, //tr/td avoids the headers so no need perform any action regarding that, but we still have to throw an exception when the user passes 0.


// get the column data and return a list in protractor
public columnData(columnNumber:number){
	if(columnNumber == 0){
		throw new Error("Column number starts from 1");
	}
	columnNumber = columnNumber + 1;
	return this.webTable.all(by.xpath("//tr/td["+columnNumber+"]")).getText()   
}

Get the total data :

When we design a web table in handling methods in protractor we must be in a position that we should be able to return all the data present in the table in Map format.

All the rows in an HTML table are formed using tr tags, and all the columns are formed using td tags. If there are 10 columns present under the row in a webtable, which means there are 10 td tags are present under the tr tag

To get all the data from the table we have to iterate each and every row present under the web table, once we get the rows we must iterate the td tags present under that particular row


// get all the data from the table
	public getAllData(){
		return this.webTable.all(by.xpath("td")).getText()
    }

Verify the presence of given data :

We also should give an option to the user to check whether a particular data is present in the table or not, we can use xpath to verify whether given data is present or not by using the text() function with the xpath in protractor

There could be more than one data matching, so we have used all function to find how many elements present. if one or more elements present this method returns true but this method returns false if the number of elements is 0


// verify presence of the text/data
public presenceOfData(data:string){
	// verify the data by getting the size of the element matches based on the text/data passed
	return this.webTable.all(by.xpath
	("//td[normalize-space(text())='"+data+"']"))
	.size().then(function(dataSize){
		if(dataSize > 0){
			return true;
		}
		else{
			return false
		}
	})
}

SendKeys method in protractor


Get data from Specific Cell :

We should return the data from a specific cell when user requests, we can do it form the right xpath.

We have to exclude the Header row for that we would be adding +1 to the row requested by the user.


 // get the data from a specific cell
public getCellData(rowNumber:number, columnNumber:number) {
	if(rowNumber == 0){
		throw new Error("Row number starts from 1");
	}
	rowNumber = rowNumber+1;
	let cellData = this.webTable.element(by.xpath
	("//tr["+rowNumber+"]/td["+columnNumber+"]")).getText();
	return cellData;
}

Click the checkbox based on the value given :

Handling dynamic table is not a big deal, the trick is with xpath. For example: If you want to click the checkbox related Chercher.tech

You have to find your target element(Chercher.tech), and you can find the checkbox now select the checkbox related to the Chercher.tech.

  • find the Chercher.tech using normalize-space xpath
  • Now navigate to its parent using ..
  • Check whether you have your text and the checkbox under same parent if not use the .. again till you have both elements under the same parent, this concept is called as dependent and independent xpath</li>
  • Once you have them under the same parent now narrow down to checkbox using some of its properties like name id or tag
  • I am using tagname (input) as css selector in protractor to find the checkbox, if you have more than one checkbox you got to get your input element using the properties like normal xpath
  • Tada, you got the xpath now use it to find the element and click it.
  • I have not added, more features like verifying the checkbox is already selected or not, please do add feature according to your need.

Whether alert present or not

I have created dependent and independent xpath, please do learn it


// click checkbox with protractor
public clickCheckBox(data:string){
	this.webTable.element(by.xpath
	("//td[normalize-space(text())='"+data+"']/..//input")).click()
}


Complete program for Custom Webelement 'WebTable'


import {browser, by, ElementFinder } from "protractor";

export class Select { 
    // webtable 
    webTable:ElementFinder; 
    //constructor  accepts dropdown as element
    constructor(webTableElement:ElementFinder) { 
       this.webTable = webTableElement; 
    } 

    // get the number of rows present
    public  getRowCount(){
        console.log("Fetching number rows")
        return this.webTable.all(by.css("tr")).count()
    }

    // get the number of columns present
	public getColumnCount(){
        return this.webTable.all(by.css("th")).count()
        // if you donot have header then above will not work
        // use this if no headre is there
        // return this.webTable.all(by.xpath("//tr[0]/td")).count()
    }
    
    // get the number of rows and columns and return it as Map
	public getTableSize(){
        return {row: this.webTable.all(by.css("tr")).count(),
         columns: this.webTable.all(by.css("th")).count()}
    }
    
    // get row data and return it as list
    public rowData(rowNumber:number){
        if(rowNumber == 0){
            throw new Error("Row number starts from 1");
        }
        rowNumber = rowNumber + 1;
        return this.webTable.all(by.xpath("//tr["+rowNumber+"]/td")).getText()   
    }

    // get the column data and return as list
    public columnData(columnNumber:number){
        if(columnNumber == 0){
            throw new Error("Column number starts from 1");
        }
        columnNumber = columnNumber + 1;
        return this.webTable.all(by.xpath("//tr/td["+columnNumber+"]")).getText()   
    }

    // get all the data from the table
	public getAllData(){
		return this.webTable.all(by.xpath("td")).getText()
    }
    
    // verify presence of the text/data
	public presenceOfData(data:string){
		// verify the data by getting the size of the element matches based on the text/data passed
		return this.webTable.all(by.xpath("//td[normalize-space(text())='"+data+"']"))
		.size().then(function(dataSize){
            if(dataSize > 0){
                return true;
            }
            else{
                return false
            }
        })
    }
    // get the data from a specific cell
	public getCellData(rowNumber:number, columnNumber:number) {
		if(rowNumber == 0){
			throw new Error("Row number starts from 1");
		}
		rowNumber = rowNumber+1;
		let cellData = this.webTable.element(by.xpath
		("//tr["+rowNumber+"]/td["+columnNumber+"]")).getText();
		return cellData;
    }
    // click checkbox with protractor
    public clickCheckBox(data:string){
        this.webTable.element(by.xpath
		("//td[normalize-space(text())='"+data+"']/..//input")).click()
    } 
 }

Protractor Interview Questions


Test Custom WebTable :

We have to test our Protractor Custom element before we use it in framework, let's test it.

We will be using the table present at the top of this page. I hope the test code is pretty simple, please comment in the if you have trouble in understanding.


import { browser, element, by} from 'protractor'
import { WebTable } from './WebTable';
describe('Protractor Typescript Demo', function() {
	browser.ignoreSynchronization = true;
	it('Dynamic table in Protractor ', function() {
		browser.get('https://chercher.tech/protractor/custom-webelement-dynamic-table-protractor');
    browser.sleep(1000)
    
    let webTable = new WebTable(element(by.id("fixed-header")));
    webTable.getRowCount().then(function(noOfRows){
      console.log("No of rows : "+noOfRows)
    })

    webTable.getColumnCount().then(function(noOfColumns){
      console.log("No of Columns : "+noOfColumns)
    })

    webTable.getTableSize().row.then(function(rows){
      console.log("No Of rows : " +rows)
    })

    webTable.getTableSize().columns.then(function(cols){
      console.log("Nof Of columns : " +cols)
    })

    webTable.rowData(1).then(function(rowData){
      console.log("Data in row : " +rowData)
    })

    webTable.columnData(1).then(function(colData){
      console.log("Data in Column : " +colData)
    })

    webTable.getCellData(1, 2).then(function(cellData){
      console.log("Cell data : " +cellData)
    })

    console.log("Presence of data : "+webTable.presenceOfData("google"))

    webTable.getAllData().then(function(totalData){
      console.log("Cell data : " +totalData)
    })

    webTable.clickCheckBox("Chercher.tech")
	});
});


Output :

webtable-custom-webelement-selenium-webdriver

Capture Screenshot in protractor

Comment / Suggestion Section
Point our Mistakes and Post Your Suggestions
  • Reply
  • Manoj KUmar
    Can you please create a video on scrollIntoView
    Reply
  • Protractor Training

    new tutorial I am starting new Protractor training batch on Weekends. Course starts from 08-june-2019 [10am IST to 1 pm IST] based on online for 6 days, and course fee is INR 10, 000.

    Interested people can confirm the seat by calling to +91 8971673487 or whatsapp to +91 9003381224

    Find the course content : View Content