Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Operates WebDriver Elements Efficiently

5.00/5 (2 votes)
13 Jun 2014CPOL2 min read 14.3K  
This tip summarizes how to using WebDriver to read/write web elements efficiently.

Introduction

Recently, I carried out some automation tests on a Single Page Application using WebDriver and I have developed my own framework to avoid redundant codes. It is quite challenging to even try to input a string into an AJAX auto-suggested text input, choose an option from an select when 'optiongrp' is used or access a cell from a table.

As a summary of my past work, as well as the first step to present my framework, I would like to introduce the very basic operations on some common HTML elements.

Input Text to a Text Field

It might seem to be too simple when everyone know that "SendKey(string text)" should work. However, in most cases, SendKey would append text to the existing one if the text field (input, textArea) is not cleared.

So a common practice need clear it first before calling SendKey():

C#
IWebElement element = theWebDriver.findElement(By.Id("textId"));
...
element.Clear();
element.SendKey("text to be input");

Another option, instead, is using "Ctrl+A" to select the existing text first before doing the real input:

C#
public static readonly string Control = Convert.ToString
(Convert.ToChar(0xE009, CultureInfo.InvariantCulture), CultureInfo.InvariantCulture);
public static readonly string Tab = Convert.ToString(Convert.ToChar
(0xE004, CultureInfo.InvariantCulture), CultureInfo.InvariantCulture);
public static readonly string Control_a = Control + "a";

IWebElement element = theWebDriver.findElement(By.Id("textId"));
...
element.SendKey(Control_a);
element.SendKey("text to be input");

This, however, is not enough for auto-completed text fields empowered by Angular for example, there would be an alert dialog pinned when I perform the following operations. Again, considering the human operations in such scenario, the problem can be solved by Sending a TAB to the field. So finally, the preferred way for me is:

C#
public static readonly string Tab = Convert.ToString
(Convert.ToChar(0xE004, CultureInfo.InvariantCulture), CultureInfo.InvariantCulture);
...


element.SendKey(Control_a);
?element.SendKey("text to be input" + Tab);

Select an Option from a Dropdown List

The proposed way by Selenium to select an option from a dropdown list is:

C#
SelectElement select = new SelectElement(driver.FindElement(By.TagName("select")));
select.DeselectAll();
select.SelectByText(Edam");

However, it doesn't work when "optiongrp" is used to contain multiple options. A very simple approach to cope with it is also operate like we do in a browser with SendKeys():

C#
IWebElement element = theWebDriver.findElement(By.Id("selectId"));
...
element.SendKey("Edam" + Tab);

In this way, we can only select option by its text instead of its value, but that can be done by matching the values before "SendKey()" and should be enough for most cases.

Accessing Table

As a collection of table rows/cells, accessing a specific cell of the table is quite problematic, the following discussion is focused only on tables whose rows have the same number of cells.

Basically, there are 3 steps to sort out the structure of the table:

  1. Get all rows (or child elements whose tagname is "tr");
  2. Parsing the first row to see if it contains headers (elements whose tagname is "th") and store the header names;
  3. Keep the actual row number and column number for further operations.
C#
public int RowCount { get; private set; }
public int ColumnCount { get; private set; }
public List<string> Headers { get; private set; }
public bool WithHeaders { get; private set; }

ReadOnlyCollection<IWebElement> rows = driver.FindElements(By.CssSelector("tr"));
IWebElement firstRow = rows[0];
var headers = firstRow.FindElements(By.CssSelecor("th")).ToList();
RowCount = rows.Count();
if (headers.Count() != 0)
{
    WithHeaders = true;
    ColumnCount = headers.Count();
    Headers = headers.Select(x => x.Text).ToList();
}
else
{
    RowCount += 1;
    WithHeaders = false;
    Headers = null;
    ColumnCount = rows.FirstOrDefault().FindElements(By.CssSelector("td")).Count();
}

Then, there could be multiple ways to access the desired cells/rows as samples listed below.

  1. To access the cell by its rowIndex and columnIndex:
    C#
    IWebElement table = driver.FindElement(By.Id("table"));
    
    public IWebElement cellOf(int rowIndex, int columnIndex)
    {
        if (columnIndex < 0 || columnIndex >= ColumnCount)
            throw new Exception("Column Index ranges from 0 to " + (ColumnCount-1));
        if (rowIndex < 0 || rowIndex >= RowCount)
            throw new Exception("Row Index ranges from 0 to " + (RowCount-1));
        if (rowIndex == 0 && !WithHeaders)
            throw new Exception("No headers (rowIndex=0) defined for this table.");
    
        String css = string.Format("tr:nth-of-type({0}) {1}:nth-of-type({2})", rowIndex + 1,
                            rowIndex == 0 ? "th" : "td",
                            columnIndex);
        return table.FindElement(By.CssSelector(css));
    }
  2. To access the cell by its rowIndex and header name:
    C#
    IWebElement table = driver.FindElement(By.Id("table"));
    
    public IWebElement cellOf(int rowIndex, string headername)
    { 
        int columnIndex = Headers.FindIndex(s => s.Contains(headerKeyword));
        return this[rowIndex, columnIndex];
    }
  3. More specifically, return the row which contains a specific value under a headername:
    C#
    IWebElement table = driver.FindElement(By.Id("table"));
    
    public IWebElement rowOf(string headerKeyword, Func<string, bool> predicate)
    {
        int columnIndex = Headers.FindIndex(s => s.Contains(headerKeyword));
        var allRows = table.FindElements(By.CssSelector("tr"));
        if (WithHeader)
            allRows.Skip(1);
    
        foreach( var row in allRows)
        {
            var td = row.FindElement(By.CssSelector(string.Format("td:nth-of-type({0})", columnIndex + 1)));
            if (predicate(td.Text))
                return row;
        }
        return null;
    }

Point of Interest

In this tip, we have discussed about some very detailed procedures to operate some common HTML elements that are working, but not documented.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)