Friday, January 7, 2011

Quick XPath For Dummies

So I had someone tell me they needed a more fundamental tutorial on xpath.  So lets start off with the basics:

What is Xpath?  It is a short way of referencing an element on a web page. 
What is it for?  In selenium it is used for identifying a html element that does not have an easy, unique identifier such as id, name, text.  
How does it work?  Well I don't really know.  But essentially it searches the web page, top to bottom, looking for elements that match the criteria. 

Let's talk about html.  Here is an example element: <a href=http://www.google.com>google link</a>.  This will show up on the page as a text link displaying the words "google link" and it will take you to www.google.com.  For each element there are three main parts:  the type, the attributes, and the text. 
Our element is of type a.  It has an attribute called href equal to http://www.google.com.  and it has text equal to "google link".  We can use all three of these things to search for our elements. 

The next concept to understand is the idea of nodes, and the familial relationship of html elements.  Look at this example code:

<div title="Section1">
   <td name="Search">
      <tr class="Yahoo">Yahoo Search</tr>
      <tr class="Google">Google Search</tr>
   </td>
</div>

Notice the </div> at the bottom? That means the td and tr elements are contained within the div.  These other elements are considered descendants of the div.  The td is a child, and the tr is a grandchild (and so on and so forth).  The two tr elements are considered siblings.  This is vital, as xpath uses these relationships to find your element

So suppose I wanted to find the google item.  Any of the following expressions will work:
//tr[@class='Google']
/div/td/tr[2]
//div[@title="Section1"]//tr[text()="Google Search"]

So lets analyze the expressions.  We start at the top element (also known as a node).  The // means to search all descendants, / means to just look at the current element's children.  So //div means look through all descendants for a div element.  The brackets [] specify something about that element.  So we can look for an attribute with the @ symbol, or look for text with the text() function.  We can chain as many of these together as we can. 

 Here is a quick reference:
