Very frequently testers will meet a situation where they need to take screenshot of webpage they are testing , either for base line or as a proof of test result. This is same with automated testing . Even though automated test cases have their own of way of publishing test results, it is always desirable to keep a proof of result.. Screenshot come to help in this regards. In this blog post , I will explain , how to take a screenshot with Selenium Web Driver with C#. In future I will add another couple of post to explain , how to consolidate the screenshots into a PDF document.

In .NET binding, we have an interface called ITakesScreenshot , which helps to capture screenshot of active window. Below code will help to take screenshot and save it in the path specified while calling the function

1
2
3
4
5
6
7
8
9
10

// PathToFolder is the location where we need to save the screenshot
// FileName is another string where PathToFolder is appended with timestamp
public void TakeScreenShot(string PathToFolder)
{
  string fileName = PathToFolder + DateTime.Now.ToString("HHmmss") +".jpeg";
  Screenshot cp = ((ITakesScreenshot)driver).GetScreenshot();
  cp.SaveAsFile(fileName, System.Drawing.Imaging.ImageFormat.Jpeg);

}

During testing we will sometimes come up to situations where developers are not following best practises for testability . We will frequently come up situations where elements doesn’t have any unique identifiable property. XPath axis comes to help in those situations. We can identify elements using various XPath Properties

List of various XPath Axis are available in https://developer.mozilla.org/en-US/docs/Web/XPath/Axes If you have well-defined properties to identify the element, use them as your locator. Please read  locator strategy  Using XPath and Other Parameters

Below are major one’s which we will frequently use

1. ancestor

This selects all ancestors of current node. That will include parent, grand parents etc Eg :

1
 //td[text()='Product Desc']/ancestor::tr

2. descendant

This selects all children of current node. That will include child, grand child etc Eg:

1
/table/descendant::td/input

3. followingis

Th selects everything after the closing tag of current node Eg:

1
//td[text()='Product Desc']/following::tr

4. following-sibling

This selects all siblings after the closing tah of current node. Eg:

1
//td[text()='Product Desc']/followingsibling::td

5. preceding

This selects everything prior to current node Eg:

1
 //td[text()='Add to cart']/preceding::tr
  1. preceding-sibling

This selects all siblings prior to current node Eg:

1
//td[text()='Add to cart']/precedingsibling::td

7. child

This selects all children of current node

8. parent

This select parent of current node

As usual , you can always use combinations of above in your test. Statements can be constructed in the same way as we traverse the XPath axis

Last , but not least… we can also use regular expression in XPath.

XPath is XML query language which can be used for selecting nodes in XML. Hence it can be used to identify elements from DOM since they are represented as XHTML documents. Selenium WebDriver also supports XPath for locating elements. They also help to look for elements in both direction and hence it is generally slow compared to all other locator strategy. We can use XPath with both absolute path and relative path.

Absolute XPath

Absolute Path refers to specific location in DOM, by considering it’s complete hierarchy. However this is not an ideal locator strategy since it makes your test very brittle. The absolute path will change if there is any change/realignment etc in UI.

Example of Xpath using absolute path is as below

1
WebElement userId = driver.FindElement(By.XPath(html/body/div[2]/div/form/input[2]));

Relative XPath

With relative Path , we can find element directly without entire structure. It helps to look out for any elements which matches with specified relative path . Example for a relative path based locator strategy is as below.

Note: Relative XPath starts with “//”

1
2
3
4
WebElement userId = driver.FindElement(By.XPath("//input"));
// This retrieve first element with input tag.
WebElement userId = driver.FindElement(By.XPath("//input[2]"));
// This retrieve second element with input tag.

Relative XPath  - With Attributes

If we need to further narrow down our location strategy, we can use Attributes along with relative XPath. There may be situations where we need to multiple attributes to uniquely identify an element. We can also specify locators to identify for ANY attribute

1
2
3
4
5
6
7
WebElement passwordField = driver.FindElement(By.XPath("//input[@id='password']"));
// Above will identify first element with input tag which also has id as "password".

WebElement LoginButton = driver.FindElement(By.XPath("//input[@type='submit'and @value='Login']"));
//Note you can use "or" as well.
WebElement someField = driver.FindElement(By.XPath("//input[@*='password']"));
// Above will identify first element with input tag which also has any attribute as "password".

