1

I currently have a single class that gets a string which is the heading of a certain page. Depending on that heading, I need to call a method from the corresponding class. The button click method will be different depending on the page. These page classes will become quite big as there are plenty of controls on each page. My StepDefinitions class will be receiving any action (e.g. a button click) and then needs to direct it to the right class to do the action.

PageClass one:

namespace MobileAppTesting.Pages
{
    class LoginPage
    {
        public string PageName;

        public void ClickButton()
        {
            //click button
        }
    }
}

PageClass two:

namespace MobileAppTesting.Pages
{
    class HomePage
    {
        public string PageName;

        public HomePage()
        {
            PageName = "Home";
        }
        public void ClickButton()
        {
            //click button
        }
    }
}

Generic Class:

namespace MobileAppTesting.Pages
{
    class GenericPage
    {
        public GenericPage()
        {
            //empty constructor
        }
        public void ClickButton()
        {
            //click button
        }
    }
}

StepDefinitions:

namespace MobileAppTesting
{
    class StepDefinitions
    {
        LoginPage _loginPage;
        HomePage _homePage;

        public StepDefinitions()
        {
            _loginPage = new LoginPage();
            _homePage = new HomePage();
        }

        public GetCurrentClass() 
        {
            string pageName = GetPageName(); //would return page name or empty if no pagename available
            *HELP****************************************************************************
            return single class where PageName of the Page Classes = pageName, if empty return GenericPage ?? <-- This is where I am stuck
        }

        public void ClickButton(string buttonName) 
        {
            GetCurrentClass().ClickButton();
        }
    }
}
ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
  • Please be careful not to misuse tags. Most tags have descriptions that you can read by hovering over them, and the descriptions usually outline cases where you should or shouldn't use the tags. For example, the `[visual-studio]` question should only be used for questions about the Visual Studio application, as opposed to code authored within the VS environment. I've removed that tag for you. – ProgrammingLlama May 25 '21 at 03:35
  • What is the correct signature of `GetCurrentClass`? Curently you're missing the return type, so it's not clear what you intend to return. – ProgrammingLlama May 25 '21 at 03:36
  • `GetPageName` is simply a method that searches for a heading on a page with a selector and returns the found header. E.g. the heading of the page could be "Login" or "Home" or there might be none is it would return and empty string – Jeremias Rößner May 25 '21 at 03:41
  • @Llama the signature of `GetCurrentClass` would most likely be a type class itself? This is where I am stuck and not sure how to go about – Jeremias Rößner May 25 '21 at 03:44
  • 1
    Are you trying to return an _instance_ or a _type_? I think that better helps clear up exactly what `GetCurrentClass` is actually returning. You could use reflection if you just want the type. If you want an instance you could just store the instances in a dictionary by their page name. – Zer0 May 25 '21 at 03:47
  • You'll need an if / else if (or else) statement to determine what to return. If you want to return an instance of a type, you can use Olivier's answer. If you want to return type information, then you'll have to return `Type`. It's not really clear which you're asking for here. – ProgrammingLlama May 25 '21 at 03:49

2 Answers2

3

You need to use polymorphism or an interface.

Using polymorphism

Here is the abstract root class that have the page name and the default click behavior:

public abstract class PageBase
{
  public string PageName;
  public abstract void ClickButton();
}

And some childs that defines this click:

class LoginPage : PageBase
{
  public override void ClickButton()
  {
  }
}

class HomePage : PageBase
{
  public override void ClickButton()
  {
  }
}

Thus we can write that using a default page instance to be created in the constructor, and some pages put in a list, except the default:

using System.Collections.Generic;
using System.Linq;

class StepDefinitions
{
  PageBase _defaultPage;

  LoginPage _loginPage;
  HomePage _homePage;

  List<PageBase> Pages = new List<PageBase>();

  public StepDefinitions()
  {
    _defaultPage = new ...
    _loginPage = new LoginPage();
    _homePage = new HomePage();
    Pages.Add(_loginPage);
    Pages.Add(_homePage);
  }

  public PageBase GetCurrentClass(string pageName)
  {
    if ( string.IsNullOrEmpty(pageName) )
      return _defaultPage;
    else
      return Pages.Where(page => page.PageName == pageName).FirstOrDefault() ?? DefaultPage;
  }

  public void ClickButton(string buttonName)
  {
    GetCurrentClass(buttonName).ClickButton();
  }
}

Using an interface

Here, we are not using abstraction and inheritance but we have to repeat the common data members as well as all the common operations that cannot be factored (and I really dislike that but it must be used in certains cases espacially whithout multiple inheritance and true generic polymorphism on open types):

public interface IPage
{
  string PageName { get; }
  void ClickButton();
}

class LoginPage : IPage
{
  public string PageName { get; }
  public void ClickButton()
  {
  }
}

class HomePage : IPage
{
  public string PageName { get; }
  public void ClickButton()
  {
  }
}

The usage is relatively the same:

class StepDefinitions
{
  IPage _defaultPage;

