Pages

Wednesday, January 30, 2013

PRACTICUM: Selenium 2 Testing Tools Beginner's Guide: Locators



This is the next installment (in progress) of my PRACTICUM series. This particular grouping is going through David Burns' "Selenium 2 Testing Tools Beginner's Guide".

Note: PRACTICUM is a continuing series in what I refer to as a "somewhat "Live Blog" format. Sections may vary in time to complete. Some may go fast. Some may take much more time to get through. Updates will be daily, they may be more frequent. Feel free to click refresh to get the latest version at any given time. If you see "End of Section" at the bottom of the post, you will know that this entry is finished :).

Chapter 2: Locators

Much of the value of automation is that, ideally, we should be able to get tests that can run 100% reliably. Every time all the time automation is kind of a pipe dream, but we can still get high probability of success if we can give our tests reliable things to look at and do so in ways that are likely to succeed. If everyone practiced good web development protocols and everyone "followed the rules", then this would be easy.

Problem is, which rules? There's quite a few of them. Not considering the proliferation of "good practices", the fact is, a lot of teams,companies and projects don't practice good web development hygiene, or at least, not all of the time. It gets even hairier when the content is auto-generated. Not every element we want to interact with has a properly defined name attribute or id or css class name. In short, we need to have a broad and varied strategy to find what we are looking for. The nice thing is, Selenium allows for a broad and varied strategy to find what we are looking for :).

So what is an element? It's an item in a page that a person or a program can interact with. Links, buttons, images, text, controls, form fields, etc. are all elements. Each could be implemented with either the full compliment of accessibility options, or they could be the most absolutely bare bones of HTML. Either way, we want to be able to find the element we care about and interact with that element in a way that we can make a determination of a state is correct or not. We can use the following approaches to find what we are looking for:

- by ID
- by Name
- by Link
- by XPath
- by CSS
- by DOM


This section leverages Firebug, Firefinder, and the Developer Tools found with your favorite browser, so if you don't already have those, go to my first post in this series and look in the links section near the start and get what you need.

One quick way to see if you have the right name for the element you want to target is to open the IDE and type in the name of the element you want to interact with. If you enter that name, and then click the Find button to the right of the Target text box, you will either see a quick flash around the element in question on the browser page, or you will see an error in the log.

Finding ID's of Elements on the Page with Firebug

OK, yes, this may be old hat to a lot of people, but there are some out there who have probably never used Firebug or one of their analogs before (hint, if you have ever hit F12 for IE's developer tools, or you have clicked on Inspect Element in Google, you've used tools very similar to Firebug, and should be relatively easy to figure out).

You should see something that looks like this:



Here's the bare basic and super fast version of using Firebug. If you point to an item on the screen, and then CTRL-click and select "Inspect Element with Firebug", you will see the element you clicked highlighted in the lower half of the window (the blue bar you see above). If It's formatted correctly, it will have some locator details you can use (name, link, ID, css class, something).

If you then use the up or down arrow and move the highlight, you can then look at other elements and see where they are on the screen and the locator details they provide. Sections such as head, body, divs and other "structures" in the html can be expanded/contracted. Mainly, for this chapter, we're using it to find names and other aspects we can use to put in the the "Target:" text box in the IDE. If you have that down, you're golden.

In the IDE example I just used verifyElementPresent and selected a couple of named items. Run the test and we can see that the test interacts with the items and confirms they are there. Not exciting, but it gets the point across.



Finding Elements by ID

An ID is a specific parameter given so that an element can be readily and uniquely identified. its syntax is easy to spot, as it shows up in a number of elements (images, links, fields, divs, etc.) as "id=someValue".

  • Open Selenium IDE.
  • Make http://book.theautomatedtester.co.uk the base url/
  • On the first line of the script, put open in the Command: box and "/chapter2" in the "Target:" box.
  • Click the Firebug icon on the browser.
  • Look for the attribute id "but1". As you might guess, it's associated with a button.

  • In the second line of the script, type "click" in the "Command:" box.
  • In the Target box, type "id=but1". Click the "Find" button to see a flash on the browser page around the button to confirm the value is valid.
  • Run the script.


By using an ID with elements, we help the script find an item that can be positioned, well, anywhere within a page. It doesn't need to be in a specific place; as long as the ID is present, and unique, we can use it to locate an element and interact with it. Note, in this case, the text will run without id=but1 being called out explicitly. Just putting in "but1" also works.



Finding Elements by Name