Relative XPath - Partial Match

Sometimes there may be situations where element attributes like ID are dynamically generated. Those will generally have some unique part in attributes likeID and remaining will be generated dynamically , which will keep on changing. This will need a locator strategy which will help us to identify elements using partial match. Main types are

  • starts-with()

  • ends-with()

  • contains()

1
2
3
4
5
6
7
WebElement passwordField1 = driver.FindElement(By.XPath("//input[starts-with(@id,'password')]"));
// Above will identify first element with input tag which also has id starting with "password".
WebElement passwordField2 = driver.FindElement(By.XPath("//input[ends-with(@id,'password')]"));
// Above will identify first element with input tag which also has id ending with "password".

WebElement passwordField3 = driver.FindElement(By.XPath("//input[contains(@id,'password')]"));
// Above will identify first element with input tag which also has id containing with "password".

Assert.AreEqual vs Assert.AreSame

Very frequently I use Assert.AreEqual and Assert.AreSame for doing assertions in the code. Below is high level difference between both.

Assert.AreSame

Assert.AreSame checks whether both comparing objects are exactly the same ( reference indicate same object in memory) .It is normally known as Reference Equality

Assert.AreEqual

Assert.AreEqual checks whether both objects contain same value. It is normally known as Value Equality. For primitive value types ( like int, bool) this is straight forward. But for other types ( especially user defined objects) , it is depends on how the type defines equality.

Hence Assert.AreEqual will fail ( most of the time) when we compare two objects. This link have some discussion point about the same.

Locators are html properties of a web element , which can be considered as an address of the element. An element will have various html properties. We can use Firebug extension or Chrome dev tools to identify different locators of an element.

Selenium Web Driver provides two different methods for identifying html elements .

  • FindElement  for WebDriver and WebElement Class. When locating element matching specified criteria, it looks through DOM( Document Object Model) for matching element and return the first matching element. If there are no matching element, it will throw NoSuchElementFoundException

  • FindElements for WebDriver and WebElement Class. When locating element matching specified criteria, it looks through DOM( Document Object Model) for matching element and return a list of all matching element. If there are no matching elements, then it will return an empty list .

Note: Both of them doesn’t support regular expression for finding element. Simple way to do that will be to get list of all elements and then iterate to find a matching regular expression

There are multiple criteria which we can use for looking for an element. FindElement and FindElements work exactly same way except for above difference. Different critieria are

  • driver.FindElement(By.Id())

  • driver.FindElement(By.Name())

  • driver.FindElement(By.ClassName())

  • driver.FindElement(By.TagName())

  • driver.FindElement(By.LinkText())

  • driver.FindElement(By.PartialLinkText())

  • driver.FindElement(By.CssSelector())

  • driver.FindElement(By.XPath())

Example:

1
2
3
4
5

WebElement _firstElement = driver.findElement(By.id("div1"));
WebElement _secondElementInsideFirstOne =_firstElement .findElement(By.linkText("username"));

IList<IWebElement> elements = driverOne.FindElements(By.ClassName(<span class="pl-s"><span class="pl-pds">“</span>green<span class="pl-pds">“</span></span>));

Using any attributes other than XPath and CssSelector are straight forward. More about using XPath and CssSelector in next blog.

In Specflow, Step definitions are global. So a scenario can have multiple step definitions which can be present in different classes.  Sometimes, there arise a need to share the data between steps residing in different classes. How do we do it??

There are multiple ways to do it

  1. Context Injection

  2. Feature Context

  3. Scenario Context

Let us look into more details about how to store and retrieve data using Scenario Context .

ScenarioContext.Current

How do we add a key value pair to Scenario Context ? It is as simple as below

1
2
3
4
5
6
[Given(@"I have entered (.*) and (.*) into the Login Page")]
public void GivenIHaveEnteredAndIntoTheLoginPage(string p0, string p1)
 {
 ScenarioContext.Current.Add("username", p0);
 ScenarioContext.Current.Add("password", p1);
 }

How do we retrieve the value from ScenarioContext ?

1
2
3
4
5
6
7
When(@"I press retrieve data")]
public void WhenIRetrieveData()
{
string username = (string)ScenarioContext.Current["username"];
string password = (string)ScenarioContext.Current["password"];

}