  LoginPage _loginPage;
  HomePage _homePage;

  List<IPage> Pages = new List<IPage>();

  public StepDefinitions()
  {
    _defaultPage = new ...
    _loginPage = new LoginPage();
    _homePage = new HomePage();
    Pages.Add(_loginPage);
    Pages.Add(_homePage);
  }

  public IPage GetCurrentClass(string pageName)
  {
    if ( string.IsNullOrEmpty(pageName) )
      return _defaultPage;
    else
      return Pages.Where(page => page.PageName == pageName).FirstOrDefault() ?? DefaultPage;
  }

  public void ClickButton(string buttonName)
  {
    GetCurrentClass(buttonName).ClickButton();
  }
}

Improvement

For speed and to avoid using Linq every time, we can use a dictionary instead of the exposed list for the idea:

Dictionary<string, PageBase> Pages = new Dictionary<string, PageBase>();

Pages.Add(_loginPage.PageName, _loginPage);

return Pages.Contains(pageName) ? Pages[pageName] : DefaultPage;

And that's better. But... pages can't have the same name, else the list is required and the search logic must be revised.

Links

Abstraction

Encapsulation

Polymorphism

Interface & Class

About the lack of true generic polymorphism and the missing diamond operator in C#

  • 3
    This works, although I'd suggest a dictionary instead of list to make it O(1), presuming no hash collisions. Not really sure how many pages there are in that list. Perfectly fine if the list is small. – Zer0 May 25 '21 at 03:52
  • 2
    This is perfect, exactly what I was trying to do. Thank you :) – Jeremias Rößner May 25 '21 at 03:59
  • @Zer0 Indeed, using a `Dictionary` will be a standard improvement on the first code review instead of `Where`. –  May 25 '21 at 04:35
  • Or just write it O(1) to begin with, especially since it's very minimal effort over the O(N) solution. But I digress... I'm just the annoying performance guy. – Zer0 May 25 '21 at 04:39
  • @Zer0 No you are right. After such a draft, I use a dictionary. I'm not used to using things like the HashSet or anything else especially if I have to implement a tedious comparison. In general for what I do the dictionaries are sufficient. I use dictionaries of lists or dictionaries, of ..., a lot –  May 25 '21 at 04:44
0

@Olivier Rogier's answer is good, but this is another approach.

namespace StackOverflowConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            StepDefinitions stepDefinitions = new StepDefinitions();

            Console.WriteLine($"Login Page");
            stepDefinitions.ClickButton("Login");

            Console.WriteLine($"Home Page");
            stepDefinitions.ClickButton("Home");

            Console.WriteLine($"Default Page");
            stepDefinitions.ClickButton(string.Empty);

            PageFactory.AddPage(new ExtraPage());

            Console.WriteLine($"Extra Page");
            stepDefinitions.ClickButton("Extra");

            Console.ReadKey();
        }
    }

    abstract class PageBase
    {
        public string PageName { get; set; }
        public abstract void ClickButton();
    }
    class HomePage : PageBase
    {
        public HomePage()
        {
            PageName = "Home";
        }
        public override void ClickButton()
        {
            Console.WriteLine("Home Page Button Clicked");
        }
    }

    class LoginPage : PageBase
    {
        public LoginPage()
        {
            PageName = "Login";
        }
        public override void ClickButton()
        {
            Console.WriteLine("Login Page Button Clicked");
        }
    }

    class ExtraPage : PageBase
    {
        public ExtraPage()
        {
            PageName = "Extra";
        }
        public override void ClickButton()
        {
            Console.WriteLine("Etra Page Button Clicked");
        }
    }
    class PageFactory
    {
        private static PageFactory instance;
        private static Dictionary<string, PageBase> _pages = new Dictionary<string, PageBase>();
        const string defaultPage = "Home";
        private PageFactory()
        {
            LoadPages();
        }
        private static void LoadPages()
        {
            _pages.Add("Home", new HomePage());
            _pages.Add("Login", new LoginPage());
        }
        // Singleton Access
        public static PageFactory GetInstance()
        {
            if (instance != null)
                return instance;

            instance = new PageFactory();
            return instance;
        }

        public static void AddMorePages(List<PageBase> pages)
        {
            foreach (var page in pages)
            {
                _pages.Add(page.PageName, page);
            }
        }

        public static void AddPage(PageBase page)
        {
            AddMorePages(new List<PageBase> { page });
        }

        public PageBase GetPage(string pageName)
        {
            if (_pages.ContainsKey(pageName))
               return _pages[pageName];

            return _pages[defaultPage];
        }
    }

    class StepDefinitions
    {
        public PageBase GetCurrentClass(string pageName)
        {
            return PageFactory.GetInstance().GetPage(pageName);
        }

        public void ClickButton(string buttonName)
        {
            GetCurrentClass(buttonName).ClickButton();
        }
    }
}
Biju Kalanjoor
  • 532
  • 1
  • 6
  • 12