Custom Elements in Protractor

What is Custom WebElements in Protractor ?
Custom webelements 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 operation but in longer run this way doing becomes more and more difficult to handle with 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 table which are populated using databases

Dynamic Table shuffles on refresh

Please do perform a refresh to confirm the table is dynamic

Select Website Field

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 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 implement 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)

Accept WebTable Element :

We need to accept the table element on which we are going to perform the operations. We will be accepting the webtable as 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 user may request for 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 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 cab use the getRowCount, getColumnCount to get the row count and column count. This methods returns a Map of values, Map consists String as keys and Integer as Value.


 // get the nuber 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 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 the all the td HTML element from a specific row, and iterates those element to store the text from it. We would be adding the text into 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 throw the throw the exception when user passes 0 as value as all rows starts 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 exception when user passes 0.


// get the column data and return as 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 a HTML table are formed using tr tags, and all the columns are formed using td tags. If there are 10 columns present under 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 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 methods returns false if 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
		}
	})
}


Get data from Specific Cell :

We should return the data from 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 the your target element(Chercher.tech), and you can find the check box now select the check box 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 check box under same parent if not use the .. again till you have both elements under same parent, this concept is called as dependent and independent xpath</li>
  • Once you have them under same parent now narrow down to check box 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 he checkbox is already selected or not, please do add feature according to your need.
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 nuber 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, lets test it.

We will be using the table present in 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. It is two days course from 01-june-2019 to 02-june-2019 based on class room (BANGALORE)

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

    Find the course content : View Content