Перейти к содержимому

Фотография

C# - NUnit - WebDriver. Внедрение Page Object. Типовой пример

C# NUnit WebDriver Page Object

  • Авторизуйтесь для ответа в теме
Сообщений в теме: 21

#1 Ivis

Ivis

    Новый участник

  • Members
  • Pip
  • 53 сообщений
  • Город:Одесса


Отправлено 16 августа 2017 - 14:02

Друзья.

Я не так давно в автоматизации тестировании и только недавно начал изучать программирование.
Работаю над своим первым проектом (C# - NUnit - Selenium WebDriver).

Написал первые тесты и осознал, что упустил по сути самое главное - про Page Object я совсем забыл. В итоге у меня все запросы написаны в хелперах, и иногда даже в тестах (что не есть хорошо, я знаю - но сейчас не об этом).

Структура проекта сейчас выглядит следующим образом:

 

Прикрепленный файл  2017-08-16_163727.png   6,72К   15 Количество загрузок:

 

Прикрепленный файл  2017-08-16_162548.png   33,53К   10 Количество загрузок:

 

В тестах - тесты, в хелперах - сценарии с запросами, ну и в менеджере приложений - запуск/остановка браузера.

Пример теста:

    [TestFixture]
    public class Login : TestBase
    {
        [Test]
        public void Login_LoginWithValidCredentials()
        {
            // prepare
            app.Auth.Logout();


            // action
            AccountData account = new AccountData("autotests-1@replyteam.io", "reply-auto");
            app.Auth.Login(account);


            // verification      
            //app.Auth.WaitForElement(By.TagName("rp-dropdown-menu"));
            Assert.IsTrue(app.Auth.IsLoggedIn(account));
        }
    }

И пример хелпера:

{
    public class LoginHelper : HelperBase
    {
        public LoginHelper(ApplicationManager manager) : base(manager)
        {
        }




        public void Login(AccountData account)
        {
            if (IsLoggedIn())
            {
                if (IsLoggedIn(account))
                {
                    return;
                }
                Logout();
            }


            manager.Navigator.GoToLoginPage();


            Type(By.Name("Email"), account.Username);
            Type(By.Name("Password"), account.Password);
            driver.FindElement(By.CssSelector("button[type=\"submit\"]")).Click();


            WaitForElement(By.TagName("rp-dropdown-menu"));
        }


        public bool IsLoggedIn()
        {
            return IsElementPresent(By.TagName("rp-dropdown-menu"));
        }


        public bool IsLoggedIn(AccountData account)
        {
            return IsLoggedIn()
                && GetLoggedUserName() == account.Username;
        }


        public string GetLoggedUserName()
        {
            string text = driver.FindElement(By.CssSelector(".menu-title")).Text;
            //System.Console.Out.Write(text);
            return text;
        }


        public void Logout()
        {
            if (IsLoggedIn())
            {
                driver.FindElement(By.TagName("rp-dropdown-menu")).Click();
                driver.FindElement(By.LinkText("Logout")).Click();
            }            
        }
    }
}

Гуглил много на эту тему достаточно много, но ничего подходящего, к сожалению, не нашёл. Видел пару примеров, в которых сценарии пишутся прямо в объектах страниц, без менеджера приложений, минуя хелперы, и всё...
Т.е. в целом я понимаю что такое Page Object, но не знаю что делать мне конкретно... т.е. застрял на архитектурном вопросе + его коддинге.

Подскажите, пожалуйста, как будет наиболее просто и эффективно внедрить Page Object Pattern в мой проект? 
В идеале увидеть какой-то работающий пример кода, который бы мне позволил взять его структуру 'as is' и на его основании строить и наращивать свой проект.


  • 0

#2 Alex

Alex

    Постоянный участник

  • Members
  • PipPipPip
  • 237 сообщений
  • ФИО:Алексей

Отправлено 17 августа 2017 - 08:09

Друзья.

Я не так давно в автоматизации тестировании и только недавно начал изучать программирование.
Работаю над своим первым проектом (C# - NUnit - Selenium WebDriver).

Написал первые тесты и осознал, что упустил по сути самое главное - про Page Object я совсем забыл. В итоге у меня все запросы написаны в хелперах, и иногда даже в тестах (что не есть хорошо, я знаю - но сейчас не об этом).

Структура проекта сейчас выглядит следующим образом:

 

attachicon.gif2017-08-16_163727.png

 

attachicon.gif2017-08-16_162548.png

 

В тестах - тесты, в хелперах - сценарии с запросами, ну и в менеджере приложений - запуск/остановка браузера.

Пример теста:

    [TestFixture]
    public class Login : TestBase
    {
        [Test]
        public void Login_LoginWithValidCredentials()
        {
            // prepare
            app.Auth.Logout();


            // action
            AccountData account = new AccountData("autotests-1@replyteam.io", "reply-auto");
            app.Auth.Login(account);


            // verification      
            //app.Auth.WaitForElement(By.TagName("rp-dropdown-menu"));
            Assert.IsTrue(app.Auth.IsLoggedIn(account));
        }
    }

И пример хелпера:

{
    public class LoginHelper : HelperBase
    {
        public LoginHelper(ApplicationManager manager) : base(manager)
        {
        }




        public void Login(AccountData account)
        {
            if (IsLoggedIn())
            {
                if (IsLoggedIn(account))
                {
                    return;
                }
                Logout();
            }


            manager.Navigator.GoToLoginPage();


            Type(By.Name("Email"), account.Username);
            Type(By.Name("Password"), account.Password);
            driver.FindElement(By.CssSelector("button[type=\"submit\"]")).Click();


            WaitForElement(By.TagName("rp-dropdown-menu"));
        }


        public bool IsLoggedIn()
        {
            return IsElementPresent(By.TagName("rp-dropdown-menu"));
        }


        public bool IsLoggedIn(AccountData account)
        {
            return IsLoggedIn()
                && GetLoggedUserName() == account.Username;
        }


        public string GetLoggedUserName()
        {
            string text = driver.FindElement(By.CssSelector(".menu-title")).Text;
            //System.Console.Out.Write(text);
            return text;
        }


        public void Logout()
        {
            if (IsLoggedIn())
            {
                driver.FindElement(By.TagName("rp-dropdown-menu")).Click();
                driver.FindElement(By.LinkText("Logout")).Click();
            }            
        }
    }
}

Гуглил много на эту тему достаточно много, но ничего подходящего, к сожалению, не нашёл. Видел пару примеров, в которых сценарии пишутся прямо в объектах страниц, без менеджера приложений, минуя хелперы, и всё...
Т.е. в целом я понимаю что такое Page Object, но не знаю что делать мне конкретно... т.е. застрял на архитектурном вопросе + его коддинге.

Подскажите, пожалуйста, как будет наиболее просто и эффективно внедрить Page Object Pattern в мой проект? 
В идеале увидеть какой-то работающий пример кода, который бы мне позволил взять его структуру 'as is' и на его основании строить и наращивать свой проект.

Ну, в целом не так и плохо, чего уж. Только ваш LoginHelper на 80% можно просто переименовать в LoginPage и получить тот же PageObject.

 

В общем случае PageObject - это реализация классов страниц 1 в 1 как в приложении. Т.е. для LoginPage создаются поля userNameEdit, passwordEdit, btnLogin. И метод login(). При необходимости могут быть добавлены loginFail, getUserName, setUserName, getPassword, setPassword и т.п. Но суть в том, что вы ограничены именно этой страницей. Соответственно там не будет методов IsLoggedIn или Logout. Потому что этой функциональности нет на странице. Вот эти части конкретно в вашем примере, мы, как правило, выносим в AdministrationHelper, который отвечает за контроль сессии пользователя в приложении.

 

Также в Helper выносится логика, которая затрагивает за раз несколько страниц (как правило, это часто повторяющиеся сценарии работы с приложением: создать документ/заказ, перевести сущность в другой статус и т.п.).


  • 1

#3 Ivis

Ivis

    Новый участник

  • Members
  • Pip
  • 53 сообщений
  • Город:Одесса


Отправлено 17 августа 2017 - 08:55

А как это будет тогда выглядеть с точки зрения структуры тестового приложения? ApplicationManager, получается, будет управлять и хелперами, и объектами страниц?


  • 0

#4 checo

checo

    Опытный участник

  • Members
  • PipPipPipPip
  • 400 сообщений
  • Город:Н.Новгород

Отправлено 17 августа 2017 - 12:26

А как это будет тогда выглядеть с точки зрения структуры тестового приложения? ApplicationManager, получается, будет управлять и хелперами, и объектами страниц?

 

Можно для страниц сделать отдельный менеджер, если так удобнее.

Главное, чтобы зависимость была однонаправленная:

- апп менеджер и хелперы могут использовать страницы, а страницы не обращаются к хелперам

- драйвер используется только в страницах


  • 1

#5 Alex

Alex

    Постоянный участник

  • Members
  • PipPipPip
  • 237 сообщений
  • ФИО:Алексей

Отправлено 18 августа 2017 - 07:42

 

А как это будет тогда выглядеть с точки зрения структуры тестового приложения? ApplicationManager, получается, будет управлять и хелперами, и объектами страниц?

 

Можно для страниц сделать отдельный менеджер, если так удобнее.

Главное, чтобы зависимость была однонаправленная:

- апп менеджер и хелперы могут использовать страницы, а страницы не обращаются к хелперам

- драйвер используется только в страницах

 

+1

 

У нас как правило структура такая:

 

- tests (сами тесты)

- ui.pages - классы страниц

- ui.elements - обертки для элементов (это вовсе не обязательно)

- ui.utils - различные расширения для работы с драйвером, элементами

- helpers - собственно различные хелперы. Как минимум упомянутый AdministrationHelper. далее уже в зависимости от нужд тестов и самого приложения

- data - данные

- utils - различные утилиты

 

Это усредненно, если брать. Ограничения: страницы могут обращаться к элементам. И только. Ни тесты, ни кто либо другой с элементами не работает. Страницы работают внутри себя, могут использовать данные/утилиты, но не другие страницы (кроме варианта возвращения объекта новой страницы) и не хелперы. Хелперы могут дергать друг друга, а также методы страниц. В тестах возможно использование страниц и хелперов.


  • 1

#6 Ivis

Ivis

    Новый участник

  • Members
  • Pip
  • 53 сообщений
  • Город:Одесса


Отправлено 18 августа 2017 - 09:57

Спасибо за помощь.

В общих чертах всё стало понятно. Попробовал реализовать - со скрипом, но заработало.

 

Сейчас структура выглядит так:

 

Прикрепленный файл  2017-08-18_124609.png   45,75К   17 Количество загрузок:

 

Но есть большие сомнения в том, что правильно всё с кодом в плане архитектуры классов страниц (инициализация, ссылки на классы и т.д.).

Очень хотел бы услышать конструктивную критику того, что я накодил на данный момент.

 

 

ApplicationManager.cs

    public class ApplicationManager
    {
        protected IWebDriver driver;
        protected string baseURL;


        protected PageManager pageManager;


        protected LoginHelper loginHelper;
        protected NavigationHelper navigator;
        protected ContactHelper contactHelper;


        private static ThreadLocal<ApplicationManager> app = new ThreadLocal<ApplicationManager>();


        private ApplicationManager()
        {
            driver = new FirefoxDriver();
            baseURL = "...";


            pageManager = new PageManager(this);


            loginHelper = new LoginHelper(this);
            navigator = new NavigationHelper(this, baseURL);
            contactHelper = new ContactHelper(this);
        }


        ~ApplicationManager()
        {
            try
            {
                driver.Quit();
            }
            catch (Exception)
            {
                // Ignore errors if unable to close the browser
            }
        }




        public static ApplicationManager GetInstance()
        {
            if (! app.IsValueCreated)
            {
                ApplicationManager newInstance = new ApplicationManager();
                newInstance.Navigator.GoToLoginPage();
                app.Value = newInstance;
            }
            return app.Value;
        }
        
        public IWebDriver Driver
        {
            get
            {
                return driver;
            }
        }


        public PageManager Pages
        {
            get
            {
                return pageManager;
            }
        }


        public LoginHelper Auth
        {
            get
            {
                return loginHelper;
            }
        }


        public NavigationHelper Navigator
        {
            get
            {
                return navigator;
            }
        }


        public ContactHelper Contacts
        {
            get
            {
                return contactHelper;
            }
        }
    }

 

PageManager.cs

    public class PageManager    {
        protected IWebDriver driver;
        protected ApplicationManager manager;


        public PageManager(ApplicationManager manager)
        {
            this.manager = manager;
            driver = manager.Driver;


            Login = InitElements(new LoginPage(this));
        }


        private T InitElements<T>(T page) where T : AnyPage
        {
            PageFactory.InitElements(driver, page);
            return page;
        }


        public LoginPage Login { get; set; }
    }

AnyPage.cs

    public class AnyPage    {
        protected IWebDriver driver;
        private PageManager pageManager;


        public AnyPage(PageManager pageManager)
        {
            this.pageManager = pageManager;
        }
    }

LoginPage.cs

    public class LoginPage : AnyPage    {
        public LoginPage(PageManager pages) : base(pages)
        {
        }


        [FindsBy(How = How.Name, Using = "Email")]
        public IWebElement UsernameField;


        [FindsBy(How = How.Name, Using = "Password")]
        public IWebElement PasswordField;


        [FindsBy(How = How.CssSelector, Using = "button[type=\"submit\"]")]
        public IWebElement SubmitButton;




        public LoginPage LoginAsUser(string username, string password)
        {
            // type text to fields
            SubmitButton.Click();
            return this;
        }

  • 0

#7 Ivis

Ivis

    Новый участник

  • Members
  • Pip
  • 53 сообщений
  • Город:Одесса


Отправлено 18 августа 2017 - 14:43

И сразу ещё один вопрос по тому, как преобразовывать некоторые методы, использующие локаторы By в методы, использующие сразу веб-элементы страниц.

Например, ранее в HelperBase.cs у меня был метод ожидания элемента:

        public IWebElement WaitForElement(By locator)
        {
            WebDriverWait waitForElement = new WebDriverWait(driver, TimeSpan.FromSeconds(30));
            return waitForElement.Until(ExpectedConditions.ElementIsVisible(locator));
        }

Как мне его переписать, чтобы на вход можно было подавать IWebElement?

Или, может, как-нибудь можно получить локатор, зная нужный нам IWebElement?


  • 0

#8 Alex

Alex

    Постоянный участник

  • Members
  • PipPipPip
  • 237 сообщений
  • ФИО:Алексей

Отправлено 21 августа 2017 - 07:06

 

Спасибо за помощь.

В общих чертах всё стало понятно. Попробовал реализовать - со скрипом, но заработало.

 

Сейчас структура выглядит так:

 

attachicon.gif2017-08-18_124609.png

 

Но есть большие сомнения в том, что правильно всё с кодом в плане архитектуры классов страниц (инициализация, ссылки на классы и т.д.).

Очень хотел бы услышать конструктивную критику того, что я накодил на данный момент.

 

 

ApplicationManager.cs

    public class ApplicationManager
    {
        protected IWebDriver driver;
        protected string baseURL;


        protected PageManager pageManager;


        protected LoginHelper loginHelper;
        protected NavigationHelper navigator;
        protected ContactHelper contactHelper;


        private static ThreadLocal<ApplicationManager> app = new ThreadLocal<ApplicationManager>();


        private ApplicationManager()
        {
            driver = new FirefoxDriver();
            baseURL = "...";


            pageManager = new PageManager(this);


            loginHelper = new LoginHelper(this);
            navigator = new NavigationHelper(this, baseURL);
            contactHelper = new ContactHelper(this);
        }


        ~ApplicationManager()
        {
            try
            {
                driver.Quit();
            }
            catch (Exception)
            {
                // Ignore errors if unable to close the browser
            }
        }




        public static ApplicationManager GetInstance()
        {
            if (! app.IsValueCreated)
            {
                ApplicationManager newInstance = new ApplicationManager();
                newInstance.Navigator.GoToLoginPage();
                app.Value = newInstance;
            }
            return app.Value;
        }
        
        public IWebDriver Driver
        {
            get
            {
                return driver;
            }
        }


        public PageManager Pages
        {
            get
            {
                return pageManager;
            }
        }


        public LoginHelper Auth
        {
            get
            {
                return loginHelper;
            }
        }


        public NavigationHelper Navigator
        {
            get
            {
                return navigator;
            }
        }


        public ContactHelper Contacts
        {
            get
            {
                return contactHelper;
            }
        }
    }

 

PageManager.cs

    public class PageManager    {
        protected IWebDriver driver;
        protected ApplicationManager manager;


        public PageManager(ApplicationManager manager)
        {
            this.manager = manager;
            driver = manager.Driver;


            Login = InitElements(new LoginPage(this));
        }


        private T InitElements<T>(T page) where T : AnyPage
        {
            PageFactory.InitElements(driver, page);
            return page;
        }


        public LoginPage Login { get; set; }
    }

AnyPage.cs

    public class AnyPage    {
        protected IWebDriver driver;
        private PageManager pageManager;


        public AnyPage(PageManager pageManager)
        {
            this.pageManager = pageManager;
        }
    }

LoginPage.cs

    public class LoginPage : AnyPage    {
        public LoginPage(PageManager pages) : base(pages)
        {
        }


        [FindsBy(How = How.Name, Using = "Email")]
        public IWebElement UsernameField;


        [FindsBy(How = How.Name, Using = "Password")]
        public IWebElement PasswordField;


        [FindsBy(How = How.CssSelector, Using = "button[type=\"submit\"]")]
        public IWebElement SubmitButton;




        public LoginPage LoginAsUser(string username, string password)
        {
            // type text to fields
            SubmitButton.Click();
            return this;
        }

А зачем везде передавать ApplicationManager, если он реализует Singleton?

Почему метод LoginPage.LoginAsUser возвращает страницу логина? Мы же попадаем на другую страницу по итогу. Ее и нужно возвращать.


  • 1

#9 Alex

Alex

    Постоянный участник

  • Members
  • PipPipPip
  • 237 сообщений
  • ФИО:Алексей

Отправлено 21 августа 2017 - 07:07

И сразу ещё один вопрос по тому, как преобразовывать некоторые методы, использующие локаторы By в методы, использующие сразу веб-элементы страниц.

Например, ранее в HelperBase.cs у меня был метод ожидания элемента:

        public IWebElement WaitForElement(By locator)
        {
            WebDriverWait waitForElement = new WebDriverWait(driver, TimeSpan.FromSeconds(30));
            return waitForElement.Until(ExpectedConditions.ElementIsVisible(locator));
        }

Как мне его переписать, чтобы на вход можно было подавать IWebElement?

Или, может, как-нибудь можно получить локатор, зная нужный нам IWebElement?

Не понял, а зачем? Если вопрос к использованию PageFactory, то... просто не пользуйтесь фабрикой там, где это неудобно. PageObject не обязывает использовать PageFactory


  • 1

#10 Ivis

Ivis

    Новый участник

  • Members
  • Pip
  • 53 сообщений
  • Город:Одесса


Отправлено 21 августа 2017 - 07:46

А зачем везде передавать ApplicationManager, если он реализует Singleton?

 

 

Эмм... не совсем понял.
Непосредственно ApplicationManager я передаю в PageManager, а для страниц я уже использую PageManager с драйвером от ApplicationManager. По крайней мере такая была задумка...

Можете привести конкретный пример кода, или что необходимо исправить, если где-то у меня есть "излишек"?
 


  • 0

#11 Ivis

Ivis

    Новый участник

  • Members
  • Pip
  • 53 сообщений
  • Город:Одесса


Отправлено 21 августа 2017 - 07:48

Не понял, а зачем? Если вопрос к использованию PageFactory, то... просто не пользуйтесь фабрикой там, где это неудобно. PageObject не обязывает использовать PageFactory

 

 

Честно говоря изначально нашёл пример с PageFactory и взял его за основу... И потом начал видеть, что местами он неудобен.
А разве можно где-то пользоваться фабрикой, а где-то нет (в рамках одного класса страницы) и избежать дублирования кода?


  • 0

#12 checo

checo

    Опытный участник

  • Members
  • PipPipPipPip
  • 400 сообщений
  • Город:Н.Новгород

Отправлено 21 августа 2017 - 11:50

 

Не понял, а зачем? Если вопрос к использованию PageFactory, то... просто не пользуйтесь фабрикой там, где это неудобно. PageObject не обязывает использовать PageFactory

 

 

Честно говоря изначально нашёл пример с PageFactory и взял его за основу... И потом начал видеть, что местами он неудобен.
А разве можно где-то пользоваться фабрикой, а где-то нет (в рамках одного класса страницы) и избежать дублирования кода?

 

Да, фабрика инициализирует те элементы, на которых проставлены атрибуты FindsBy.

Те элементы, для которых нужен отдельный Wait или специфическая процедура получения, добавляем не как поля, а как методы, возвращающие IWebElement.

Получить из IWebElement локатор, указанный для фабрики, не так просто. В принципе, это можно сделать, но тут как обычно: только если знаете, зачем это надо. Проще иметь в классе прозрачный метод, который ищет этот элемент.


  • 0

#13 Ivis

Ivis

    Новый участник

  • Members
  • Pip
  • 53 сообщений
  • Город:Одесса


Отправлено 21 августа 2017 - 12:29

 

 

Не понял, а зачем? Если вопрос к использованию PageFactory, то... просто не пользуйтесь фабрикой там, где это неудобно. PageObject не обязывает использовать PageFactory

 

 

Честно говоря изначально нашёл пример с PageFactory и взял его за основу... И потом начал видеть, что местами он неудобен.
А разве можно где-то пользоваться фабрикой, а где-то нет (в рамках одного класса страницы) и избежать дублирования кода?

 

Да, фабрика инициализирует те элементы, на которых проставлены атрибуты FindsBy.

Те элементы, для которых нужен отдельный Wait или специфическая процедура получения, добавляем не как поля, а как методы, возвращающие IWebElement.

Получить из IWebElement локатор, указанный для фабрики, не так просто. В принципе, это можно сделать, но тут как обычно: только если знаете, зачем это надо. Проще иметь в классе прозрачный метод, который ищет этот элемент.

 

 

Понял.

Но всё же, мне кажется, получится какое-то месиво - где-то используется фабрика, где-то нет.
Уже лучше пользоваться чем-то одним, на мой взгляд.
Плюс, насколько я понял, фабрика позволяют лучше бороться StaleElementReferenceException. Хотя лично в моём случае эта проблема остаётся...

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


  • 0

#14 Ivis

Ivis

    Новый участник

  • Members
  • Pip
  • 53 сообщений
  • Город:Одесса


Отправлено 21 августа 2017 - 14:01

 

А как это будет тогда выглядеть с точки зрения структуры тестового приложения? ApplicationManager, получается, будет управлять и хелперами, и объектами страниц?

 

Можно для страниц сделать отдельный менеджер, если так удобнее.

Главное, чтобы зависимость была однонаправленная:

- апп менеджер и хелперы могут использовать страницы, а страницы не обращаются к хелперам

- драйвер используется только в страницах

 

 

Кстати, а допустимо ли в тестах и хелперах работать с локаторами?
К примеру, я жду появляение определённого веб-элемента на странице, и чтобы его выявить, я
- передаю определённые данные в класс страницы
- получаю локатор от класса страницы
- полученный локатор использую в тесте для ожидания веб-элемента.

Если недопустимо, то каким образом мне работать с элементами, которых ещё нет на странице? Как их инициализировать?

Пробовал вот такую штуку в классе страницы, но в результате на поиске элемента получаю ошибку "System.NullReferenceException : Object reference not set to an instance of an object.".

        public IWebElement ContactInGrid(ContactData contact)
        {
            By locator = By.XPath("//span[text()=' " + contact.Email + " ' and @class='ng-binding']");
            return driver.FindElement(locator);
        }

  • 0

#15 checo

checo

    Опытный участник

  • Members
  • PipPipPipPip
  • 400 сообщений
  • Город:Н.Новгород

Отправлено 21 августа 2017 - 18:31

 

- получаю локатор от класса страницы

У каждого класса есть своя задача, в идеале - только одна.

Задача класса страницы - взаимодействовать со страницей, а не генерировать локаторы. Хелпер не должен знать, как он получает тот или иной элемент, он просто его получает и реализует часть бизнес-логики по работе с элементом. Если нужно вынести какое-то ожидание, завязанное на логике, то мы в хелпере можем обращаться к странице за элементом, посмотреть - получили или нет, и в зависимости от этого что-то делать, но не искать из хелпера, и тем более теста.

 

каким образом мне работать с элементами, которых ещё нет на странице? Как их инициализировать?

Искать в тот момент, когда они появятся. А если это вопрос про инициализацию из фабрики, так там все равно, что есть на странице, а чего нет. Инициализация не делает никакого поиска, и вообще не обращается к драйверу. Инициализация просто присваивает полям ссылки на объекты, реализующие IWebElement, которые делают новый поиск элемента при каждом обращении.


  • 0

#16 Alex

Alex

    Постоянный участник

  • Members
  • PipPipPip
  • 237 сообщений
  • ФИО:Алексей

Отправлено 22 августа 2017 - 06:32

 

А зачем везде передавать ApplicationManager, если он реализует Singleton?

 

 

Эмм... не совсем понял.
Непосредственно ApplicationManager я передаю в PageManager, а для страниц я уже использую PageManager с драйвером от ApplicationManager. По крайней мере такая была задумка...

Можете привести конкретный пример кода, или что необходимо исправить, если где-то у меня есть "излишек"?
 

 

Не передавайте ApplicationManager как параметр нигде. Вы всегда можете получить к нему доступ через ApplicationManager.GetInstance(). Потому нет смысла его везде передавать почем зря. Иначе у вас получается масло маслянное, PageManager имеет поле ApplicationManager, а ApplicationManager имеет поле PageManager. Зачем PageManager-у это поле.


  • 0

#17 Ivis

Ivis

    Новый участник

  • Members
  • Pip
  • 53 сообщений
  • Город:Одесса


Отправлено 22 августа 2017 - 08:02

каким образом мне работать с элементами, которых ещё нет на странице? Как их инициализировать?

Искать в тот момент, когда они появятся. А если это вопрос про инициализацию из фабрики, так там все равно, что есть на странице, а чего нет. Инициализация не делает никакого поиска, и вообще не обращается к драйверу. Инициализация просто присваивает полям ссылки на объекты, реализующие IWebElement, которые делают новый поиск элемента при каждом обращении.

 

 

В теории это понятно. Но на практике я не понимаю как это сделать - видимо, не хватает знаний.
 

В моём конкретном примере я работаю с элементом, который появляется на странице после определённых действий в тесте.
И только после этих действий я могу составить локатор, по которому нужно искать элемент на странице.
Сейчас код выглядит следующим образом.

 

Тест:

        [Test]
        public void People_ContactEditingManually()
        {
            // prepare
            string timeStamp = app.Contacts.GetTimestamp(DateTime.Now);


            ContactData newContact = new ContactData("email-update-" + timeStamp + "@qa.test", "FirstName-update-" + timeStamp) {
                LastName = "LastName-update-" + timeStamp,
                Phone = "+1 888 340 72 32",
                Title = "Title-update-" + timeStamp,
                Company = "Company-update-" + timeStamp
            };


            // action
            app.Contacts.UpdateContactManually(newContact);


            // verification 
            By locatorOfUpdatedContactInGrid = app.Pages.People.GetLocatorOfContactInGrid(newContact);


            app.Contacts.WaitForElement(locatorOfUpdatedContactInGrid);
            Assert.IsTrue(app.Contacts.IsElementPresent(locatorOfUpdatedContactInGrid));


        }
И метод в классе страницы:
        public By GetLocatorOfContactInGrid(ContactData contact)
        {
            return By.XPath("//span[text()=' " + contact.Email + " ' and @class='ng-binding']");
        }

Можете подсказать как мне конкретно переписать этот код теста и страницы, чтобы получать из класса страницы веб-элемент, а не локатор?
Как мне инициализировать такой веб-элемент при помощи PageFactory?
 


  • 0

#18 checo

checo

    Опытный участник

  • Members
  • PipPipPipPip
  • 400 сообщений
  • Город:Н.Новгород

Отправлено 22 августа 2017 - 09:11

Опять же, в идеале, тест "не знает", что такое локаторы или element present. Тест работает с контактом.
Например:
app.Contacts.IsContactPresent(newContact)

Фабрика такие вещи делать не может. Для этого нужно писать свою реализацию фабрики (есть ли такая - не знаю), которая будет создавать элементы с изменяемыми локаторами.
  • 0

#19 checo

checo

    Опытный участник

  • Members
  • PipPipPipPip
  • 400 сообщений
  • Город:Н.Новгород

Отправлено 22 августа 2017 - 09:16

Одно из типичных решений с фабрикой - задавать поле как List<IWebElement> и указать для них обобщенный локатор, а потом в коде перебрать эти элементы и найти нужный. Но естественно, это медленнее, чем искать по конкретному вычисленному локатору.
  • 0

#20 Ivis

Ivis

    Новый участник

  • Members
  • Pip
  • 53 сообщений
  • Город:Одесса


Отправлено 23 августа 2017 - 08:54

Опять же, в идеале, тест "не знает", что такое локаторы или element present. Тест работает с контактом.
Например:
app.Contacts.IsContactPresent(newContact)

Фабрика такие вещи делать не может. Для этого нужно писать свою реализацию фабрики (есть ли такая - не знаю), которая будет создавать элементы с изменяемыми локаторами.

 

Вот решение, которое я реализовал.

 

Метод получения элемента в классе страницы: 

        public IWebElement ContactInGrid(ContactData contact)
        {
            By locator = By.XPath("//span[text()=' " + contact.Email + " ' and @class='ng-binding']");
            return WaitForElement(locator);
        }

И класс AnyPage.cs, от которого наследуются все страницы:

    public class AnyPage
    {
        private PageManager pageManager;


        public AnyPage(PageManager pageManager)
        {
            this.pageManager = pageManager;
        }




        public bool IsElementPresentAndVisible(By locator)
        {
            var driver = ApplicationManager.GetInstance().Driver;


            try
            {
                return driver.FindElement(locator).Displayed;
            }
            catch (Exception ex)
            {
                if (ex is NoSuchElementException || ex is ElementNotVisibleException || ex is StaleElementReferenceException)
                {
                    return false;
                }
                else if (ex.InnerException != null && ex.InnerException is StaleElementReferenceException)
                {
                    return false;
                }


                throw ex;
            }
        }


        public IWebElement WaitForElement(By locator)
        {
            var driver = ApplicationManager.GetInstance().Driver;
            bool isFound = false;


            for (int i = 0; i < 30; i++)
            {
                if (IsElementPresentAndVisible(locator))
                {
                    isFound = true;
                    break;
                }
                else
                {
                    System.Threading.Thread.Sleep(1000);
                }
            }


            if (isFound)
            {
                return driver.FindElement(locator); ;
            }
            else
            {
                Assert.Fail("ERROR! It's impossible to detect web-element.");
                return null;
            }
        }
}

    

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


  • 0



Темы с аналогичным тегами C#, NUnit, WebDriver, Page Object

Количество пользователей, читающих эту тему: 0

0 пользователей, 0 гостей, 0 анонимных