// search all descendant elements
/ search all child elements
[] The predicate (specifies something about the element you are looking for)
@ Specifies an element attribute.  (For example, @title)
text() Gets the text of the element. 
. specifies the current node (useful when you want to look for an element's children in the predicate)
.. specifies the parent node
contains() Use this in the predicate if you can't do a full string match on an attribute or text() value.

waiting for a popup to close

I was having a problem yesterday where the popup I was working on didn't close by the time the next one was supposed to be open.  I looked through the selenium api and realized there was no good waitForPopUpToClose command, so I created a new function.  All it does is wait until the number of windows is 1.  This will obviously only work if you have 1 window open (besides the popup).

        public void waitForPopupToClose()
        {
            string[] titles;
            for (int second = 0; second<timeoutSeconds; second++)
            {
                try
                {
                    titles = selenium.GetAllWindowTitles();
                    if (titles.Length > 1)
                        sleep(1000);
                    else
                    {
                        break;
                    }
                }
                catch (Exception)
                {
                  
                }
                Thread.Sleep(1000);
            }
        }

Thursday, January 6, 2011

Xpath In All It's Glory

I thought it would be a good idea to write up a little bit about Xpath, why it rocks, and why you SHOULD use it.  I know many "experts" in selenium tell you that Xpath is bad, it's slow, and it needs to be avoided at all costs.  I disagree completely.  I will, of course, try to reference an item in the most clean and concise method possible.  So if an item has a unique id or name, you should use that and avoid an xpath expression.  However, there are so many times when the element you're trying to locate does not have a unique identifier, and you need to find it.

The problem with Xpath is the way most xpath generating programs work.  They generate a string of absolute locators a million miles long.  So something like this:  /body/td/tr[2]/div[3]/a.  This is NOT how you want to use Xpath.  This style of xpath expression should be your very LAST resort on locating an element.  But what is the alternative? Start with the parent.

First off, I strongly recommend the use of // instead of /.  It may be slightly slower locating an element, but it allows you to construct a much more transparent, and reliable xpath expression. 

So something like this would be a more appropriate xpath from the above example:  //div[@class='object1']/a  This looks through the entire page for a div with attribute "class" equal to "object1".  It then looks for a child <a> element.  This is fine as long as there is only one div with class=object1.  However, class is not a unique identifier, and it's quite possible there is more than one item on the page that matches our expression.  What is the solution?  Add another parent. 

//td[@title='Row1']//div[@class='object1']/a.  Neither title nor class are unique, but hopefully the combination of both is.  However, maybe not. 

If you can't find a parent or grandparent to start with that is unique, you can also try a sibling node.  Suppose we have a table where nothing has attributes.  We want to type into the text field in an adjacent row to a link.  We can find the link, because the link text is unique.  But we can't find the text field without resorting to some caveman xpath expression.  The easiest way i've found to do this is to start with the parent node, and in the predicate (the []) look for a child node, then select another node.  For example:
This expression select a row, looks for a descendant link with text "User1", then selects any descendant input.  You have to use the period inside to select the current node.

//tr[.//a[text()="User1"]]//input

Selenium and Selenium2 Visual Studio Templates

Here are some visual studio templates I created for selenium and selenium2.  All you should have to do is fix the references once you import them into visual studio 2010.

They feature:
Base class with Selenium1 api commands.
.config file usable by nUnit
PageObject design
Multi-project format
Automatically launches and closes selenium server.
Relative pathing
Sample Google Test

Selenium 1 Template

Selenium 2 Template

Monday, December 6, 2010

Webdriver Extensions

I was playing with Selenium2 Webdriver, and the IWebDriverExtensions class gives you the ability to extend the base api.  I used this to create an api more like selenium1.  Now when I create a new webdriver driver, I have access to all the following functions.  Being able to do things like driver.isVisible saved much time.


    public static class IWebDriverExtensions
    {
              public static void click(this IWebDriver driver, By bylocator)
        {
            driver.WaitForElementPresent(bylocator);
            IWebElement element = driver.FindElement(bylocator);
            element.Click();
        }
        public static void type(this IWebDriver driver, By bylocator, string text)
        {
            driver.WaitForElementPresent(bylocator);
            IWebElement element = driver.FindElement(bylocator);
            element.SendKeys(text);
        }

        // This is a basic wait for element not present a'la Selenium RC
        // but sharing the same timeout value as the driver
        public static bool isVisible(this IWebDriver driver, By bylocator)
        {
            try
            {
                IRenderedWebElement element;
                element = (IRenderedWebElement)driver.FindElement(bylocator);
                return element.Displayed;

            }
            catch (NoSuchElementException)
            {
                return false;
            }
        }
        public static bool isText(this IWebDriver driver, By bylocator, string textString)
        {
            string elementText = driver.FindElement(bylocator).Text;
            if (elementText == textString)
                return true;
            else
                return false;
        }
        public static bool isTextPresent(this IWebDriver driver, By bylocator, string textString)
        {
            string pageText = driver.PageSource.ToString();
            string elementText = driver.FindElement(bylocator).Text;

            if (elementText == pageText)
                return true;
            else
                return false;
        }
        public static bool isPresent(this IWebElement driver, By bylocator)
        {
            try
            {
                IWebElement element = driver.FindElement(bylocator);
                return true;
            }
            catch (NoSuchElementException)
            {
                return false;
            }
        }

        public static void WaitForElementNotPresent(this IWebDriver driver, By bylocator)
        {
            int timeoutinteger = Common.DriverTimeout.Seconds;

            for (int second = 0; ; second++)
            {
                Thread.Sleep(1000);

                if (second >= timeoutinteger) Assert.Fail("Timeout: Element not found: " + bylocator);
                try
                {
                    IWebElement element = driver.FindElement(bylocator);
                }
                catch (NoSuchElementException)
                {
                    break;
                }
            }
        }
        public static void WaitForElementPresent(this IWebDriver driver, By bylocator)
        {
            int timeoutinteger = Common.DriverTimeout.Seconds;

            for (int second = 0; ; second++)
            {
                Thread.Sleep(1000);

                if (second >= timeoutinteger) Assert.Fail("Timeout: Element not found: " + bylocator);
                try
                {
                    IWebElement element = driver.FindElement(bylocator);
                    break;
                }
                catch (NoSuchElementException)
                {

                }
            }
        }
        public static void WaitForElementNotVisible(this IWebDriver driver, By bylocator)
        {
            int timeoutinteger = Common.DriverTimeout.Seconds;

            for (int second = 0; ; second++)
            {
                Thread.Sleep(1000);

                if (second >= timeoutinteger) Assert.Fail("Timeout: Element still visible at: " + bylocator);
                try
                {

                    if (!driver.isVisible(bylocator)) break;

                }
                catch (NoSuchElementException)
                {
                    break;
                }
            }
        }
        public static void WaitForElementVisible(this IWebDriver driver, By bylocator)
        {
            int timeoutinteger = Common.DriverTimeout.Seconds;

            for (int second = 0; ; second++)
            {
                Thread.Sleep(1000);

                if (second >= timeoutinteger) Assert.Fail("Timeout: Element not visible at: " + bylocator);
                try
                {



                    if (driver.isVisible(bylocator)) break;

                }
                catch (NoSuchElementException)
                {
                    break;
                }
            }
        }
    }

Dealing with frames

So I see a lot of people struggling to figure out frames and selenium.  In my opinion, they are always a pain.  Many times they are dynamic, meaning they have a different id or name every time you run your tests.  Also, having to call selectFrame every few lines of code to go back and forth between functional areas is annoying, not to mention the pain of having to figure out what the frame names are.

I'm here to tell you that for the most part, you don't even need to deal with them.  The IDE throws in TONS of selectFrame commands when it records.  Your tests will work 3x better if you remove them all.  There are two things that selecting a frame really does. 
1) It limits your scope. 
2) It tells selenium what page you using.

