Какая должна быть правильная структура тестов?

PageManagers - папка с пейджами менеджеров, это так для систематизации и удобства

Я наверное повторюсь

За что отвечают “пейджи менеджеров” или “менеджеры пейджей”?
“систематизации и удобства” чего?
Что в классе Tests и как выглядит код одного теста?

С Variables стало яснее. Это класс с тестовыми данными. Обычно такую информацию хранят в Properties файлах (Java Properties File: How to Read config.properties Values in Java? • Crunchify)
Строка Variavles = new Variavles(); в классе TestBase не нужна.

import org.openqa.selenium.By;
import org.testng.Assert;
import org.testng.annotations.Test;
import pages.DashBoardPage;
import pages.Variables;

public class Tests extends TestBase{
@Test
public void logIn(){
loginPage.logIn();
Assert.assertTrue(dashBoardPage.getUsersButton().isEnabled());
}

@Test (dependsOnMethods = {“logIn”})
public void createManager() throws InterruptedException {
createManagerPage.createManager();
Assert.assertEquals((driver.findElements(By.cssSelector(“div.manager-info__name > span + br + span”)).get(0).getText()), Variables.getEmailManager());
}
}

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

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

В классе Tests зависимости между тестами быть не должно (dependsOnMethods = {“logIn”}).
Вот это вынесите в класс вашей страницы в какой то метод который будет возвращать текст этого элемента и потом уже дергайте этот метод в тестовом классе.

(driver.findElements(By.cssSelector(“div.manager-info__name > span + br + span”)).get(0).getText())

а как я могу проводить тесты если не залогинюсь? Или мне перед каждим методом логиниться? или есть возможность запустить тест уже залогиненым?

логин в Before делайте

Итак по-порядку. Начнём с тестов.

  1. Идея наследовать все тестовые классы от базового тестового класса, в котором выполняются настройки - это самая стандартная практика, которая в 90% несёт пользу.

  2. В базовом тестовом классе не нужно инициализировать все страницы сразу. По-хорошему в тестовых классах вообще не должно быть инициализации страниц. Этим должны заниматься вспомогательные классы. В идеале инициализированные страницы должны возвращаться из методов. Например, loginPage.logIn() должен возвращать страницу dashBoardPage. Если метод loginPage.logIn() параметризировать и дать возможность передавать данные для логина из теста, то в случае если данные неверны - этот метод должен возвращать LoginPage.

  3. Также в базовом тестовом классе не нужны поля для хранения страниц. Тесты должны быть независимыми и атомарными/самодостаточными. Это означает, что страницы между тестами и тестовыми классами передаваться не должны. Т.е. одна и та же страница не должна использоваться в нескольких тестах.

  4. Насколько я понял в качестве тестового фреймворка Вы используете TestNG. Внимательно изучите, что означают его аннотации. В TestNG существует следующая базовая иерархия тестовых сущностей. Самая маленькая - тестовый method (метод тестового класса). Тестовые методы объединяются в тестовые классы. Далее следует Test, который может состоять из нескольких тестовых классов или тестовых методов разных тестовых классов. Test-ы собираются в Suite. Перепроверьте чего Вы хотели достигнуть используя @BeforeTest аннотацию. @BeforeTest будет выполнятся раз перед TestNG Test. Не перед каждым методом с аннотацией @Test.

  5. Детали инициализации драйвера лучше убрать из базового тестового класса в какой-нибудь DriverManager или DriverFactory. А в базовом тестовом классе обращаться для получения драйвера к DriverManager или DriverFactory. Также не используйте абсолютные пути для указания пути к файлу драйвера - пользуйтесь относительными путями к папке вашего рабочего проекта.

  6. Implicit wait 10 секунд - это многовато. Пользуйтесь explicit wait-ами где это необходимо.

  7. Что Вы проверяете в тесте logIn()? Насколько я понимаю, Вы хотите проверить, что после логина открывается страница dashBoardPage. Если метод loginPage.logIn() будет возвращать страницу dashBoardPage (как было сказано выше), то Вам нужно проверить, что в результате его выполнения вернётся экземпляр (instanseof) этой странице. Button-ами в тесте лучше не оперировать.

  8. Как уже было сказано в посте Какая должна быть правильная структура тестов? - #10 от пользователя ordeh зависимости между тестами - это крайняя мера и если есть возможность их не делать, то лучше не делать. Понятно, что для второго теста Вам нужно залогиниться, как и скорее всего для третьего понадобиться. Это то, что нужно делать в каждом тесте отдельно либо вынести в базовый тестовый класс с аннотацией @BeforeMethod

  9. Тест throws InterruptedException. Такого как по мне быть не должно. Такие эксепшены должны обрабатываться уровнем ниже.

  10. Как уже было сказано в том же посте от @ordeh все локаторы должны быть в классах страниц.

  11. Рекомендую сразу учиться пользоваться более продвинутыми Assert-ами Hamcrest Tutorial

Теперь к пейджам

  1. Для педжей часто тоже создают базовый класс, в котором собирают основные методы и от которого потом наследуют остальные пейджи. В Вашем случае в этот базовый класс уже можно засунуть конструктор с вызовом PageFactory и инициализацией драйвера. Туда же можно перенести поле драйвера и сделать его protected

  2. Драйвера, кстати, в коде тестов быть не должно. По-хорошему драйвер должен быть только в классах страниц.

  3. Старайтесь не использовать тестовые данные (Variables) в коде страниц. Делайте методы страниц параметризируемыми, а данные передавайте из тестов. Например, метод logIn() можно переделать на logIn(String username, string password)

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

  5. Внутри пейджовых методов также следует выполнять ожидание завершения действия/загрузки страницы. В Вашем случае после логина, если драйвер сам не дожидается загрузки страницы (такое бывает если нажатие кнопки не ведёт к переходу на другую страницу в браузере = смене URL), надо дождаться пока загрузится dashBoardPage

