4
\$\begingroup\$

I am very new to Selenium, and for my current project, I had the task of creating the automated test scripts for our website. I am the only one on my team who had any experience with Selenium, but not any in depth experience. I also didn't have a lot of time to spend on this, as I had other work to do, so I didn't spend much time prior to writing the tests investigating Page Object Model.

I wrote tests that worked well, and they were code reviewed and I was told that they are looking good. However, I am almost certain that they could be made a lot better using some best practices for Selenium.

public class ProductRunThroughTests
{
    private static ChromeDriver Driver = new ChromeDriver();

    public void Go()
    {
        Driver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(3));
        Driver.Manage().Window.Maximize();

        Product1Sequence();
        Driver.FindElementById("inner_product_box").Click();
        Driver.Manage().Cookies.DeleteAllCookies();

        Product2Sequence();
        Driver.FindElementById("inner_product_box").Click();

        Product1Sequence();
        Driver.FindElementByName("Restart").Click();
        if (Driver.FindElementsByTagName("a").ToList().Count() != 6)
            Assert.Fail("Did not end on landing page");

        Driver.Quit();
    }

    private void Product1Sequence()
    {
        Driver.Navigate().GoToUrl(ConfigurationManager.AppSettings["Host"]);

        Driver.FindElementsByTagName("a").ToList()[0].Click();

        #region Question 1
        CheckBreadcrumbMenu(1);
        ClickRadBtnByIndex(3);
        #endregion

        #region Question 2
        CheckBreadcrumbMenu(2);
        ClickRadBtnByIndex(1);
        #endregion

        #region Question 3
        CheckBreadcrumbMenu(3);
        ClickRadBtnByIndex(1);
        #endregion

        #region Question 4
        CheckBreadcrumbMenu(4);
        ClickRadBtnByIndex(0);
        #endregion

        #region Question 5
        CheckBreadcrumbMenu(5);
        MoveSlider(7, "left");
        Driver.FindElementById("ForwardButton").Click();
        #endregion
    }

    private void Product2Sequence()
    {
        Driver.Navigate().GoToUrl(ConfigurationManager.AppSettings["Host"]);

        Driver.FindElementsByTagName("a").ToList()[0].Click();

        #region Question 1
        CheckBreadcrumbMenu(1);
        ClickRadBtnByIndex(0);
        #endregion

        #region Question 2
        CheckBreadcrumbMenu(2);
        ClickRadBtnByIndex(0);
        #endregion

        #region Question 3
        CheckBreadcrumbMenu(3);
        ClickRadBtnByIndex(0);
        #endregion

        #region Question 4
        CheckBreadcrumbMenu(4);
        ClickRadBtnByIndex(1);
        #endregion

        #region Question 5
        CheckBreadcrumbMenu(5);
        Driver.FindElementById("ForwardButton").Click();
        #endregion

    }

    private void ClickRadBtnByIndex(int index)
    {
        var tableRow = Driver.FindElementByTagName("tr");
        List<IWebElement> radBtns = tableRow.FindElements(By.TagName("label")).ToList();
        radBtns[index].Click();
        Driver.FindElementById("ForwardButton").Click();
    }

    private void CheckBreadcrumbMenu(int CurrentQuestionNumber)
    {
        var breadcrumbDiv = Driver.FindElementById("rightCorner");
        List<IWebElement> breadcrumbList = breadcrumbDiv.FindElements(By.TagName("li")).ToList();
        if (breadcrumbList.Count != CurrentQuestionNumber)
        {
            Driver.Quit();
        }
    }

    private void MoveSlider(int keyMovements, string sliderDirection)
    {
        Driver.FindElementById("slider").Click();
        for (int i = 0; i < keyMovements; i++)
        {
            if (sliderDirection.Equals("left", StringComparison.InvariantCultureIgnoreCase))
                Driver.Keyboard.SendKeys(Keys.ArrowLeft);
            else if (sliderDirection.Equals("right", StringComparison.InvariantCultureIgnoreCase))
                Driver.Keyboard.SendKeys(Keys.ArrowRight);
        }
    }
}

[TestClass]
public class Test
{
    [TestMethod]
    public void RunThroughWizard()
    {
        var c = new ProductRunThroughTests();
        c.Go();
    }
}
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

The Page Object Model will help to remove information about the page from your tests. To use this pattern well, you have to think of it more like a "widget object model" as you don't want to represent the whole page with one class, but more like a related widget, like the "search feature".

So you would create a class that has private members for each of the elements you want to interact with, then encapsulate all of the interactions in that same class.

Let's look at this from a calling perspective... what can I tell about your product feature by reading your code...

Driver.Navigate().GoToUrl(ConfigurationManager.AppSettings["Host"]);

Driver.FindElementsByTagName("a").ToList()[0].Click();

#region Question 1
CheckBreadcrumbMenu(1);
ClickRadBtnByIndex(3);
#endregion

// Things are quite repetetive in this middle section...
// So I have collapsed the code for now

#region Question 5
CheckBreadcrumbMenu(5);
MoveSlider(7, "left");
Driver.FindElementById("ForwardButton").Click();

And what could you tell if we had a page object model? (Note: because I can't tell what your product feature does, I have had to use some examples here for you to guess what my product feature does instead). I have used Shoudly to make my assertion at the end of the test.

ProductPage productPage = new ProductPage();

productPage.AddProduct();
productPage.SetName("Test Product");
productPage.SetPrice(100);
productPage.Save();

productPage.ConfirmationMessage.ShouldBe("Saved OK");

This second version has no knowledge of Selenium, or the HTML elements. It means the test can express the behaviour you expect, rather than being full of distracting selectors and WebDriver method calls.

Instead of accessing the elements directly, there are methods available that allow you to express the action you want to take. That action may be a single WebDriver action, or interaction with many elements (for example loginPage.LogIn("Test User", "Test Password"), which would enter the two values and submit the form).

Here is an example page object model that shows how you might set things up, taken from this Selenium Page Object Elements article.

public class ExamplePage
{
    [FindsBy(How = How.Id, Using = "name")]
    private IWebElement NameInput { get; set; }

    [FindsBy(How = How.Id, Using = "email")]
    private IWebElement EmailInput { get; set; }

    [FindsBy(How = How.Id, Using = "go")]
    private IWebElement GoButton { get; set; }

    internal void EnterName(string name)
    {
        NameInput.Clear();
        NameInput.SendKeys(name);
    }

    internal void EnterEmail(string email)
    {
        EmailInput.Clear();
        EmailInput.SendKeys(email);
    }

    internal void ClickGoButton()
    {
        GoButton.Click();
    }
}

This is quite granular, and as I mentioned, if the three methods shown here are really a single interaction, such as logging in, you could expose it with just one method instead. In fact, I would model my calling example in that way:

ProductPage productPage = new ProductPage();
productPage.AddProduct("Test Product", 100);
productPage.ConfirmationMessage.ShouldBe("Saved OK");

Now the test does a better job of expressing the behaviour of the system, and the page object model is the mapping to the UI.

You should find that by introducing the Page Object Model, most of the repetition in your current code is eliminated.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.