Note: While retrieving , scenarioContext.Current always return an object . Hence we need use explicit casting while retrieving data from scenario context.

In Nut Shell,

**Set a value for a key ( Store data ) ** ScenarioContext.Current.Add(string key, object value); **Get a value of the key ( Retrieve data) ** var value =(Type) ScenarioContext.Current.[string Key]; var value = ScenarioContext.Current.Get(string Key);

We can use this for storing and passing objects as well

Example of storing webdriver object is as below.(where "browser" is current webdriver object ) ScenarioContext.Current.Add("driver1", browser); IWebDriver driver2 = (IWebDriver)ScenarioContext.Current["driver1"];

I am not going to explain what is data driven framework or what is its benefits.  All of them are pretty well-known . If not, just google it.

Here I am going to explain a sample code which can be used to read from xml data files. This will be helpful to implement a data driven frame work for BDD testing , using Specflow or Cucumber

Pre - requiste

Code is written in Csharp . We need to add below reference to visual studio solution

  1. Add reference to System.xml

  2. Add reference to System.Xml.Linq

XML Format

[code language=“xml” ]

[/code]

Node names in above example are Scenario1, Scenario2 and Scenario3

Element or Attribute name are username, password, Email .

You can add any number of nodes and attributes depending on the test scenario

Read specific Value from XML

Below code provides a solution to read values of existing Key/Attribute from a specific node.

1
2
3
4
5
6
7
8
public static string ReadDataFromXML(string FileName, string NodeName, string KeyName)
{
 string _basePath = AppDomain.CurrentDomain.BaseDirectory.ToString();
string _datafilePath = _basePath + @"..\..\Data\" + FileName;
XDocument xmlDoc = XDocument.Load(_datafilePath);
data = xmlDoc.Root.Element(NodeName).Attribute(KeyName).Value;
return data;
}

 

Read All values for a Scenario from XML

