[Заметка] Автоматическое создание Браузера и инициализация PageObject

Проблема:

Многих людей, хлебом не корми – дай только пописать лишний код, да и передать лишний вебдрайвер каждому ПейджОбжекту в самый конструктор…

В примере ниже, я покажу, как избежать лишних явных созданий экземпляра вебдрайвера и лишних инициализаций PageFactory.InitElements

Я понимаю, что многие начали работу с PageObject по этому примеру с PageFactory, но ведь это совсем не значит, что этот пример самый оптимальный. Это – просто пример.

Я не хочу каждый раз инициализировать вебдрайвер. Я не хочу, каждый раз инициализировать страницу при помощи PageFactory. Я просто, хочу писать код…

Решение

Для начала, разберемся с надоедливым созданием нового экземпляра WebDriver в каждом тесте. Пусть Вебдрайвер – сам себя создает, когда мне это нужно.

Вызов Browser.Driver(), вернет либо уже созданный Вебдрайвер, либо создаст его при первом обращении.

    // Вызови Browser.Driver(), и драйвер  – твой!
    public static class Browser
    {
        private static IWebDriver driver;

        // Автоматически создает новый WebDriver при 
        // первом обращении, либо возвращает уже созданный 
        public static IWebDriver Driver()
        {
            // Если driver равен null (??), то создать новый FirefoxDriver
            driver = driver ?? new FirefoxDriver();
            return driver;
        }

        // Driver капут
        public static void CloseDriver()
        {
            if (driver != null) driver.Quit();
        }

        
        // Эммм… у статических классов – нет деструктора. 
        // Тут создается объект обычного класса Finalizer, 
        // который будет безжалостно уничтожен .NET фреймворком 
        // по завершению теста. А за собой он потянет закрытие 
        // вебдрайвера. 
        static readonly Finalizer finalizer = new Finalizer();
        sealed class Finalizer
        {
            ~Finalizer()
            {
                CloseDriver();
            }
        }
    }

Идем дальше.
Я хочу, чтобы каждая страница сама себя инициализировала при помощи PageFactory.InitElements. Тем самым, при создании нового экземпляра страницы, будет возвращаться готовая страница, с которой уже можно работать, а не какой-то полуфабрикат, который еще и на фабрику отправлять нужно.
Для этого, необходимо создать специальный базовый класс.

В .NET есть такая особенность: конструкторы без параметров дочерних классов, будут автоматически вызывать, в первую очередь, конструктор базового класса.
Вы спрашиваете, будет ли это работать и в Java? – Не знаю, попробуйте.

    // Это базовый класс для всех страниц 
    public abstract class BasePage
    {
        // Полезное свойство. Позволяет писать меньше точек. 
        public IWebDriver Driver { get { return Browser.Driver();  } }

        // Конструктор без параметров дочернего класса, 
        // автоматически вызывает конструктор без параметров базового. 
        // Тут очень важно, что это работает только для конструкторов 
        // ** без параметров **. 
        // На этом свойстве и сыграем.
        public BasePage()
        {
            // На самом деле, this – это будет объект дочернего класса. 
            // PageFactory умеет с ним работать со столь запутанной 
            // схемой. 
            // Убийца – садовник. Извините. 

            PageFactory.InitElements(Driver, this);
        }
    }

Пришел черед создать PageObject, который декларирует страницу. В нем есть два действия (метода):

  • Invoke() – прото открывает страницу
  • Calcualte() – выполняет действия по вычислению логарифма
    public class LogCalculatorPage : BasePage
    {
        // =================== Элементы страницы =======================
        // 
        [FindsBy(How = How.XPath, Using = @"//input[@name='b']")]
        protected IWebElement txtLogBase { get; set; }

        [FindsBy(How = How.XPath, Using = @"//input[@name='x']")]
        protected IWebElement txtLog { get; set; }

        // Товарищи, эти локаторы были записаны на скорую руку при помощи 
        // SWD Page Recorder 
        // И мне лень было их оптимизировать. Но, это не значит, что это
        // правильный путь. 
        [FindsBy(How = How.XPath, Using = @"//form[@name='calcform1']/table[1]/tbody[1]/tr[5]/td[2]/input[1]")]
        protected IWebElement btnCalculate { get; set; }

        [FindsBy(How = How.XPath, Using = @"//input[@name='y']")]
        protected IWebElement txtResult { get; set; }
        // =================== ~~~~~~~~~~~~~~~~~ =======================


        // Тыщ-тыщ, «вызывает» страницу 
        public void Invoke()
        {
            // Я еще раз напомню, что Driver – унаследован из базового 
            // класса, 
            // А на самом деле, при каждом таком обращении, вызывается 
            // Browser.Driver(). 
            // Но, удобно же, правда?
            Driver.Navigate().GoToUrl(@"http://www.rapidtables.com/calc/math/Log_Calculator.htm");
        }


        // Калькъ!
        public double Calcualte(string logBase, double logValue)
        {
            txtLogBase.SendKeys(logBase);
            
            txtLog.SendKeys(logValue.ToString());

            btnCalculate.Click();

            var rawResult = txtResult.GetAttribute("value");

            return Convert.ToDouble(rawResult);
        }
    }

А вот так выглядит тест:

        static void Main(string[] args)
        {
            // Магия базового класса! При создании объекта, 
            // он уже будет проинициализирован PageFactory
            var calcPage = new LogCalculatorPage();

            calcPage.Invoke();

            var result = calcPage.Calcualte("10", 100);

            Console.WriteLine("Вот такой суровый логарифм: " + result);
        }

Видео работы теста, чтобы вы не говорили, что я обманываю :slight_smile:

Весь исходный код – тут

3 лайка

Джава + твик на многопоточность.

3 лайка

C# понятен, но за Java приятно :slight_smile:

Я переместил(а) часть сообщений (17) в новую тему: Особенности инициализации элементов страниц в PageFactory

а что лучше, такой подход? Или простеньким синглтоном создать инстанс драйвер, и в любом месте дергать этот геттер?

public class DriverSetup {

   private static DriverSetup _instance = null;

   private WebDriver driver;

    public DriverSetup() {
        driver = new ChromeDriver();
        wait = (WebDriverWait) new WebDriverWait(driver, EXPLICIT_WAIT_TIME).withMessage("Element was not found");
        driver.manage().timeouts().implicitlyWait(DEFAULT_WAIT_TIME, TimeUnit.SECONDS);
    }

    public static DriverSetup getInstance() {
        if (_instance == null) {
            _instance = new DriverSetup();
        }
        return _instance;
    }

    public WebDriver getDriver() {
        return driver;
    }

    public WebDriverWait getWait() {
        return wait;
    }

}

как-то так, не проверял работает или нет, но должно без проблем, а вызывать его так:

DriverSetup.getInstance().getDriver();
//
driver = DriverSetup.getInstance().getDriver();