А как вы проставляете значения полей на формах?

Так точно.

А еще каждое поле у меня может иметь значение null. В таком случае, FillForm просто не трогает поле на странице, которое советует полю в классе.
Поправлюсь. Допустил ошибку в коде выше.
Вот два примера: Один создает новый класс данных, и следовательно все поля будут null, а потом присваивает значение только полю Name. Следовательно, FillForm заполнит только Name, а все остальные поля не тронет.

Во втором примере GetDefault() возвращает некоторые значения по умолчанию. В следующих двух строчках, я лишь меняю то, что мне важно для теста, а все остальное будет как есть.

/// 1
PaymentFormData formWithName = new PaymentFormData() // all fields are null by default
formWithName.Name = "Hello World";

PaymentPage page = new PaymentPage();
page.FillForm(formWithName);

/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/// 2
PaymentFormData formWithName = new PaymentFormData.GetDefault() // all (or some) fields will have user-predefined values

// Change only the values which are important
formWithName.Name  = "Hello World";
formWithName.Bablo = 50;

PaymentPage page = new PaymentPage();
page.FillForm(formWithName);

А почему на ПсевдоПитоне, а не на ПсевдоДжаве? :smiley:

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

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

А по поводу джавы, то подход такой же (вкратце):

  1. Есть класс отвечающий за представление формы
  2. В ПейджОбъекте есть объявленный экземпляр класса формы
  3. При вызове метода заполненния формы передается объект данных с описанными полями в виде аттрибутов класса
  4. Внутри метода заполнения формы делается проход по установленным значениям и там где есть значения, выполняется заполнение поля на форме

Это прекрасно, только возникают у меня подозрения, что в .fillForm() поля дата модели жестко привязаны к вебэлементам. И если приходит одно поле, а остальные 19 null, то мы получаем 19 лишних проверок на Null. Поправь меня, если ошибаюсь.

Везде есть свои исключения. Если есть такая форма, где один элемент, зависит от другого, то я думаю, что можно разбить эту форму на группы, типа маленьких формочек, которые составляют одну.
Да, вместо 1-го будет два или 3 вызова, тут нужно смотреть на конкретную ситуацию.
У меня был такой кейс: есть форма создания пользователя.
У пользователя около 30 полей, большая часть из которых не обязательная. И можно сделать привязку между пользователей системы и Active Directory.
В случае создания обычного пользователя, нужно заполнять поля Password/Confirm
В случае Active Directory пользователя – нужно ввести логин, поставить галочку на Active Directory, дождаться, пока система подгрузит данные из AD, и продолжить заполнение.
В этом случае, FillForm просто реагировал на установку IsActiveDirectoryUser – и вел себя по-разному в обоих случаях.

Да, типа:

if (data.Name != null) page.txtName.SendKeys(data.Name);

Но, это совсем не лишние проверки. Для меня, они обеспечивают универсальность алгоритма.

  • Можно вынести все в отдельный метод, и писать, например так:

MySendKeys(page.txtName, data.Name)

А внутри MySendKeys – уже проверять, если data.Name == null – то ничего не делать

Я собственно сздал данный топик, из желания оптимизировать данную операцию.
Из идей у меня есть 2 варианта. плохой и чуть лучше :slight_smile:

Плохой, это использовать рефлекшены. Второй - на хеш мапах. На текущий момент он кажется чуть более практичным, но коммуна в праве все поставить на свои места :slight_smile:

Итак, в качестве датаМодели используется мапа. Допустим нужно засетить одно поле amount.

@When("bla-bla set amount ="100500")
public void setCashContractAmount(String amount){
    //создаем объект DTO
    HashMap<String,String> dtoMap = new HashMap<String,String>();
    //ложим туда связку имя необходимого поля, и значение.
    //Для имен полей, я создал enum ContractFields внутри page, содержащий имена. Просто чтобы избежать риска ошибки при вводе.
    dtoMap.put(CashContractsPage.ContractFields.AMOUNT.get(), amount);
    //отдаем пейдже мапу
    cashContract.fillForm(dtoMap);
}

// в самом pageObject нам нужно в конструкторе стрраницы создать еще одну мапу, в которую мы кладем связку имени и WebElement.
public CashContractsPage(WebDriver driver) {
    super(driver);
        webElementsMap.put(ContractFields.AMOUNT.get(),amount);
    }
}

