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

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

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

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

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

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

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

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

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

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

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

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

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

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

1 лайк
  1. Так то ваши обьекты не хранят никакого состояния и они просто не могут прийти в негодность.
  2. Запуская тесты не через паралелинг на CI вы создаете всего 1 джава машину что в случае лага валит за собой все оставшиеся тесты.
  3. Статический драйвер может быть в множественном числе - 1 экземпляр на 1 поток.

А вот с возравщаемым типо я не понял

public AbstractPage login(String log, String pass){
//do login
if(url.equlas(homepage.url){
return new HomePage()
}{
else return new  LoginPage();
} 

Тогда после возвращения мы можем использовать только методы абстрактной страницы

Так а смысл возвращать новый обьект?

не факт что это хорошо в сложных кейсах
если собираете данные - удобно собрать всё и проверить всё
и выдать общий ассерт
AssertJ пример

    private SoftAssertions softly = new SoftAssertions();
    String url = getUrlFromRedirect(redirectUrl);
    softly.assertThat(url)
            .contains(redirectUrl);
    Map<String, String> result = queryParameters(url);
    log.info(result.toString());
    softly.assertThat(result)
            .containsEntry("ss_id",
                    "esd4?referrerPage=/content/st_com_cx/en" + widgetConfig.getRootPath());
    softly.assertAll();

я о том что я проверяю что ошибка появилась, помежуточные состояния можно и в обьект сохранять и по reflectionEquals проверять.

1 лайк

Что-то я запутался.
Как в таком случае выглядит у Вас строка декларации драйвера.
Давайте разберёмся с терминологией.

public static WebDriver driver;

Статический в моём понимании - это один экземпляр драйвера на всю Джава Машину, к которому я могу обратиться без создания экземпляра класса, в котором хранится это поле.

Никто не запрещает делать cast (приведение типов) после проверки, что это страница нужного типа, и использовать методы нужной страницы. У меня например два метода логина: первый - tryToLogin, который я использую для тестов Логин страницы и который мне возвращает абстрактную страницу, а я дальше проверяю тип, привожу к нужному (если нужно) и делаю проверки. После приведения типов дальше чем проверить на Логин странице сообщение или на Главной странице наличие какого-то элемента не заходит. Второй - login, который я использую в тех случаях, когда мне не нужно тестировать именно логин, а просто для входа в приложение и дальнейшей работы с ним. Этот метод всегда возвращает Главную страницу, а если нет - значит, что-то неправильно написано в тесте либо в данных для логина. Это подход, который я выбрал для себя. Никто не запрещает сделать так, чтобы логин ничего не возвращал и дальше разбираться в тесте.

Мне показалось, что вероятность падения Джава Машины при выполнении моих тестов очень мала. По крайней мере за 5 лет запусков тестов я такого не встречал (быть может я счастливчик). А вот затраты на поддержку параллельности в CI очень велики. Вообщем это вопрос вкусовщины. Для меня запускать тесты в 20 потоков на CI в нескольких дев ветках выглядит страшно.

Не понял. Можете пояснить/перефразировать.

Советую еще раз почитать про локальность статических перменных на отдельный поток.

  1. Кастовать типы к сабклассу это уже признак плохого кода.

Когда будете запускать 80 потоков тогда узнаете какая вероятность. И при правильной настройке кубика с докерами это делается 1 раз и поддержа больше не нужна.

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

2 лайка

Вообщето я вообще не использую эту методику - возвращение обьекта. У меня все методы void. Перед каждым тестов в Step классах я инициирую нужные мне PageObjects. Если что, я использую BDD.

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

Посоветуйте источник, а то мне внятного поиск не выдаёт

Вот тут - https://articles.javatalks.ru/articles/17

1 лайк

Хорошая статья, раньше не задумывался

может есть пример для тестов
на каком уровне добалять в сред локал (класс, метод, действие, поле)
чтобы было не слишком много слов
или может в виде фикстуры применять…

смотрю в основном к драйверу применяют только
https://www.swtestacademy.com/selenium-parallel-tests-grid-testng/

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

А так Вы про ThreadLocal. Я думал есть какие-то нюансы именно с таким объявлением драйвера.
public static WebDriver driver
Что он как-то может быть разным для разных потоком.

Тут я сам себя запутал. Вы всё правильно сказали.
public static ThreadLocal<WebDriver> driver
В таком случае поле драйвер действительно статическое и содержит свой экземпляр драйвера для каждого потока. Экземпляров драйвера по-факту много, но все они статические и хранятся в одной переменной. Это почти как
public static Map<ThreadID, WebDriver> drivers

Спасибо, что поправили!

Да смысл тот же, на сколько я знаю селенидовский драйвер тоже сделан по такому принципу

1 лайк

Вот перечитал переписку и не могу понять где объявлять и инициализировать классы пейджов, переменные, чтоб статиками не были. Одна пейджа может использовать данные из другой и наоборот. То где из инициализировть? где прописывать методы, которые инициализируют пейджи, тесты ж зависимыми становятся! Полная неразбериха!!!

А причем тут зависимые тесты? Если на условной Page1 у вас есть метод getPage2, который вернет вам new Page2, то в чем зависимость тестов между собой? Если Page2, что-то очень нужно, то можно через контекст какой-то это пробросить или через конструктор передать.