1

It seems to be a best practice to return from one page object the next page object when navigating from one to another

I have the situation where after the first page object a simple "OK"-dialog informs the user about a status. And after clicking "OK" the next page object comes. Because I have handle such simple "OK" dialog often in the app, I did it till now in a generic way (find inside a "OK"-dialog page object the dialog and press OK). With this approach I cannot return the next page object.

To solve this I see these opportunities where I like to get some feedback on which of them might be the "best" one and what additional ideas you have.

  • implement for each of these "OK" dialogs a dedicated one which returns the next specific page object
  • create a generic Simple dialog where the type parameter is the next page object and inject the logic to find and return the next page object somehow (possible? how to do?)
  • don't follow the best practices and use the current approach where the simple "OK" dialog page object doesn't return the next page object and the "test logic" is resolving and calling the next page object themselves
  • let the first page object handle the call of the simple "OK"-dialog page object and then return the next page object from here; this would be fine for a simple "OK"-dialog, but I assume an astonishing coding experience in case it is a more complex "Yes/No"-dialog.

Thanks for your helping thoughts and feedback.

ps: might be important: the programming language is C#

Marko A.
  • 73
  • 9
  • Have you already seen this SO question? https://stackoverflow.com/questions/9106695/page-object-pattern-and-alternatives – Sabuncu Mar 09 '20 at 21:14

2 Answers2

1

Selenium guides to return PageObject to "model the user's journey through our application" Depending on the type of journey the following options work:

  • option two: If "Simple OK Dialog" is A PART of the user journey (i.e. you are going to use this page object in tests) you can use a generic class approach

SimpleDialogPage Declaration:

   public class SimpleDialogPage<T>
    {
     private IWebDriver driver;
     public SimpleDialogPage(IWebDriver driver){
            this.driver = driver;
     }
     public T ClickOk(){ 
     return new T(driver);
    }
  }

SimpleDialogPage Invocation from the "first page"

public SimpleDialogPage<TheNextPage> ClickNext(){ 
     return new SimpleDialogPage<TheNextPage>(driver)
    }
  • Option four: If "Simple OK Dialog" is NOT a part of the user journey (i.e. you are not going to use this page object in tests) just hide it behind the action
 TheNextPage ClickNextAndConfirmAlert(){
      SimpleDialogPage alert = _buttonNext.Click();
      alert.ClickOk();
      return new TheNextPage(driver);
 }
Anatoliy Sakhno
  • 146
  • 1
  • 7
0

Thanks @Anatoly!

I did it in a close way as you proposed. The current draft needs some refactorings, but the overall concept seems to be fine:

using FlaUI.Core.AutomationElements;
using System;

namespace Bvms.UiAutomation.SDK.Commons
{
    /// <summary>Enhanced simple dialog allowing to specify two delegates to define the next page objects
    /// for the confirmation and cancellation case.</summary>
    /// <typeparam name="T">Next page object in case of confirmation (OK).</typeparam>
    /// <typeparam name="TU">Next page object in case of canceling (Cancel).</typeparam>
    /// <seealso cref="Dialog" />
    public class GenericDialog<T, TU> : Dialog where T : PageObject where TU : PageObject
    {
        protected readonly Func<T> _confirmationCommand;
        protected readonly Func<TU> _cancelCommand;

        public GenericDialog(Window parentWindow, string windowsTitle, TimeSpan timeout)
            : this(parentWindow, windowsTitle, timeout, null, null)
        {
        }

        public GenericDialog(Window parentWindow, string windowsTitle, TimeSpan timeout, Func<T> confirmationCommand, Func<TU> cancelCommand)
            : base(parentWindow, windowsTitle, timeout)
        {
            _confirmationCommand = confirmationCommand;
            _cancelCommand = cancelCommand;
        }

        public new virtual (T, TU) EndDialog(DialogCompletion dialogCompletion)
        {
            switch (dialogCompletion)
            {
                case DialogCompletion.Confirmation:
                    return (ConfirmDialog(), default);
                case DialogCompletion.Cancellation:
                    return (default, CancelDialog());
                default:
                    throw new ArgumentOutOfRangeException(nameof(dialogCompletion), dialogCompletion, null);
            }
        }

        public virtual T ConfirmDialog()
        {
            if (null == _confirmationCommand)
            {
                throw new NotImplementedException($"Use ConfirmDialog(Func<T> confirmationCommand) or create with ctor: GenericDialog(Window parentWindow, string windowsTitle, TimeSpan timeout, Func<T> confirmationCommand, Func<TU> cancelCommand)");
            }
            return ConfirmDialog(_confirmationCommand);
        }

        public virtual TU CancelDialog()
        {
            if (null == _cancelCommand)
            {
                throw new NotImplementedException($"Use CancelDialog(Func<TU> cancelCommand) or create with ctor: GenericDialog(Window parentWindow, string windowsTitle, TimeSpan timeout, Func<T> confirmationCommand, Func<TU> cancelCommand)");
            }
            return CancelDialog(_cancelCommand);
        }

        public virtual T ConfirmDialog(Func<T> confirmationCommand)
        {
            base.EndDialog(DialogCompletion.Confirmation);
            return confirmationCommand();
        }

        public virtual TU CancelDialog(Func<TU> cancelCommand)
        {
            base.EndDialog(DialogCompletion.Cancellation);
            return cancelCommand();
        }
    }
}
Marko A.
  • 73
  • 9