1)  For the most part, limiting your scope is BAD.  It makes your tests less stable (if a button moves from one frame to another, you will have to change your tests, which should be avoided whenever possible).   In addition, any frame will AUTOMATICALLY look in all child frames for elements.  So if you select the main window frame, you will be looking through all subframes on the page.  You can do this with selectWindow("null").  Passing "null" tells selenium to look at the root frame, the one that was opened when you created a new browser.

2)  There is only one reason you need to select a frame: When you need to tell selenium what page you're referencing.  This happens for one of two reasons:
   A)  When a selenium command references the page.
   B)  When you have a popup or more than one window. 

A) A typical website has multiple pages, normally to separate navigation and content.  Commands like waitForPageToLoad, and verifyTextPresent use the currently selected page ONLY.  So you will need to selectFrame before you use these types of commands.  If you have the wrong frame selected, they will fail. 

B)  PopUp windows need to be selected to be interacted with.   Since there are typically only 1 popup open at a time, you can look for ANY popup, and avoid trying to determine the ID or name of the popup.  For example, by passing a blank string, selenium will return the first popup it finds.  I use this:
            selenium.WaitForPopUp("");
            selenium.SelectPopUp("");
 
In addition, besides PopUps, sometimes a new browser window or tab is opened with something like target=_blank.  The easiest way to find the window is by looking for the title, with something like this:  selectWindow("title=My Title").   Lastly you can call getAllWindowIds to get an array of ID's, and then call selectWindow with one of the returned values. 

Monday, November 29, 2010

Automatically launching Selenium server

Selenium is an amazing tool.  However, one of the most annoying things about it is having to deal with the selenium server.  For those who don't know, the selenium-server is a .jar that you run on the machine you will be testing on.  It has to be running for tests to pass.  What this means is that you either have to start/stop it each time you want to perform a test, or you have to leave it up and running on your machine constantly.  This can be an issue when you are dealing with a remote machine, you don't want to have to remote desktop into it, launch selenium-server.jar, and then run your tests.

So my first blog will be about how to add a SetUpFixture to your tests to automatically launch and close selenium-server.

I created a new class to contain the SetUpFixture.  For those who don't know, the SetUpFixture tag identifies a class to be run before/after all Fixtures.  I created a string called Common.seleniumDirectory which contains the location of my tests.  I did it this way to allow it to be more dynamic.  I also am having it launch a specific firefox profile, however you can modify the string to be whatever you want.

namespace SeleniumTests
{
    [SetUpFixture]
    public class Common_Setup_Fixture
    {
        // Runs before all tests
        public static Process seleniumServer;
        public static readonly ProcessStartInfo seleniumServerProcessStartInfo = new ProcessStartInfo("java", "-jar " + Common.seleniumDirectory  + "RemoteControl\\selenium-server-1.0.3\\selenium-server.jar -firefoxProfileTemplate \"" + Common.seleniumDirectory + "FirefoxProfile\"");
      
        [SetUp]
        public void SetUp()
        {
            seleniumServer = Process.Start(seleniumServerProcessStartInfo);
            Common.verificationErrors = new StringBuilder();
        }

        [TearDown]
        public void TearDown()
        {
            try
            {
                seleniumServer.CloseMainWindow();
            }
            catch (Exception)
            {
                // Ignore errors if unable to close the browser
            }
          
        }
    }