Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- http://joxi.ru/Q2KpJYOs49yXvA
- /*
- Не держи в одном пекедже тестовые данные и предка тест-класса
- Это очень разные вещи
- И такое лучше раскладывать отдельно друг от друга
- повторю про структуру
- http://joxi.ru/nAyqEx7HXvxQoA
- http://prnt.sc/bvuytd
- вот пример хорошей структуры проекта
- в src \ main
- core - универсальное, что можно переиспользовать в разных проектах
- pages - пейджи тоже можно переиспользовать для других тестов этого же приложения
- в src \ test
- testdata - тестовые данные (если такие есть и они вынесены в отдельный класс)
- testconfigs - предки тест-класса (так можно их изолировать от собственно тест-классов - чтоб легче было ориентироваться
- про пекеджи еще немного)
- если GroupID = com.somesite
- а проект todomvctest
- то пакет корневой должен быть com.somesite.todomvctest
- логика - чтобы "не смешивались имена сущностей"
- внутри одной компании - может быть несколько проектов)
- и у всех у них один com.somesite - базовый пекедж
- но для каждого проекта должен быть свой “базовый пекедж проекта"
- иначе все смешается)
- важно то, что когда этот проект выльется в отдельную библиотеку,
- то не будет конфликтов при его подключении
- */
- *******************************************
- public class TestData {
- //testData
- public static String email = ("...");
- public static String password = ("...");
- }
- /*
- комментарий //testData - не помогает
- скобки в выражениях - email = ("...") - лишние
- можно вот так - email = "..."
- */
- *****************************************
- public class BaseTest {
- public static WebDriver driver = new FirefoxDriver();
- @AfterClass
- public static void closeBrowser() {
- driver.quit();
- }
- }
- /*
- Только это и должно остаться в предке тест-класса
- Остальное (assertThat и open) = вспомогательные методы
- да, для них нужен вебдрайвер
- и согласна - очень соблазнительно такие методы расположить тут
- чтоб вебдрайвером можно было пользоваться
- но это неверно с точки зрения Single Responsibility principle
- в предке тест-класса - должна жить лишь какая-то логика именно тест-класса в целом
- а никак не надор инструментов
- пока - вынеси эти методы в отдельный класс (пусть будет ConciseAPI)
- как статические
- и передавай в них вебдрайвер - как параметр метода
- да, получится громоздко
- но мы это позже подправим
- пока пусть так будет
- и еще - обращай внимание на форматирование кода
- заведи привычку - отдавать реформатированный код
- это мелочь
- но это важная мелочь - типа вежливости/уважения к тому, кто читает код)
- в случае - если читает код твой лид или заказчик - это будет действительно важно)
- */
- **********************
- public static void assertThat(ExpectedCondition<Boolean> condition){
- public static void assertElements(ExpectedCondition<List<WebElement>> condition){
- /*
- Ты верно подметил - что кондишены бывают разные
- ExpectedCondition<Boolean> и ExpectedCondition<List<WebElement>>
- есть и другие варианты)
- Можно написать код лаконичнее - реализовать лишь один метод
- самый простой способ - не уточнять ничего в <...>
- Но нам на самом деле - в итоге будет важно уточнить...
- Про это - напишу в конце ревью
- Пока - можешь упростить до варианта ExpectedCondition condition
- сначала - достичь минимального результата, а потом уже усложнять код и разбираться с новым
- также - далеко не всегда нам нужен таймаут в 30 секунд
- если
- реализовать assertThat с еще одним параметром - таймаутом
- то
- можно будет при вызове проверки - явно указывать размер таймаута
- это бывает удобно - когда в проекте есть проверки, требующие какого-то не стандартного таймаута
- в то же время
- для всех проверок указывать аймаут - уже перебор)
- потому - реализуем второй assertThat - уже без параметра - таймута
- в нем - вызовем первый assertThat и в качестве таймаута укажем значение Configuration.timeout
- Тут же, в core, реализуем класс Configuration со статическим полем timeout,
- в котором будет задан таймаут по умолчанию (разумно его установить равным 4 секунды)
- А если для тестов значения по умолчанию Configuration.timeout недостаточно
- то всегда есть возможность в тест-классе настроить самостоятельно Configuration.timeout
- Собственно - мы аналогично поступали - когда реализовывали версию этого задания на Selenide
- мы для этого теста изменяли значение Configuration.timeout
- */
- *********************************************
- public class Gmail{
- @FindBy (css = "#Email")
- public static WebElement emailField;
- public Gmail(WebDriver driver){
- PageFactory.initElements(driver, this);
- }
- /*
- про форматирование кода уже писать не буду - его везде подправь
- то что эту версию решил сделать с прокси-элементами - это норм
- на самом деле, последний шанс поработать с @FindBy-элементами на курсе)
- потому - стоит попробовать и разобраться
- мало ли куда ты придешь работать и что там будет использоваться)
- код инициализации пейджа - будет одинаков для всех пейджей
- реализуй предка для пейджей
- и перенеси этот код внутрь предка
- цель - разгрузить сами пейджи от этого = быть DRY
- еще - ты используешь вебдрайвер - просто его импортируя из предка тест-класса
- не лучшая стратегия)
- пейджи ничего не должны знать про то, что есть у тест-класса или его предка
- не тест-класс для пейджа, а пейдж - для тест-класса
- не может вспомогательное просить помощи у главного
- это здорово усложнит логику
- избегай таких решений
- не зря - см пример хорошей структуры - пейджи и тест-классы, пейджи и тестовые данные - находятся в разных ветках проекта
- ты бы просто не смог так код написать - при такой структуре
- тебе же в конструктор пейджа передали драйвер
- вот и сохрани его в переменной пейджа и переиспользуй
- (это тоже уйдет в предка пейджа)
- */
- **************************
- public void visit(){
- driver.get("https://gmail.com");
- }
- /*
- используй метод open
- */
- ************************************
- public void login(String email, String password){
- emailField.sendKeys(email);
- nextButton.click();
- assertThat(invisibilityOfElementLocated(By.cssSelector("#Passwd")));
- passwordField.clear();
- passwordField.sendKeys(password);
- signinButton.click();
- }
- /*
- ага, ты таки заметил - что ожиданий не хватает
- @FindBy-элементы, конечно, переискиваются
- но сами по себе - не обеспечивают нужных ожиданий - как элементы в Selenide
- в классе ConciseAPI - реализуй метод $
- который как параметр - получит элемент
- и в его реализации - сначала встрой ожидание видимости элемента
- а уже затем - возвращай элемент как результат
- как писала выше - передавай в такие методы вабдрайвер как параметр
- потом следаем красивее
- и далее - можно будет работать с элементом
- $(driver, passwordField).clear()
- так ты будешь гарантировать - что на момент вызова метода clear() - элемент уже видим и с ним можно работать
- чтоб понять как работают эти прокси @FindBy-элементы - почитай
- http://selenium2advanced.blogspot.com/2013/08/working-with-page-factory-and-webdriver.html
- https://github.com/SeleniumHQ/selenium/wiki/PageFactory
- */
- *****************************************************************************************************
- public class Mails extends BaseTest {
- @FindBy(css = "[role='main'] .zA:nth-child(1)")
- static WebElement mails;
- /*
- так тебе нужен - элемент = первый мейл в списке
- или все же список элементов
- ?
- насколько я понимаю - нам список нужен)
- вопрос - раз работаем с пейджом объектом
- то зачем объявлять что-либо в пейдже как static?
- это неверно
- пересмотри материалы по пейджам
- раз работаем с пейджом-объектом - так и дуступайся до всего - через пейдж-объект
- ну и по именам классов-пейджей - тоже повспоминай
- */
- ****************************************************************
- public static void assertMail(int index, String mailHeaderText){
- assertElements(numberOfElementsToBe(By.cssSelector("[role='main'] .zA"),index));
- assertThat(textToBePresentInElementLocated(By.cssSelector("[role='main'] .zA:nth-child(1)"),mailHeaderText));
- }
- /*
- тут уже или-или)
- если делаешь версию с @FindBy-элементами
- так используй кондишены, которые работают не с локаторами
- а именно с WebElement или с List<WebElement>
- ну и выше - я тебе писала - что лебе нужен список мейлов)
- вот - ты уже с ним как раз работаешь) - By.cssSelector("[role='main'] .zA")
- правда, не верно)
- если уже и использовать локатор - то правильнее использовать кондишен minimumSizeOf или его стандартный аналог
- https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/support/ui/ExpectedConditions.html
- но на самом деле - нам нужен один кондишен)
- который еще придется написать
- нам нужен кондишен
- который будет проверять
- для списка элементов
- по такому-то индексу
- текст элемента
- например - назови кондишен nthElementHasText
- Вот тут - очень важный момент
- его надо понять и запомнить обязательно
- если у кондишена первый параметр - WebElement или List<WebElement> -
- то кондишен ждет не просто элемент или список элементов
- кондишен ждет лейзи прокси элемент или список элементов (аннотированный @FindBy)
- потому что - такие элементы - могут переискиваться
- а если у кондишена первый параметр - By - локатор для элемента или списка элементов
- то уже внутри кондишена по локатору происходит получение элемента или списка элементов
- и мы в таком случае не нуждаемся в лейзи прокси элементах или списках элементов (аннотированных @FindBy)
- еще момент
- предположим, у тебя есть лейзи прокси List<WebElement> elements;
- вот такой элемент elements.get(...) - не будет лейзи прокси
- это - уже обычный вебэлемент
- его передавать в кондишен бесполезно
- Поскольку ты реализуешь версию с @FindBy-элементами - оперируй параметром типа List<WebElement>
- а не By
- Аналогично - и для проверки текстов мейлов
- типа того как у тебя для селениде-реализации было
- public static void assertMails(String... mailHeaderTexts){
- mails.shouldHave(texts(mailHeaderTexts));
- }
- Тут - тоже понадобится новый кондишен
- Мы определились, что нам нужны 2 новых кондишена,
- у которых первый параметр - это List<WebElement> или By = список элементов,
- первый кондишен - textsOf(... elements, String... texts)
- где параметры - список элементов (или его локатор) и текстЫ которые мы будем проверять для каждого из элементов
- второй кондишен - listNthElementHasText(.... elements, int index, String text)
- где параметры - список элементов(или его локатор), индекс проверяемого элемента
- и текст который мы для него проверим
- чтоб не придумывать велосипед, давай посмотрим на код 2-ух стандартных селениумских кондишенов,
- один из них работает со списком элементов и этим нам интересен
- второй - с текстом и этим нам интересен
- стандартные кондишены реализованы в классе ExpectedConditions
- напиши ExpectedConditions в своем коде
- зажми ctrl+кликни на ExpectedConditions
- откроется модуль (ты сам в IntelIJ Idea сможешь это увидеть)
- Попробуй по аналогии с приведенным примером организовать свои кондишены
- У тебя есть лекция Якова, которая рассказывает, как кондишены устроены
- У тебя есть пример кондишена с первым параметром такого же типа, как нам нужно
- У тебя есть пример кондишена, который проверяет текст
- Ты можешь открыть код селенидовского кондишена texts or exactTexts и тоже взять оттуда идеи
- Мы определились с тем, что наши новые кондишены должны делать, с их именами и парамерами
- Попробуй написать эти 2 кондишена.
- Будет сложно - обращайся, будем упрощать задачу)
- */
- ***************************************
- /*
- в целом - стратегия такая
- сначала реализуй версию - без проверок assertMails и assertMail
- затем - реализуй проверки (и соответственно - конидишены)
- потом - далее написанное читай - если все заработает
- не приступай к следующему шагу - если с предыдущим не разобрался
- */
- *************************это разбирай после того - как получишь рабочую версию************************
- /*
- Давай разберемся. Сначала "идейно".
- Этот метод until возвращает такую интересную динамическую штуку-аватар...
- "воплощение" которого зависит от того что именно ты передашь вейт антилу параметром
- - точнее - какой именно кондишен ты ему передаешь...
- А кондишены бывают "разные"... "Разность" эта определеятся "параметром типа" который в джава записывается
- ИмяТипа<ЗДЕСЬ>
- Ты уже ведь использовал списки, например...
- В частности - List<WebElement>
- вот в такой записи типа - его параметром, выступает WebElement, указывая джаве,
- что это не просто список, а "список ВебЕлементов"
- а мог бы быть и списком стрингов например - List<String> или еще чего :)
- Такие "умные" типы которые получают параметр (или несколько параметров) - так и называются - параметризированными
- (а фича языка, в которой они поддерживаются называется - параметрическим полиформизмом -
- не путать с тем другим полиморфмизом... который называется - subtyping polymorphism,
- а в джава все его знают как просто "полиморфизм")...
- Еще они называются - шаблоны... или дженерики...
- Именно так - Java Generics - их принято называть в Java
- Так вот... таким же дженериком является тип ExpectedCondition
- который может быть параметризирован...
- Ты уже встречал запись типа:
- ExpectedCondition<Boolean>
- что же значит этот булеан? что он параметризирует?
- А он параметризирует внутреннюю реализацию этого кондишена...
- Он определяет то, каким должен быть тип возвращаемого значения в методе apply
- То есть...
- Если ты поставишь цель создать кондишен ExpectedCondition<Boolean> - ты должен будешь реализовать
- его метод apply - как возвращающий тип Boolean
- Но зачем вообще нам париться о типе возвращаемой сущности метода apply?
- А потому, что есть еще один "умный дженерик"...
- Только который не "параметризированный класс", а "параметризированный метод"
- (в джава бывают как Generic Types так и Generic Methods)
- и этот параметризированный метод - как раз и есть наш wait.until
- особенность реализации этого метода в том... что он вызывает внутри метод apply
- нашего кондишена... и запоминает то значение, которое этот метод apply возвращает...
- И потом... в конце всей истории... этот метод until - либо бросит исключение
- (в случае если "не дождется" кондишена)
- либо вернет то, что вернул метод apply в "случае успеха"...
- Получается... Если ты передашь вейт антилу кондишен параметризированный типом Boolean
- то в случае успеха антил - вернет тру...
- то есть ты можешь писать код вида:
- if (wait.until(enabled(composeButton))) {
- doSomething();
- }
- но на самом деле, такое нужно не часто...
- то есть - такие кондишены - которые
- параметризированы типом булеан...
- бошьше толку как раз от кондишенов параметризированных типом "того, характеристики чего ждет кондишен"...
- вот кондишен visibilityOf - как раз параметризирован типом WebElement
- ExpectedCondition<WebElement>
- и его метод apply возвращает обьект типа WebElement,
- а "идейно" - возвращает этот же элемент, визибилити которого мы дожидались...
- (если бы мы не дождались - apply вернул бы null - что в этом случае играет роль "false")
- а until в конце концов вернет то, что вернет apply
- и именно поэтому мы можем писать такой код:
- wait.until(visibilityOf(composeButton)).click()
- теперь твоим заданием будет - переделать assertThat что бы он возвращал то что возвращает wait.until
- ну и потом - переделать все свои кондишены, что бы они были параметризированы типом той сущности,
- характеристики которой они должни дожидаться (кстати так выражатся немного некорректно - ведь кондишен не ждет,
- он только выдает информацию - да/нет - а ждет уже вейт антил, ну то такое:) )
- для того, чтобы "правильно реализовать этот wait.until" -
- тебе придется разобраться с этими дженериками...
- И с дженерик типами и дженерик методами.
- И даже этого будет мало...
- Прийдется покопаться в коде селениума, чтобы понять, что там за тип реально получает
- вейт антил как кондишен...
- Потому что он получает не ExpectedCondition а что то еще более странное...
- Это работает - потому что это "странное" - есть родительским классом для ExpectedCondition
- */
- /*
- Что почитать про Generics
- про дженерики в общем(русский)
- http://www.quizful.net/post/java-generics-tutorial
- http://www.tutorialspoint.com/java/java_generics.htm
- http://developer.alexanderklimov.ru/android/java/generic.php
- конвеншенcы http://stackoverflow.com/questions/2900881/generic-type-parameter-naming-convention-for-java-with-multiple-chars
- https://docs.oracle.com/javase/tutorial/java/generics/types.html
- уроки
- http://docs.oracle.com/javase/tutorial/extra/generics/index.html
- очень приличный faq (есть pdf, и есть кое-что еще, помимо дженериков)
- http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html
- */
Advertisement
Add Comment
Please, Sign In to add comment