4
\$\begingroup\$

I have recently started to create my first Selenium Framework to help test the product at my work. I have managed to implement the first three pages and would like some feedback on and selenium or C# practices I can improve on.

Loading the website takes you to the login page. Once the user logs in, they are take to the DashboardPage. All the fields we care about on the page is the navigation bar. Clicking on StartNewQuote button takes the user to the clientDetailsOne Page.

I have a lot more pages to do so I would like to know if I am doing anything wrong or cant improve my practices sooner rather than later.

I also plan on implementing Cucumber, Gherkin, and Specflow in the future to make I more business readable.

TestMethod:

[TestMethod]
public void Test()
{
    NavigationHelper.NavigateToUrl(ObjectRepository.Config.GetWebsite());

    var loginPage = new LoginPage();
    loginPage.User_Login_As(ObjectRepository.Config.GetUsername(), ObjectRepository.Config.GetPassword());

    var dashboardPage = new DashboardPage();
    dashboardPage.GoToClientDetailsOnePage();

    var clientDetailsOnePage = new ClientDetailsOnePage();
    clientDetailsOnePage.TitleTextBox("Mr");
    clientDetailsOnePage.FirstNameTextBox("John");
    clientDetailsOnePage.NameTextBox("Smith");
    clientDetailsOnePage.DesignatedAuthorityTextBox("None");
    clientDetailsOnePage.UKResidentCheckBox();
    clientDetailsOnePage.NextButton();

    Thread.Sleep(5000);
}

LoginPage.cs:

using OpenQA.Selenium;
using TestAutomationFrameworkPOMS.Settings;
using OpenQA.Selenium.Support.PageObjects;

namespace TestAutomationFrameworkPOMS.PageObject.ContactCentre
{
    public class LoginPage 
    {
        public LoginPage()
        {
            PageFactory.InitElements(ObjectRepository.Driver, this);
        }

        [FindsBy(How = How.Id, Using = "username-inputEl")]
        private IWebElement Username;

        [FindsBy(How = How.Id, Using = "password-inputEl")]
        private IWebElement Password;

        [FindsBy(How = How.Id, Using = "remember-displayEl")]
        private IWebElement RememberMe;

        [FindsBy(How = How.LinkText, Using = "Login")]
        private IWebElement Login;

        public void User_Login_As(string username, string password)
        {
            Username.SendKeys(username);
            Password.SendKeys(password);
            RememberMe.Click();
            Login.Click();
        }
    }
}

DashboardPage.cs:

using OpenQA.Selenium;
using OpenQA.Selenium.Support.PageObjects;
using TestAutomationFrameworkPOMS.Settings;

namespace TestAutomationFrameworkPOMS.PageObject.ContactCentre
{
    public class DashboardPage
    {
        public DashboardPage()
        {
            PageFactory.InitElements(ObjectRepository.Driver, this);
        }

        #region Navigation

        [FindsBy(How = How.Id, Using = "AgentPAS")]
        private IWebElement NavigationPolicy;

        [FindsBy(How = How.Id, Using = "Billing")]
        private IWebElement NavigationBilling;

        [FindsBy(How = How.Id, Using = "id_LogOut")]
        private IWebElement NavigationLogout;

        [FindsBy(How = How.Id, Using = "id_Home")]
        private IWebElement NavigationDashboard;

        [FindsBy(How = How.Id, Using = "id_Search")]
        private IWebElement NavigationSearch;

        [FindsBy(How = How.Id, Using = "id_New")]
        private IWebElement NavigationStartNewQuote;

        public void GoToPolicyExpress()
        {
            NavigationPolicy.Click();
        }

        public void GoToBillingExpress()
        {
            NavigationBilling.Click();
        }

        public void GoToLogoutButton()
        {
            NavigationLogout.Click();
        }

        public void GoToDashboardPage()
        {
            NavigationDashboard.Click();
        }

        public void GoToSearchPage()
        {
            NavigationSearch.Click();
        }

        public void GoToClientDetailsOnePage()
        {
            NavigationStartNewQuote.Click();
        }

        #endregion
    }
}

