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.