ID's are nice, but not everyone uses them. Sometimes, elements have a name attribute. Again, these are easy to find because the syntax they use is "name=someValue". Using Firebug, you can look at the elements on the page and see if a name is used instead of an id. You place that named item into the Target: text box, and can also use Find to confirm that the value is accessible.

  • Open Selenium IDE.
  • Make http://book.theautomatedtester.co.uk the base url/
  • On the first line of the script, type "open" in the Command: box and "/chapter2" in the Target: box.
  • Click the Firebug icon on the browser.
  • Look for the attribute name "but2". Yep, it's associated with a button.

  • In the second line of the script, type "click" in the "Command:" box.
  • In the Target box, type "but2". Again, click "Find" to see a flash on the browser page around the button to confirm the value is valid.
  • Run the script.

Also, if you want to be more specific, Target will let you put in "name=but1".


Just like in the previous example, id and name are being used in the same way. The script will recognize the element by either means of identification. Having an id or name means the element can be anywhere on the page and as long as the name or id is unique, the script will find it.

Sometimes there can be several named items that share the same name. When this happens, a "value=someValue" tag can be used. This helps to make sure that you identify the right element. in the IDE, type "name=someName" followed by "value=someValue" to locate the specific element you are after:

Note, in the book, the example is displayed as follows:

name=verifybutton value=chocolate

However, when I do that, I get an error message.



First, the value is no longer valuebutton, but valuebutton1.

Second I'm not sure if this is type-setting, a difference w/ Ubuntu and Darwin, or 1.5.0 and 1.10.0 versions of IDE, but if you want to use the value attribute, you need to put it in the Value: column. To do that, in the Value textbox, type:

value=chocolate

And then run the script. This time it will work.



Finding Elements by Link Text

We are most familiar with links on pages, since they are what we actually click to get from one page to another. Likewise, Selenium lets you target links and interact with them. By using "link=linkName" we can access and use the link attributes to do any number of operations.

http://book.theautomatedtester.co.uk/chapter2 has a link that points to the Index page for the site. In the IDE:

  • type "click" into the command text box. 
  • type "link=Index" in the target box 
  • Run the script. 

Net result, the user is taken to the index page, as intended.

Finding elements by accessing the DOM via JavaScript

Sometimes pages get updated dynamically. AJAX and other "what's new this instant" methods make for an interesting challenge. In short, we don't necessarily know what is going to be there when we load or reload a browser. AJAX physically updates the Document Object Model (DOM), so we need to have some technique to tell us if the document element we care about is actually there, and is intended to be there.

Using JavaScript, we can do this.

An example JavaScript call to look for the first link on a page would be document.links[0]; document in this case means our HTML file. links is an array within the object "document". The zero represents the first of the links to be found in that document.


  • Open Selenium IDE
  • Set the base URL to http://book.theautomatedtester.co.uk/
  • On the first line of the script, use the command "open" and the target "/chapter2"
  • On the second line of the script, use the command "click" and the target "dom=document.links[0]"
  • Save and run the script.




Our net result is that we can reference that first link on the chapter 2 page and interact with it. In short, the example above and the previous example with the link text do the exact same thing.

Finding elements by XPath

Sometimes, the markup is even more dynamic than the example above. the element in question might be even harder to find because aspects of its id or name changes, or its based on something that's not inherently obvious. For these times and situations, XPath is an option. XPath allows for complex queries, and gives us a way to get to elements we might not otherwise be able to access.

Here's the example that David gives for the Chapter 2 page:

Let's start by creating a basic XPath. We are going to look for an input button:


  • Open Selenium IDE
  • Set the base URL to http://book.theautomatedtester.co.uk/
  • On the first line of the script, use the command "open" and the target "/chapter2"
  • On the second line of the script, use the command "click" and the target "xpath=//input"
  • Click the "Find" button to see the element selected.
  • Highlight the click line and press 'x'. The command executes and it clicks the button.



This test will look at the DOM And explicitly tell the text that it is using XPath to do it. Note: many of the prefix options are optional, but I personally find it valuable to include them wherever possible and practical. It's also good in that it helps apply a little discipline and focus on exactly what we want to access and how. Anyway, I think its a cool thing to do.

The ''//" means find the first element that matches this. Dave points out that this is a greedy query; if you have large passes to traverse, it could take awhile before it returns your value. An alternative is to use a single slash and make a more explicit call, such as in the following example:


  • Open Selenium IDE.
  • Make http://book.theautomatedtester.co.uk the base url/
  • On the first line of the script, put open in the Command: box and "/chapter2" in the "Target:" box.
  • On the second line, type click in the command box, and put "xpath=/html/body/div[2]/div[3]/input" in the target box.
  • Click "Find" next to the target box.



This example gives a much more focused query, and it also defines an exact region in the HTML to check. Down side, if the HTML changes, the test will break. Greedy search but flexible, or focused search and brittle. Hey, them's the tradeoffs!