// ну и дальше просто мапим мапу на мапу =))
public void fillForm(HashMap<String,String> dtoMap) {
    for(String elementName:dtoMap.keySet()){
        webElementsMap.get(elementName).sendKeys(dtoMap.get(elementName));
    }
}

[quote=“Zvonov, post:14, topic:3478”]
Второй - на хеш мапах.
[/quote]Где то я уже видел попытку подобного подхода, то ли тут, то ли на http://software-testing.ru

Проблема будет в том, что форма может состоять не только из одних инпутов, это могут быть и чекбоксы/ddl/радиобаттоны/etc или сложные контролы типа datepicker - тут одним sendKeys не отделаешься. Плюс в процессе заполнения формы часто приходится вставлять вейты, ждать стейлов, кликать на инпуты и творить прочее непотребство.

Есть еще шанс побороться. Мы создаем типизацию вебэлементов (Input, checkbox, etc) наследуем их от WebElement. Который расширяем методом setData(). А его, в свою очередь реализуем по своему для каждого типа.

Не особо большие изменения, вцелом

Это будет работать, только если условия будут практически идеальные. На деле всегда находятся контролы, поведение которых нестандартно, тогда идут в ход всяческие хаки и трюки. А поведение форм довольно запутано и не прямолинейно.

Вот пример “покладистой” формы - тут вопросов нет.

	textField("Last Name").type(lastName);
	textField("Phone").type(phone);
	textField("Email").type(email);
	textField("Username").type(userName);
	textField("Password").type(password);
	textField("Password again").type(password);
	dropDownList("User Type").select(userType);
	dropDownList("Reporting Level").select(reportingLevel);
	checkbox("I agree").check();

А вот “вредной”. Форма состоит из друх фреймов (благо что автосвитч реализован), которые взаимодействуют друг с другом в процессе заполнения. И не дай бог убрать какой-нибудь, с виду не нужный вейт или клик, либо изменить порядок действий, даже в тех местах, где необходимость последовательности не очевидна (серия кликов)…

ReservationForm reservation = callCenter.reservationForm;
ResultsPane results = callCenter.resultsPane;
EventDetailsSection eventDetails = reservation.eventDetailsSection;
GuestInfoSection guestInfo = reservation.guestInfoSection();

reservation.receivedFrom.type(HOTEL_USER_NAME);
reservation.performSearchForEvent(EVENT_NAME);

results.link(EVENT_NAME).waitForElement().click();

eventDetails.type.click();

results.link(ATTENDEE_TYPE).waitForElement().click();

eventDetails.arrival.click();
eventDetails.departure.click();
eventDetails.hotel.click();

results.link(HOTEL_NAME).waitForElement().click();

guestInfo.firstName.type(GUEST_FIRST_NAME);
guestInfo.lastName.type(GUEST_LAST_NAME);
guestInfo.email.type(GUEST_EMAIL);
guestInfo.arrival.click();
guestInfo.departure.click();
guestInfo.country.type(GUEST_COUNTRY);
guestInfo.zip.type(GUEST_ZIP_CODE);
guestInfo.street.type(GUEST_STREET);
guestInfo.phone.type(GUEST_PHONE);

reservation.howBooked.type(HOW_BOOKED_VALUE);
reservation.actionsBar.button("Submit").scrollIntoView();
reservation.actionsBar.button("Submit").click();

А если бы тут были и “капризные” контролы, для которых иногда приходится городить вот такие огороды?

case IE:
        moreInfoMenu.link(sectionName).executeScriptByAttribute("onclick");
        break;
default:
        moreInfoMenu.link(sectionName).click();
}
3 лайка