Общая рекомендация - избавляйтесь от static-ков по-максимуму.

5 лайков

Огромное спасибо!!!

1 лайк
  1. Как в джавке сделать что бы метод возвращал или логин пейдж или следующую пейджу?
  2. Чем плохо использовать 1 обьект в нескольких тестах которые допустим создаются в бефор сьют?

  1. Чем плох статический драйвер? Как не явно в тесте передать драйвер?
  2. ка ктогда быть если нам нужно остаться на той же странице после появления ошибки?
  3. Так всё же чем плох статик?

Если это вопросы, то попробую частично ответить.
2. Сделать метод не void а чтобы возвращал новый PageObject. Например, есть Login метод, который логинится. В методе сделать все действия, а в конце - return new MainPage(driver) - код может меняться, в зависимости от того, как устроен framework.

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

По пункту 2 добавлю еще пример на селениде

public LoginPageImpl openLoginPage(){
        btnOk.click();
        return page(LoginPageImpl.class);
    }

@Valentin_G, @ordeh Как вернуть объект я и так знаю, я имею в виду что если нам после нажатия на кнопку логин не всегда нужно переходить на новую страницу? Например после нажатия должно появится ерор что логин не правильный. Это возможно только явной передачей типа возвращаемого объекта при возвращаемом дженерик типе, ну или городить костыли с интерфейсами.

А что если я скажу что можно паралелить на уровне CI а не стандартным parallel=true, или использовать потокобезопасные контейнеры для хранения драйверов ну или самый банальный ThreadLocal ?

1 лайк

посмотрите примеры - выберите то что понравится
серебряной пули нет

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

Остерегайтесь оверинжиниренга
https://comaqa.by/2018/07/27/comaqa-spring-2018-ui-automation-antipatterns/

2 лайка

Для такого я пишу следующий шаг в тесте, который дожидается элемента, где находится данное сообщение об ошибке и валидирую его - если я в тесте проверяю конкретно это сообщение. Если какой-то эррор и тест не прошел дальше, хотя должен был - тест фейлится.

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

1 лайк

Сразу скажу, что я не даю Вам единственный правильный ответ. Это только мой опыт и мнение, которое Вы можете использовать или нет. Никто не указывает Вам делайте так и только так.

Зачастую и Логин страница и Первая(Главная) страница после логина наследуются от базового класса. В методе логин можно определять какая из них открылась и возвращать новый экземпляр Главной страницы (если логин успешен) или this/новый экземпляр страницы Логина (если логин не успешен). Да тип возвращаемого значения логин метода будет базовый класс, но в этом то и идея, чтобы потом сделать проверку в тесте. Как по мне использование Полиморфизма тут вполне уместно.
Можно завести два метода логина один будет возвращать Логин страницу, второй Главную страницу и использовать соответственно где Вы ожидаете успешный и неуспешный логин.
Подходов много, единственно-правильного нет, какой понравиться, такой и используйте.
К сожалению, про костыли с интерфейсами ничего сказать не могу, потому что пока не понимаю как это можно сделать с их помощью. Поделитесь, в целях самообучения.

Параллелить тесты на уровне CI (в моём случае TeamCity) для меня этап пройденный. С этого и начиналась параллелизация тестов у нас. В итоге оказалось неудобно, долго и накладно. Конечно может мы что-то делали неправильно, но мы от этого отказались. Из обнаруженных минусов:

  1. нужно больше билд агентов для параллельного запуска билд конфигураций.
  2. больше временных затрат на поддержание агентов в одинаковом и актуальном состоянии (например, если что-то нужно обновить на билд агенте, то вместо одного приходилось делать на 5-ти)
  3. больше временных затрат на поддержание билд конфигураций в одинаковом состоянии
  4. на каждом билд агенте делался чекаут довольно большого репозитария - а это время и место
  5. сложнее решать конфликтные ситуации, если все билд конфигурации ранаются на одном тестовом окружении. Иначе больше времени на поддержку большего числа тестовых окружений.
    На этом пожалуй остановлюсь, так как это не предмет обсуждения вопроса автора.

Уточните пожалуйста, что вы имеете ввиду под “потокобезопасные контейнеры”?

Также Вы сказали “для хранения драйверов”. Т.е. драйвер у Вас уже не один, а значит не статический.

1 лайк
  1. Если тесты запускаются параллельно, то с одним объектом могут быть проблемы
  2. Если не параллельно. Первый тест может привести объект в состояние непригодное для второго теста. Особенно если первый тест упал по какой-то причине. Конечно, в этом случае можно в AfterMethod приводить объект в исходное состояние, но по-моему легче создать новый.
  3. За состоянием общего для нескольких тестов объекта сложно следить. Например, я пишу новый тест, где хочу использовать этот объект. Мне нужно просмотреть все тесты, где он уже используется, чтобы понять в каком он состоянии придёт в мой тест. И если мой тест будет не последним, кто использует этот объект, то проверить что я его не сломаю для следующего теста
  4. Наличие общего объекта для тестов создаёт как по мне некую зависимость между тестами.
1 лайк

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

Можно проверять состояние, пересоздавать драйвер и присваивать его в тот же статический филд. Но в чём тогда смысл статического драйвера?!

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

Но помните главное. Не нужно сразу строить сложную структуру классов, менеджеров и тестов. Вы должны дойти до этого постепенно с пониманием того, зачем Вам это нужно и чем Вам это поможет.

Нет ничего плохого в статическом драйвере, когда Вы только начинаете писать тесты (сам с такого начинал) и когда Вы понимаете, что для Вас в данный момент это не проблема. Совет был дан с запасом на будущее.

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

1 лайк