What happens when you get a button or a range of them that don't have a unique name or ID. If the items are all similar enough, you can address them as an array.

In chapter 2, there are three div elements. Which one do you mean? "//input" will give you the first item. "//input[2]" will give you the second element, provided it's of the same level as the first element returned. If they aren't at the same level, you'll get a failure; the system will say it can't be found.

Try this:

We can see this with the input buttons that are present on the page. They all reside in their own containing div element, so do not have any sibling elements that are also input elements. If you were to put //input[2] into Selenium IDE, it would not be able to find the element and fail.

Well, that's what's supposed to happen, but ya' know what? I just tried this out, and I'm able to make it work as listed. It's supposed to error out, but as is, I can access both of them with this array approach.
See?


Using element attributes in XPath queries

If elements are the same but have some slight differences in attributes, you can use that to your advantage. By using "xpath=//element[@attribute='attribute value']", you can make distinctions between elements.

Try this:

  • Open Selenium IDE
  • Navigate to http://book.theautomatedtester.co.uk
  • Open /chapter2
  • Enter the following statements into their own lines using the Target:
//input
//input[2]
//div[@class='mainheading']
//div[@class='mainbody']
//div[@class='leftdiv']
//div[@id='divontheleft2']


Clicking on the 'Find' button will show where all of these areas are and that they are accessible.





XPath for Partial Match of Attribute Content

Sometimes you just can't create a completely determinable element. Dynamically generated content may use something like a time stamp.

Sometimes only part of the ID is changing. Some of it might always be the same, so you could use that part as a reference point. XPath let's you do a partial match.

Think of it this way… let's say that an element has a dynamic timestamp, but it starts with the word time.

Open Selenium IDE
Enter the following items on their own lines:

//div[contains(@id,'time_')]
//div[starts-with(@id,'time_')]


Click 'Find' and you will see that they reference the same thing.


XPath to Help Find an Element via Text

Sometimes the text in an element can be a helpful place to look for an identifier when there isn't anything else to use.

By using the text attribute "//element[text()='inner text']", you can find stuff on the screen, like this:

Start Selenium IDE
Enter the following XPath line:

//div[text()="This element has a ID that changes every time the page is loaded"]
//div[contains(text(),'element has a ID')]

See, same thing, different use:


Looking for Elements using the XPath Axis

As if XPath didn't already have a decent arsenal of tools to try to access elements (I feel like the RonCo guy when he says "now how much would you pay" and "but wait, there's more!"), there's another trick that XPath provides. Not content to just give you search options and arrays, you can even look for stuff that's near something else.

xpath=//div[@class='leftdiv']/ input[2].

Let's start with an element we can find directly:

//input[@ value='Button with ID']

Place this into the target text box and click 'Find'. You'll see the button it highlights. There's another button right below it. This button is the next in line as far as the DOM is concerned. It's a "sibling" element. To access it, we could do so directly… but what fun is that ;)? 

XPath has an option called "following-sibling", and it's used like this:

//input[@value='Button with ID']/following-sibling::input[@ value='Sibling Button'] 

This following-sibling syntax is showing us how we can reference elements not just by their direct identification, but also their relative position to other elements. That can be very powerful, in that we may need to check a group of elements to make sure that the group is being represented correctly and in the proper order. 

We could also go the opposite direction, stating with the 2nd element and pick the one just prior to it. To do that, we reverse the order and use the preceding-sibling option, like this:

//input[@ value='Sibling Button']/preceding-sibling::input[@value='Button with ID']

See? Both are able to reference each other in both directions:


To see how to access a variety of axis elements, here's a list of XPath queries you can use:

ancestor Selects all the ancestors (parent, grandparent, and so on) of the element
descendant Selects all the descendants (children, grandchildren, and so on) of the element
following Selects all elements that follow the closing tab of the current element
following-sibling Selects all the siblings after the current element
parent Selects the parent of the current element
preceding Selects all elements that are before the current element
preceding-sibling Selects all of the siblings before the current element

Finding Elements by CSS

XPath has some cool aspects to it. They can also be greedy and time consuming. Another approach is to use CSS selectors if they are in place. Selenium is compatible with CSS 1.0, CSS 2.0, and CSS 3.0. 

The syntax for using CSS is:

css=cssSelector

Let's try it out…

-  Open Selenium IDE.
-  Navigate to http://book.theautomatedtester.co.uk/chapter2
-  Click on the Firebug icon. 
-  Click on the Firefinder tab in Firebug.
-  Look at one of the buttons in the div with the ID divontheleft. 
-  The CSS Selector for the buttons would be "div.leftdiv input". 
-  Place that into FireFinder and click on the Filter button.