можно вопросик касательно фразы “благо что автосвитч реализован”.

Каким образом отслеживается в каком фрейме искать элемент? Можете в двух словах принцип реализации описать?

У меня в одном из веб-проектов активно используются iFrame и всплывающие/исчезающие окна. Пока переключение между ними сделано топорно, но хочется сделать прозрачно для кодера

@vmaximv и @joemast отвечать лучше через создание новой темы от комментария (справа от комментария появляется ссылка “Ответить в новой теме”), так как это уже совсем другое обсуждение

Вот в этой статье была идея использовать аспектно-ориентированное программирование (AOP)

Про Selenium и один «велосипед»

Читать можно вот отсюда:

А часть из них лежит еще и на фрэймах! Я экспериментировал на этом и этом, и сразу же столкнулся с описанной проблемой. А ситуацию усугубляет еще и то, что экземпляр, реализующий WedDriver, видит только активную страницу (окно/вкладку) или фрэйм.

Хотелось бы иметь возможность разбивать страницу на такие блоки, которые работающий тест мог бы запоминать и работать с ними напрямую, без вызовов driver.switchTo().window(somehandle) или driver.switchTo().frame(frameIdentity).

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

У меня есть пост на тему использования AOP в C#

2 лайка

да, я тоже думал про AOP, но у меня к нему предвзятое отношение, основанное на прочтении всяких нелестных отзывов :slight_smile: Лестные тоже были, но “осадочек остался”.

Поэтому я первым делом рассматриваю альтернативные варианты. А именно, применение паттерна Proxy и ему подобных. То есть суть в том же, что и в AOP: при обращении к элементу, обращение происходит к Proxy-объекту, который делает нужные действия до передачи управления собственно WebElement-у. Если с ними не получится, тогда буду AOP пробовать.

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

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

А что происходит при фэйле? Мне надо протестировать админку с кучей форм в которых есть много однотипных полей и если фэйл происходит в каком-то поле, то не факт, что его будут чинить. Что же получается, нужно переписывать тест?

Или например такая ситуация. бэкенд косячит и иногда дату не сохраняет на форме. И повторяться это может на любой из форм. Но вот воспроизвести это пока не удается. То есть все тесты могут быть как фэйл так и пасс. Что в этом случае делать?

Зачем тогда это тестировать?

Выписывать баг и пугать менеджера “красными” тестами.
Тесты, которые “обходят” баги - это взаимоисключающие параграфы и путь в никуда.

@vmaximv Я имел ввиду пока что не будут чинить. Что делать в таком случае? Баг отложен на 2 месяца, но тестировать нужно продолжать дальше тестировать форму. Переписывать тесты тогда?

И опять же могут сказать, у нас есть сейчас дела по важнее чем баг повторяющийся через раз. Тогда что делать?

Разбить тесты разных форм на независимые. Если для заполнения формы #2 нужно заполнить форму #1,
то поступаем так:

  • создаем тест(ы) на заполенение формы #1. Возможно, с применением softAssert-ов.
  • создаем метод, который заполняет форму #1 БЕЗ ее тестирования. Его цель - заполнить форму во что бы то ни было. Если в процессе ее заполнения возникают ошибки, в этот метод можно запихнуть костыли по их устранению с варнинг-месседжами в логи: повторное заполнение, заполнение через джава-скрипт и тп. Далее использовать этот метод как пререквизит к тестированию формы #2 (и других зависимых форм). После устанения багов просто убираем эти костыли.

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

Выяснить как часто возникает такая ситуация. Исходя из этой цифри определить число запусков N, при котором ошибка точно воспроизведется. Далее создать отдельный тест на заполнение такого поля, и запускать данный тест с параметрами invocationCount = N и successPercentage = 100 (если у Вас testNG, для других раннеров ищите похожие техники).
Для предотвращения падения других тестов, пойти по способу описанному выше - создать отдельный метод по заполнению поля с датой, который будет заполнять дату X раз, пока не получится.