Есть отличная удаленная работа для php+codeception+jenkins+allure+docker спецов. 100% remote! Присоединиться к проекту

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


(Zvonov) #1

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

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

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


Структура и организация тестов (Аннотация @Test)
(Дмитрий Жарий) #2

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

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

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”

(Zvonov) #4

Насколько я понимаю, PaymentFormData - это модель данных на форме. другими словами PaymentFormDataEntity. Оно. содержит в себе все поля, имеющиеся на форме. так?


(Mykhailo Poliarush) #5

В принципе у меня похожая реализация на 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

```

(Дмитрий Жарий) #7

Так точно.

А еще каждое поле у меня может иметь значение 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);

(Дмитрий Жарий) #8

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


(vmaximv) #9

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


(Mykhailo Poliarush) #10

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

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

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

(Zvonov) #11

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


(Дмитрий Жарий) #12

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


(Дмитрий Жарий) #13

Да, типа:

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

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

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

MySendKeys(page.txtName, data.Name)

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


(Zvonov) #14

Я собственно сздал данный топик, из желания оптимизировать данную операцию.
Из идей у меня есть 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));
    }
}

(vmaximv) #15

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

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


(Zvonov) #16

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

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


(vmaximv) #17

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

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

	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();
}

(Александр Таранков) #18

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

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

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


(Mykhailo Poliarush) #19

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


(Дмитрий Жарий) #21

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

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

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

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

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

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

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


(Александр Таранков) #22

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

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

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


(vmaximv) #23

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


Прозрачная работа со всплывающими окнами и iFrame