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

Публикации Ivis

14 публикаций создано Ivis (учитываются публикации только с 17 апреля 2023)


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

Отправлено автор: Ivis 23 августа 2017 - 09:04 в Selenium - Functional Testing

 

 

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

 

 

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

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

 

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

 

 

PageManager использует драйвер от ApplicationManager (для инициализации фабрики).

 

И, например, в классе 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;
            }
        }


    }

Тут, чтобы всё заработало, мне пришлось прописывать несколько раз 

var driver = ApplicationManager.GetInstance().Driver;

Т.е. дублирование кода налицо.

Но если прописать 

protected IWebdriver driver = ApplicationManager.GetInstance().Driver;

в самом классе, то у меня открывается множество экземпляров браузера.

Как можно решить эту проблему без дублирования кода?




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

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

Опять же, в идеале, тест "не знает", что такое локаторы или 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;
            }
        }
}

    

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




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

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

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

Искать в тот момент, когда они появятся. А если это вопрос про инициализацию из фабрики, так там все равно, что есть на странице, а чего нет. Инициализация не делает никакого поиска, и вообще не обращается к драйверу. Инициализация просто присваивает полям ссылки на объекты, реализующие 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?
 




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

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

 

А как это будет тогда выглядеть с точки зрения структуры тестового приложения? 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);
        }



#161719 Борьба со "StaleElementReferenceException: Element is no longer at

Отправлено автор: Ivis 21 августа 2017 - 13:39 в Selenium - Functional Testing

В общем, разобрался, вроде.

 

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

        public bool IsElementPresentAndVisible(IWebElement webElement)
        {
            try
            {
                return webElement.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;
            }
        }

Может кому пригодится...




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

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

 

 

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

 

 

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

 

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

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

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

 

 

Понял.

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

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




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

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

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

 

 

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




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

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

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

 

 

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

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




#161701 Борьба со "StaleElementReferenceException: Element is no longer at

Отправлено автор: Ivis 21 августа 2017 - 07:33 в Selenium - Functional Testing

 

Здравствуйте.

У меня такая же проблема, и почему-то решение отлавливания этой ошибки не срабатывает.

 

Есть два метода: метод ожидания элемента и метод проверки видимости элемента с отлавливанием ошибок.

        public IWebElement WaitForElement(IWebElement webElement)
        {
            bool isFound = false;


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


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


        public bool IsElementPresentAndVisible(IWebElement webElement)
        {
            try
            {
                if (webElement.Displayed)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            catch (NoSuchElementException)
            {
                return false;
            }
            catch (ElementNotVisibleException)
            {
                return false;
            }
            catch (StaleElementReferenceException)
            {
                return false;
            }
        }

Т.е. я тут пытаюсь отлавливать ошибку "StaleElementReferenceException".
Но, тем не менее, мои тесты периодически падают в этом месте с сообщением
"System.Reflection.TargetInvocationException : Exception has been thrown by the target of an invocation.

  ----> OpenQA.Selenium.StaleElementReferenceException : The element reference is stale. Either the element is no longer attached to the DOM or the page has been refreshed."

Что не так у меня с кодом?

Вообще, конкретно по Select, похоже, что у вас при попытке выбрать что-либо в списке, этот список обновляется или вообще используется другой (на одном проекте встречал подобное). Вам нужно детальнее посмотреть, что у вас происходит в DOM при работе с этим select. И решить каких именно событий ожидать, чтобы гарантированно дождаться обновления списка. Это может быть также наличие тех или иных опций в списке.

 

 

Эмм... у меня тут вопрос не связан с select.
В моих тестах во многих местах используюется ожидание элемента (код выше), и на разных элементах тест падает с "StaleElementReferenceException".
Например, после логина я жду появление навигационного меню... и в этом ожидании тест часто падает.




#161686 Борьба со "StaleElementReferenceException: Element is no longer at

Отправлено автор: Ivis 19 августа 2017 - 14:19 в Selenium - Functional Testing

Здравствуйте.

У меня такая же проблема, и почему-то решение отлавливания этой ошибки не срабатывает.

 

Есть два метода: метод ожидания элемента и метод проверки видимости элемента с отлавливанием ошибок.

        public IWebElement WaitForElement(IWebElement webElement)
        {
            bool isFound = false;


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


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


        public bool IsElementPresentAndVisible(IWebElement webElement)
        {
            try
            {
                if (webElement.Displayed)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            catch (NoSuchElementException)
            {
                return false;
            }
            catch (ElementNotVisibleException)
            {
                return false;
            }
            catch (StaleElementReferenceException)
            {
                return false;
            }
        }

Т.е. я тут пытаюсь отлавливать ошибку "StaleElementReferenceException".
Но, тем не менее, мои тесты периодически падают в этом месте с сообщением
"System.Reflection.TargetInvocationException : Exception has been thrown by the target of an invocation.

  ----> OpenQA.Selenium.StaleElementReferenceException : The element reference is stale. Either the element is no longer attached to the DOM or the page has been refreshed."

Что не так у меня с кодом?




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

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

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

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

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

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

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




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

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

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

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

 

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

 

2017-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;
        }



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

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

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




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

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

Друзья.

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

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

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

 

2017-08-16_163727.png

 

2017-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' и на его основании строить и наращивать свой проект.