This code gives a solution for reading details of all attributes/key from a node. This comes very handy for reading all data required for a scenario and adding them to scenario context , so that data can be shared across specflow/cucumber step definitions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void ReadAllDataFromXml(string xFileName, string xNodeName)
{
 string data = string.Empty;
// Load path of xml file . Data Folder in below is folder name
string _basePath = AppDomain.CurrentDomain.BaseDirectory.ToString();
string _datafilePath = _basePath + @"..\..\Data\" + xFileName;
XDocument xmlDoc = XDocument.Load(_datafilePath);

var cols = xmlDoc.Descendants(xNodeName).First();

foreach (XAttribute xAtt in cols.Attributes())
{
Console.WriteLine("{0},{1}", xAtt.Name, xAtt.Value); // --printing values --
string temp = xAtt.Name.ToString();
ScenarioContext.Current.Add(temp, xAtt.Value);
// The values are added to scenario context as key value pair.can be modified
}

Write Data into XML

  Below function gives a solution to update value on an existing key/attribute in data sheet . Sometime the data sheet will be copied over to bin folder when we build the solution. That makes it necessary to update both original data sheet and the one in bin folder  so that modified data can be used in same test without another rebuild.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void WriteIntoXML(string xData, string xElement, string xAttribute, string xFileName)
{
 // This solution updates value of an existing key.
 // Xdoc refers to the xmlsheet in the Solution explorer
 // xDoc_2 refers to the xmlsheet inside bin folder while running

 // This solution write them separately in below code.
 // Below is the path to xml file after building solution
 XDocument xDoc_2 = XDocument.Load(Path.Combine(Environment.CurrentDirectory, "Data Folder", xFileName));

 //Below should be the path of xml file
 XDocument xDoc = XDocument.Load(Path.Combine(Path.GetFullPath(@"../../Data Folder"), xFileName));
 xDoc_2.Root.Element(xElement).Attribute(xAttribute).Value = data.ToString();
 xDoc.Root.Element(xElement).Attribute(xAttribute).Value = data.ToString();
 xDoc_2.Save(Path.Combine(Environment.CurrentDirectory, "Data Folder", xFileName));
 xDoc.Save(Path.Combine(Path.GetFullPath(@"../../Data Folder"), xFileName));
}

In previous blog post, I have explained about how use XML for making a data driven framework for automation testing . It can be found here. I have also written about how to use jxl library for reading from excel and writing into Excel.

Below is another code snippet to read all values of a row and save it into a hash map for accessing later during automation test.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public  HashMap GetAllDataForARow  (String sheet, int Row){
        HashMap DataMap = new HashMap();
        try {
            Workbook wrk1 =  Workbook.getWorkbook(new File(dataPath));

            //Obtain the reference to the first sheet in the workbook
            Sheet sheet1 = wrk1.getSheet(sheet);
            int x =0;


            Cell colArow1 , colArow2;
            do {
                colArow1 = sheet1.getCell(x,0);
                colArow2 = sheet1.getCell(x,Row);
                DataMap.put(colArow1.getContents(), colArow2.getContents());
                x=x+1;
            }while (colArow1.getContents() != "");

        }

        catch (BiffException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }catch (IndexOutOfBoundsException e){
        }
        return DataMap;

    }

This post is just for making notes during my learning of Node.js through various courses and online material. This will be always a work in progress blog post

Node.js is an open source server side runtime environment, which is cross platform.It uses Javascript as its language

check node version node --version

Making web request in Node we can make webrequest by using inbuilt http or by using ‘request’ example : For making web request by http

1
2
3
4
5
6
var http = require('http');
var req = http.request('http://www.google.com/finance/info?infotype=infoquoteall&q=NSE:TCS', function(response) {
    console.log(response.statusCode);
    response.pipe(process.stdout);
});
req.end();

example: Please note that there is no space between key and : while defining options

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var http = require('http');

var options = {
  host: 'www.google.com',
  port: 80,
  path: '/finance/info?infotype=infoquoteall&q=NSE:TCS',
  method: 'GET'
};

var req = http.request(options, function(response) {
    console.log(response.statusCode);
    response.pipe(process.stdout);
});
req.end();

Example: We can simply by giving GET . There is no need to close request since we are not going to send any more information to request

1
2
3
4
5
6
7
8
9
10
11
12
13
var http = require('http');

var options = {
  host: 'www.google.com',
  port: 80,
  path: '/finance/info?infotype=infoquoteall&q=NSE:TCS',
  method: 'GET'
};

http.get(options, function(response) {
    console.log(response.statusCode);
    response.pipe(process.stdout);
});

Another option is

1
2
3
4
5
6
7
8
9
10
11
12
13
http.get(options, function(res){
    var body = '';

    res.on('data', function(chunk){
        body += chunk;
    });

    res.on('end', function(){
        console.log("Got a response: ", body);
    });
}).on('error', function(e){
      console.log("Got an error: ", e);
});

Starting with Node.js and Express

1
npm init

Above command will create a package.json. Leave details are default or change accordingly

1
npm install express --save

Above will install express and also add it as dependency in package.json

1
touch app.js

Above will create a app.js file. In package.json add below

1
2
3
4
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node app.js"
  },

Now, if we run “npm start” from command line, it will run app.js

Bower

Package manager for web/front end. It is installed with NPM and have flat package hierarchy(doesnt install dependency underneath one level) .It works similar to NPM and have Bower.json for dependency managament.

Create a .bowerrc file and have project specific settings. Now move the components from bower_component to public folder defined earlier. update .bowerrc with below

1
2
3
  {
      "directory" : "public/lib"
  }

now run below

1
2
3
4
  bower init
  \\ leave all settings as default
  bower install --save bootstrap
  \\ it will install bootstrap. It create a directory under public/lib and under that it will have bootstrap andjquery since jquery is a dependency for bootstrap

Gulp

It is a task manager for web projects. It have code based config. It is packaged base so that we can use different external packages

Mongo DB

  • Install MongoDB from website
  • mongoD is command for running server
  • mongo is command for running another terminal for interacting with mongo db
  • show dbs will show list of db
  • Install MongoDB Node.js driver using NPM ```

A couple of months back , I helped out to organize clinical examination for RACP. I created a tool ( Excel Macro) for finalizing exam schedules for all attendees. The roster should consider employee preference, examiner availability, timeslot, venue and other parameters. Below is what I got in return ( even though they misspelled my name).