You'll see the following:



- Put css=div.leftdiv into the Target textbox, click the Find button.

If we put in css=div.leftdiv input into the Target pox and click find, only the first button will light up.



Using Child Nodes to Find the Element

"div.leftdiv input" will look look for the div, then it will look for an input node in the DOM below leftdiv. This works similar to "descendant" in an XPath query.

By putting ">" between the div selector and the input selector, like this: 

css=div.leftdiv > input 

We can find the child of the element. 


Using Sibling Nodes to Find the Element (CSS)

Using XPath, we can use "following-sibling" in the XPath Query. To do the same in CSS, we use a "+" between items in the query. This checks the direct next node to see if its a match. 

In the following example there are two buttons. So far, our examples have found the first button. This time let's find the second button:

   
     
     
     

"css=input#but1" will find the first button.

but1 sibling is the "br" tag. The "br" tag's sibling is "input". 

To make this selector, we would enter the following:

css=input#but1 + br + input

Unfortunately, putting it in as described gives me the following



Now, if we put in just #but1, it lights up. If we put in "+ br" and click find, the br lights up. It's just when we put in the next command, the + input, that we get the error.



Using CSS Class Attributes in CSS Selectors

Finding elements by their CSS class starts by calling the CSS class first, and then moving through the DOM via descendent nodes to find the element.

The syntax looks like this:

css = node.class

To find the div with the class centerdiv, type:

css=div.centerdiv




Using Element IDs in CSS Selectors

CSS also allows for using IDs to help locate elements. To look for elements by ID in a CSS selector, put a "#" in front of the ID of the element.

To find the ID of divinthecenter, the CSS selector would look like this:

css=div#divinthecenter 

Or simplify to:

css=#divinthecenter

See, both due the same thing:



This works this way because ID's have to be unique.


Finding Elements by Their Attributes

Like XPath, CSS lets you look for attributes of elements, too.

There's a button that has the value chocolate. For web page buttons, value == what you actually see on the screen.

The syntax for attributes is:

node[attribute='value'] 

So for the button with the value "chocolate", we want to use:

css=input[value='chocolate']

Which looks like this when you use it:



David shows an example using href. On the Index page, you could put in the following:

css=a[href='/chapter2']



If you click the Find button, it will highlight the Chapter 2 link. Follow it, and it will take you to the chapter 2 page.



Want to make sure you're hitting the right element? Use chaining. Put two attributes together, like this:

css=node[attr1='value1'] [attr2='value2']

Use the following with but1:

css=input[id='but1'][value='Button with ID']

Just in case you happened to have two but1 buttons, this extra step makes it clear which one you mean.

Partial Matches on Attributes


XPath lets you use "contains" to make partial matches for values. We can do something similar in CSS to make partial matches, too:

^=   Finds the item starting with the value passed in. This is the equivalent to the XPath starts-with.
$=   Finds the item ending with the value passed in. This is the equivalent to the XPath ends-with.
*=   Finds the item which matches the attribute that has the value that partially matches. This is equivalent to the XPath contains.

Comparison:

XPath : //div[contains(@ id,'time_')] 
CSS :  div[id^='time_'] or div[id*='time_']

To put these in the IDE, do the following:

xpath=//div[contains(@ id,'time_')]
css=div[id^='time_']
css=div[id*='time_']




Finding The nth Element With CSS

To find the second input after the div with the class leftdiv, XPath let us use

xpath=//div[@class='leftdiv']/input[2]

CSS doesn't exactly have a similar analog. To find "second to nth" elements, there is an approach called "pseudo-classes".

A Pseudo class lets a user add a "special effect" to a selector.

This example assigns ":nth-child" for the first example.

Open Selenium IDE.
Navigate to http://book.theautomatedtester.co.uk/chapter2
Type css=div#divinthecenter *:nth-child(3)
Click Find. You'll see the button that says chocolate gets highlighted.


Note: :nth-of-type can't access the specific type. The selector uses the wildcard "*" and then looks for the nth- child from the starting div.



OK, wow, that was a lot of stuff to get through. For the record we are now 35% of the way through the total volume of the book, and have completed just two chapters to do it! It's one thing to do a "yeah yeah, I get it" while reading through these, it's quite another to actually walk through all of them, try them out, make sure they work the way that we expect them to, and put them into practice one at a time.

I really appreciate the time the David took to put all of these examples together. Some of them turned out to be a little different than printed, but such is life. For the most part, the examples work as they are portrayed. The lesson I'm taking out of this is that, when using a locator scheme, be explicit. Call out exactly what locator approach you want to use, and fill in the details for each.


End of Section

No comments:

Post a Comment