Стало интересно, какие подходы в своих фреймверках используют читатели ресурса.
Допустим есть веб форма на ней грид, в котором отображается список кредитов. по двойному клику на избранную запись открывается попап форма для эдита. Там 20 полей. Они уже могут как содержать значения, так и быть пустыми. Как, на ваш взгляд, наиболее оптимально решить данную задучу?
Само собой, разные поля будут проставлятся в разное время из разных тестов.
void FillForm(PaymentFormData data)
и
PaymentFormData ReadForm()
Эти два метода работают со специальным классом PaymentFormData, который в народе называют паттерном DTO.
PaymentFormData содержит все данные поля, например, для текстового поля Name – будет:
String Name
А для чекбокса PaymentProcessed:
Boolean IsPaymentProcessed
И еще, в PaymentFormData есть метод
PaymentFormData GetDefault(),
который возвращает некоторые дефолтные значения для формы.
FillForm реагирует на значение null в полях PaymentFormData. Если встречает такое, то просто ничего не делает с соответствующим полем.
Псевдокод теста, который я набираю в Ворде, можнет выглядеть так:
PaymentFormData form = PaymentFormData.GetDefault()
form.Name =”New Payment 100500”
Насколько я понимаю, PaymentFormData - это модель данных на форме. другими словами PaymentFormDataEntity. Оно. содержит в себе все поля, имеющиеся на форме. так?
В принципе у меня похожая реализация на python. В пейджобъекте есть описанная форма, где задается как она работает, а вызываю форму через присвоение или считывание аттрибута. Вот сделал небольшой псевдо-код для демонстрации
class Form(object):
def __set__(self, obj, fields):
# go thru all values in dictionary
# and fill those are passed
for field in fields:
# find locator by field name
# and enter value there
def __get__(self, obj, cls=None):
# read page with form
# and return data as dictionary
# for fields that found
return dict_with_values
class PageObject(AbstractObject):
my_form = Form(locator_to_define_form_on_page)
class TestingSomething(unittest.TestCase)
def test_something_with_form_filling(self):
page = PageObject()
page.my_form = {"first name": value,
"last name": value,
"address": value}
print page.my_form
```
А еще каждое поле у меня может иметь значение 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);
Подход хороший, но… Как быть в тех случаях, когда необходимая последовательность заполнения формы сильно варьируется в зависимости от тест-кейса (автопопулейты, подгружаемые контролы и т.д.)?
Это прекрасно, только возникают у меня подозрения, что в .fillForm() поля дата модели жестко привязаны к вебэлементам. И если приходит одно поле, а остальные 19 null, то мы получаем 19 лишних проверок на Null. Поправь меня, если ошибаюсь.
Везде есть свои исключения. Если есть такая форма, где один элемент, зависит от другого, то я думаю, что можно разбить эту форму на группы, типа маленьких формочек, которые составляют одну.
Да, вместо 1-го будет два или 3 вызова, тут нужно смотреть на конкретную ситуацию.
У меня был такой кейс: есть форма создания пользователя.
У пользователя около 30 полей, большая часть из которых не обязательная. И можно сделать привязку между пользователей системы и Active Directory.
В случае создания обычного пользователя, нужно заполнять поля Password/Confirm
В случае Active Directory пользователя – нужно ввести логин, поставить галочку на Active Directory, дождаться, пока система подгрузит данные из AD, и продолжить заполнение.
В этом случае, FillForm просто реагировал на установку IsActiveDirectoryUser – и вел себя по-разному в обоих случаях.
Я собственно сздал данный топик, из желания оптимизировать данную операцию.
Из идей у меня есть 2 варианта. плохой и чуть лучше
Плохой, это использовать рефлекшены. Второй - на хеш мапах. На текущий момент он кажется чуть более практичным, но коммуна в праве все поставить на свои места
Итак, в качестве датаМодели используется мапа. Допустим нужно засетить одно поле 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(). А его, в свою очередь реализуем по своему для каждого типа.
Это будет работать, только если условия будут практически идеальные. На деле всегда находятся контролы, поведение которых нестандартно, тогда идут в ход всяческие хаки и трюки. А поведение форм довольно запутано и не прямолинейно.
Вот пример “покладистой” формы - тут вопросов нет.
А вот “вредной”. Форма состоит из друх фреймов (благо что автосвитч реализован), которые взаимодействуют друг с другом в процессе заполнения. И не дай бог убрать какой-нибудь, с виду не нужный вейт или клик, либо изменить порядок действий, даже в тех местах, где необходимость последовательности не очевидна (серия кликов)…
можно вопросик касательно фразы “благо что автосвитч реализован”.
Каким образом отслеживается в каком фрейме искать элемент? Можете в двух словах принцип реализации описать?
У меня в одном из веб-проектов активно используются iFrame и всплывающие/исчезающие окна. Пока переключение между ними сделано топорно, но хочется сделать прозрачно для кодера
@vmaximv и @joemast отвечать лучше через создание новой темы от комментария (справа от комментария появляется ссылка “Ответить в новой теме”), так как это уже совсем другое обсуждение
А часть из них лежит еще и на фрэймах! Я экспериментировал на этом и этом, и сразу же столкнулся с описанной проблемой. А ситуацию усугубляет еще и то, что экземпляр, реализующий WedDriver, видит только активную страницу (окно/вкладку) или фрэйм.
Хотелось бы иметь возможность разбивать страницу на такие блоки, которые работающий тест мог бы запоминать и работать с ними напрямую, без вызовов driver.switchTo().window(somehandle) или driver.switchTo().frame(frameIdentity).
Я тоже как-то пытался прикрутить такую штуку у себя для .NET, выбрал правильные инструменты, но пошел не совсем по правильному пути, так что не довел дело до конца. А сейчас понял, где оступился.
да, я тоже думал про AOP, но у меня к нему предвзятое отношение, основанное на прочтении всяких нелестных отзывов Лестные тоже были, но “осадочек остался”.
Поэтому я первым делом рассматриваю альтернативные варианты. А именно, применение паттерна Proxy и ему подобных. То есть суть в том же, что и в AOP: при обращении к элементу, обращение происходит к Proxy-объекту, который делает нужные действия до передачи управления собственно WebElement-у. Если с ними не получится, тогда буду AOP пробовать.
Пока активно этот вопрос не исследую, т.к. с серьезными проблемами топорного решения ещё не столкнулся в виду начального этапа развития автоматизации тестирования, на котором сейчас находятся мои проекты
Давайте действительно попросим модератора перенести сообщения не по теме топика в отдельную тему. Может я чего то не понимаю, но у меня это реализовано без всяческих прокси, рефлекшенов и т.д.