ClientDetailsOnePage.cs:

using OpenQA.Selenium;
using OpenQA.Selenium.Support.PageObjects;
using TestAutomationFrameworkPOMS.ComponentHelper;
using TestAutomationFrameworkPOMS.Settings;

namespace TestAutomationFrameworkPOMS.PageObject.ContactCentre
{
    public class ClientDetailsOnePage
    {
        #region InitiateElements
        public ClientDetailsOnePage()
        {
            PageFactory.InitElements(ObjectRepository.Driver, this);
        }
        #endregion

        #region WebElements
        [FindsBy(How = How.XPath, Using = "//input[@fieldref='AccountInput.Title']")]
        private IWebElement Title;

        [FindsBy(How = How.XPath, Using = "//input[@fieldref='AccountInput.FirstName']")]
        private IWebElement FirstName;

        [FindsBy(How = How.XPath, Using = "//input[@fieldref='AccountInput.Name']")]
        private IWebElement Name;

        [FindsBy(How = How.XPath, Using = "//input[@fieldref='AccountInput.DesignatedAuthority']")]
        private IWebElement DesignatedAuthority;

        [FindsBy(How = How.XPath, Using = "//span[@data-ref='displayEl']")]
        private IWebElement UKResident;

        [FindsBy(How = How.LinkText, Using = "Save & Exit")]
        private IWebElement SaveExit;

        [FindsBy(How = How.LinkText, Using = "Next")]
        private IWebElement Next;
        #endregion

        #region WebActions
        public void TitleTextBox(string text)
        {
            Title.Clear();
            Title.SendKeys(text);
            Title.SendKeys(Keys.Enter);
        }

        public void FirstNameTextBox(string text)
        {
            FirstName.Clear();
            FirstName.SendKeys(text);
            FirstName.SendKeys(Keys.Enter);
        }

        public void NameTextBox(string text)
        {
            Name.Clear();
            Name.SendKeys(text);
            Name.SendKeys(Keys.Enter);
        }

        public void DesignatedAuthorityTextBox(string text)
        {
            DesignatedAuthority.Clear();
            DesignatedAuthority.SendKeys(text);
            DesignatedAuthority.SendKeys(Keys.Enter);
        }

        public void UKResidentCheckBox()
        {
            UKResident.Click();
        }

        public void SaveExitButton()
        {
            SaveExit.Click();
        }

        public void NextButton()
        {
            Next.Click();
        }
        #endregion

        #region Navigation

        [FindsBy(How = How.Id, Using = "AgentPAS")]
        private IWebElement NavigationPolicy;

        [FindsBy(How = How.Id, Using = "Billing")]
        private IWebElement NavigationBilling;

        [FindsBy(How = How.Id, Using = "id_LogOut")]
        private IWebElement NavigationLogout;

        [FindsBy(How = How.Id, Using = "id_Home")]
        private IWebElement NavigationDashboard;

        [FindsBy(How = How.Id, Using = "id_Search")]
        private IWebElement NavigationSearch;

        [FindsBy(How = How.Id, Using = "id_New")]
        private IWebElement NavigationStartNewQuote;

        public void GoToPolicyExpress()
        {
            NavigationPolicy.Click();
        }

        public void GoToBillingExpress()
        {
            NavigationBilling.Click();
        }

        public void GoToLogoutButton()
        {
            NavigationLogout.Click();
        }

        public void GoToDashboardPage()
        {
            NavigationDashboard.Click();
        }

        public void GoToSearchPage()
        {
            NavigationSearch.Click();
        }

        public void GoToClientDetailsOnePage()
        {
            NavigationStartNewQuote.Click();
        }

        #endregion
    }
}
\$\endgroup\$
2
\$\begingroup\$

Naming

  • Consistency: User_Login_As What's up with this name having underscores between words when everything else is just standard PascalCasing?
  • Clarity: TitleTextBox("Mr") I can imply from usage that this sends the keys "Mr" to the Title textbox, but do you want your code to rely on seeing it used to know what it does? Consider a name like SetTitleText or SendKeysToTitleTextBox if you prefer a longer descriptive name.

