t.me/atinfo_chat Telegram группа по автоматизации тестирования

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

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

Допустим есть веб форма на ней грид, в котором отображается список кредитов. по двойному клику на избранную запись открывается попап форма для эдита. Там 20 полей. Они уже могут как содержать значения, так и быть пустыми. Как, на ваш взгляд, наиболее оптимально решить данную задучу?

Само собой, разные поля будут проставлятся в разное время из разных тестов.

1 Симпатия

Форма – это ПейджОбжект класс.

У каждого такого класса есть два метода:

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”

PaymentPage page = PaymentFormData.GetDefault()
page.FillForm(form);
page.SaveChanges();
PaymentFormData actualForm = page.ReadForm();
Assert actualForm == ”New Payment 100500”
3 Симпатий

Насколько я понимаю, 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);

А почему на ПсевдоПитоне, а не на ПсевдоДжаве? :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 пробовать.

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

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