Method Extensions

I've found it incredibly useful when dealing with Selenium to create some method extensions to do some common activities for me (instead of having to code the same things over and over again).

public void NameTextBox(string text)
{
    Name.Clear();
    Name.SendKeys(text);
    Name.SendKeys(Keys.Enter);
}

You do a .Clear(); and .SendKeys(Keys.Enter); when setting the text for all of these similar functions. I'd recommend creating a method extension like this,

public static class IWebElementExtensions
{
    /// <summary>
    /// Clears the element, Sends the indicated value, and presses Enter.
    /// </summary>
    public static void ClearSendAndEnter(this IWebElement element, string value)
    {
        element.Clear();
        element.SendKeys(value);
        element.SendKeys(Keys.Enter);
    }
}

public void NameTextBox(string text)
{
    Name.ClearSendAndEnter(text);
}

Regions

Is the region below really adding value to your code?

#region InitiateElements
public ClientDetailsOnePage()
{
    PageFactory.InitElements(ObjectRepository.Driver, this);
}
#endregion

The way you're using regions implies a lack of inheritance. You could simply pull each section into a separate class and apply inheritance appropriately. Make a navigation class to contain the code inside your navigation region then just add a property to your ClientDetailsOnePage. I assume there is some code that will apply to all pages in your selenium framework? Navigation and Page initialization for instance. Make a BasePage or Page class that all of those pages inherit from so you can share code between them.

Doing this should remove your need for regions. The only time you need regions is if you're using them in place of inheritance or your class is just that big (which is still sometimes due to a lack of inheritance).

Testing

  • Test One Feature: Getting to a page with a specific link is its own test. Your framework would be more flexible to be able to specify specific pages to go to. That way it's more obvious what has gone wrong when something fails.
  • Assert/Test: It doesn't seem like you're actually testing that what you wanted to happen actually occurred. It seems more like you're testing that no exceptions were thrown (which is the only case this test would fail).

See comments below.

[TestMethod]
public void Test()
{
    // Are you not able to just go straight to the login page?
    // Testing that you're able to get their by clicking a specific link would be a separate test.
    NavigationHelper.NavigateToUrl(ObjectRepository.Config.GetWebsite());

    // I'm going to assume that to proceed you must be logged in, so this is obviously needed in that case.
    var loginPage = new LoginPage();
    loginPage.User_Login_As(ObjectRepository.Config.GetUsername(), ObjectRepository.Config.GetPassword());

    // Can we not just go straight to the client details one page?
    // Testing that you're able to get somewhere via a specific link is again, a separate test.
    // It keeps thinks simple and concise so you know what has gone wrong or changed when a test fails.
    var dashboardPage = new DashboardPage();
    dashboardPage.GoToClientDetailsOnePage();

    var clientDetailsOnePage = new ClientDetailsOnePage();
    clientDetailsOnePage.TitleTextBox("Mr");
    clientDetailsOnePage.FirstNameTextBox("John");
    clientDetailsOnePage.NameTextBox("Smith");
    clientDetailsOnePage.DesignatedAuthorityTextBox("None");
    clientDetailsOnePage.UKResidentCheckBox();
    clientDetailsOnePage.NextButton();

    // What's this for? Are you waiting on the form to submit?
    // Shouldn't you check that you got the result that you want?
    // We're not certain here that we didn't get any errors or if we were successful.
    Thread.Sleep(5000);
}
\$\endgroup\$
1
\$\begingroup\$

It looks like you are testing 3 separate things in that test.

You should separate those things into their own tests, some of the tests will need setup, and that can be done with Test Methods.

Gherkin is a version of Cucumber (used in Ruby). Gherkin allows you to, in plain English, set up the order of things to be tested, but it also lets you set up objects for testing, or input data into your tests. These Scenarios are what is being tested, not the individual items. you may want to look into SpecFlow and Gherkin sooner, rather than later.

Your Testing looks more like GUI testing, which is what SpecFlow and Selenium are good for. but you are trying to test it from a code standpoint instead of a GUI standpoint.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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