Бывают ли у вас такие длинные конструкции java webdriver ?!

Здравствуйте!
Вопрос.

Бывают ли у вас такие длинные конструкции:

portlet.scroller.toolbar.saveButton.click();

Нет. Более того, подобные конструкции наталкивают на мысль о полном отсутствии ООП.

1 лайк

а такие? )

scrollerToolbar.openToEditDocument.click();

Ну вас все внутренности торчат наружу. Это плохо.

операции с элементами я описываю в степ-классах, в самих тестах они не фигурируют.

в самом тесте это выглядит как:

private Steps steps;

@Test
public void test(){
   steps.sendToApprove();
}

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

Так кому принадлежат элементы все же? Степам или пейджам?
Интересно все же увидеть, как вы все связываете воедино.

Элементы описываю в классах компонентах:

@Block(@FindBy(xpath = "//div[@class = 'scroller-toolbar actionbar']"))
public class ScrollerToolbar extends BaseHtmlElement {

    @FindBy(xpath = "//button[@class='largeButton z-button' and @title='Создать новый документ']")
    public Button createNewDocument;

    @FindBy(xpath = "//button[@class='largeButton z-button' and contains(@title, 'Обновить список документов')]")
    public Button refresh;

    @FindBy(xpath = "//img[contains(@src, 'buttonarrow.png')]")
    public Button toolbar;

    @FindBy(xpath = "//div[@class='scrollerMenu z-menupopup z-menupopup-shadow z-menupopup-open']" +
            "//*[@title='Создать заявку на включение']")
    public Button createRegister;

    @FindBy(xpath = "//button[@type='button' and @class='largeButton z-button' and contains(@title, " +
            "'Отправить на согласование') or contains(@title, 'Отправка на согласование получателю') " +
            "and @style='text-align: center;']")
    public Button sendToApprove;

    @FindBy(xpath = "//button[@class='largeButton z-button' and @style=\"\" and contains(@title, " +
            "'Согласовать (с ЭП)') or contains(@title, 'Согласовать получателем')]")
    public Button toApprove;

    @FindBy(xpath = "//button[@class='largeButton z-button' and contains(@title, 'Утвердить')]")
    public Button toConfirm;

    @FindBy(xpath = "//button[@type='button' and @title='Открыть документ на редактирование']")
    public Button openToEditDocument;

    @FindBy(xpath = "//button[@title='Сохранить изменения и закрыть окно']")
    public WebElement saveChangeAndCloseDocument;

    @FindBy(xpath = "//button[@class='largeButton z-button' and @title='Подбор проводок']")
    public Button choiceEntries;

    @FindBy(xpath = "//button[@class='largeButton z-button' and contains(@title, 'Завершить редактирование')]")
    public Button endEdit;

    @FindBy(xpath = "//button[@class='largeButton z-button' and contains(@title, 'Отразить в Учете') " +
            "or contains(@title, 'Отразить в учете')]")
    public Button includingInReports;
}

В степ-классы их включаю

public class UFOSSteps {

private ScrollerToolbar scrollerToolbar;
private HighlightedDialog highlightedDialog;

public UFOSSteps(EventFiringWebDriver driver){
    PageFactory.initElements(new HtmlElementDecorator(driver), this);
}

@Step
public void endEdit(){
    scrollerToolbar.endEdit.click();
}

@Step
public void sendToApprove() {
    scrollerToolbar.sendToApprove.click();
}

@Step
public void toApprove() throws AWTException, InterruptedException {
    scrollerToolbar.toApprove.click();
    Helper.robotForCertification();
    highlightedDialog.ok.click();
}

@Step
public void toApproveWithOutCertification() {
    scrollerToolbar.toApprove.click();
}

@Step
public void toConfirm() throws AWTException, InterruptedException {
    scrollerToolbar.toConfirm.click();
    Helper.robotForCertification();
    highlightedDialog.ok.click();
}

Затем в тестах делаю так:

@Features("Интеграция")
public class TestIntegration extends TestBase{

    /**
     * Степы
     */
    private LoginPageSteps loginPageSteps;
    private MainPageSteps mainPageSteps;
    private UFOSSteps ufosSteps;
    private NSISteps nsiSteps;

  ...

@Step
private void request4IncludeCreate() throws Exception {
    loginPageSteps.enter(host, loginFK, passFK);
    mainPageSteps.openFormular(MenuNodes.NSI_REGISTER_OF_APPLICATION_FOR_CHANGE_THE_CONSOLIDATED_REGISTRY);
    nsiSteps.createRequest();
}

@Step
private void request4IncludeSendToApprove() throws InterruptedException {
    ufosSteps.selectDocumentInScroll(Status.DRAFT);
    ufosSteps.sendToApprove();
    ufosSteps.checkStatusDocument(Status.IN_APPROVE);
    mainPageSteps.exit();
}

@Step
private void request4IncludeApprove() throws Exception {
    loginPageSteps.enter(host, loginFK, passFK);
    mainPageSteps.openFormular(MenuNodes.NSI_APPROVE_REQUESTS);
    ufosSteps.selectDocumentInScroll(Status.IN_APPROVE);
    ufosSteps.toApprove();
    ufosSteps.checkStatusDocument(Status.APPROVED);
    mainPageSteps.exit();
}

...

@Stories("Жизненный цикл")
@Test(description = "Заявка на включение Сводного реестра")
public void checkLCRequest4Include() throws Exception {
    request4IncludeCreate();
    request4IncludeSendToApprove();
    request4IncludeApprove();
    request4IncludeConfirm();
    solutionLC();
}

@Stories("Жизненный цикл")
@Test(enabled = false, description = "Заявка на изменение Сводного реестра")
public void checkLCRequest4Change() throws Exception {
    request4ChangeCreate();
    request4ChangeEditAndSendToApprove();
    request4ChangeApprove();
    request4ChangeConfirm();
    solutionLC();
}

так как в веб-морде по сути одна страница и портлет, то вместо пейджев, просто описываю и юзаю компоненты в степах.

А вы не хотите внутри блока то тоже сделать методы для работы с элементами? Блоки - это те же page объекты, только в уменьшенном виде и взаимодействовать с ними нужно так же.

А я даже не знаю, какие функции там можно сделать…

А, ну вообще если этот блок таблица, то да, конечно можно доавить впрям в нем методы по записи\получений значений из колонок и строк… Кстати, а ведь есть уже готовые объекты Таблица?

Как же не знаете? А что же вы тогда в степах делаете?

@Step
public void endEdit(){
    scrollerToolbar.endEdit.click();
}

Можно точно также обратиться через метод компонента:

@Step
public void endEdit(){
    scrollerToolbar.endEdit();
}

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

sendToApprove.click();

Отправить на одобрение -> Нажать. Даже с точки зрения русского языка звучит весьма противоречиво. Если мы уже что-то куда-то отправляем, что еще нам после следует нажать?

В целом, подобная реализация вызывает много вопросов, т.к. у вас степы являются самостоятельными, ни к чему не привязанными сущностями. В итоге, ваша абстракция совсем не упрощает написание тестов. У автоматизатора не должно возникать вопроса - а куда мне двигаться дальше? Ваш код должен сам подсказывать верный путь. Вот у вас есть N степов, дальше вы их объединяете еще в M групп в самих тестах. По каким правилам? Почему одни вынесены в степ-классы, а другие - в тестовые? Как понять, какую последовательность использовать?

Допустим у вас одностраничное приложение, и что? Ну обновляется какой-то центральный блок страницы по какому-то экшену… Но вы ведь точно знаете, что перешли на новую форму со своими уникальными фичами, а не остались на предыдущей, так? А чем тогда форма по сути отличается от страницы? У вас есть все та же логическая взаимосвязь между формами. Набор каких-то вполне реальных флоу для осуществления нужных операций.

В своем коде вы насоздавали множество кирпичиков, которые не имеют логических взаимосвязей. Ваши степы - это лишь промежуточный слой, который должен быть к чему-либо привязан. Они не могут просто так существовать сами по себе. Это нелогично. Еще более нелогично объединять компоненты внутри степов. Это равносильно созданию класса Walk, у которого внутри посредством композиции будут заданы объекты Human и Animal. Это человек и животное умеют ходить, а не наоборот. Walk - это не объект, а действие над каким-либо объектом. Ровно как и Step - не объект, а действие над X компонентом.

1 лайк

sendToApprove - это название кнопки, по нажатию на которую, запускается фнукция - Отправить на согласование.
В чем неразумность?

В тестовом классе не степы, а тесты, из котрых слагаются “большие тесты”. Просто я их обозначил как @Step для того, чтобы в отчете отображались как вложенные шаги теста… Это можно убрать и будет так

@Features("Интеграция")
public class TestIntegration extends TestBase{

    /**
     * Степы
     */
    private LoginPageSteps loginPageSteps;
    private MainPageSteps mainPageSteps;
    private UFOSSteps ufosSteps;
    private NSISteps nsiSteps;

  ...


private void request4IncludeCreate() throws Exception {
    loginPageSteps.enter(host, loginFK, passFK);
    mainPageSteps.openFormular(MenuNodes.NSI_REGISTER_OF_APPLICATION_FOR_CHANGE_THE_CONSOLIDATED_REGISTRY);
    nsiSteps.createRequest();
}


private void request4IncludeSendToApprove() throws InterruptedException {
    ufosSteps.selectDocumentInScroll(Status.DRAFT);
    ufosSteps.sendToApprove();
    ufosSteps.checkStatusDocument(Status.IN_APPROVE);
    mainPageSteps.exit();
}


private void request4IncludeApprove() throws Exception {
    loginPageSteps.enter(host, loginFK, passFK);
    mainPageSteps.openFormular(MenuNodes.NSI_APPROVE_REQUESTS);
    ufosSteps.selectDocumentInScroll(Status.IN_APPROVE);
    ufosSteps.toApprove();
    ufosSteps.checkStatusDocument(Status.APPROVED);
    mainPageSteps.exit();
}

...

@Stories("Жизненный цикл")
@Test(description = "Заявка на включение Сводного реестра")
public void checkLCRequest4Include() throws Exception {
    request4IncludeCreate();
    request4IncludeSendToApprove();
    request4IncludeApprove();
    request4IncludeConfirm();
    solutionLC();
}

@Stories("Жизненный цикл")
@Test(enabled = false, description = "Заявка на изменение Сводного реестра")
public void checkLCRequest4Change() throws Exception {
    request4ChangeCreate();
    request4ChangeEditAndSendToApprove();
    request4ChangeApprove();
    request4ChangeConfirm();
    solutionLC();
}

Свзяь есть:

компоненты -> операции над ними внутри степ-классов -> тесты, юзающие эти степ функции

компоненты объединены в функциях степ-класса, потому что одна бизнес операция, описанная в степе, имеет цепочку действий, как например:

  • нажать на кнопку согласовать
  • подписать документ криптопровайдером
  • нажать на кнопку ОК в модальном диалоге

при этой цепочке действий - юзаются разные компоненты, как например, тулбар, так и модальное окно

поэтому это и объединено в одну степ-функцию - toApprove(); (Согласовать)

В name conventions и контексте применения.

scrollerToolbar.sendToApprove.click();

Как вообще можно догадаться, что это - кнопка, а не скажем чекбокс / радио или календарик (кроме как по прямому переходу)?

Дальше читаем и пытаемся уловить логическую нить: берем тулбар, отправляем на согласование, нажимаем. Звучит, как-будто мы осуществили 2 экшена. Причем, второй - незаконченный. А на самом то деле мы попросили кнопку нажаться. И никто, кроме вас, не поймет этого, пока не прокликает по ссылкам до самого компонента.

Тесты, не помеченные аннотацией @Test, таковыми не являются. Любой unit framework вам это наглядно покажет в отчете. Это могут быть preconditions - да, но не тесты. И опять-таки, как это можно понять? Из следующего кода как раз таки очевидно, что вы одни степы группируете в другие, что в целом вносит еще больше непонимания в концепцию вашей архитектуры.

    /**
     * Степы
     */
    private LoginPageSteps loginPageSteps;
    private MainPageSteps mainPageSteps;
    private UFOSSteps ufosSteps;
    private NSISteps nsiSteps;

@Step
private void request4IncludeCreate() throws Exception {
    loginPageSteps.enter(host, loginFK, passFK);
    mainPageSteps.openFormular(MenuNodes.NSI_REGISTER_OF_APPLICATION_FOR_CHANGE_THE_CONSOLIDATED_REGISTRY);
    nsiSteps.createRequest();
}

Т.е. вместо того, чтобы объеденить логические экшены в страницах / формах, вы объединяете некие степы внутри других степов, и все это внутри тестовых классов. А все для чего? Правильно, для того, чтобы повысить читабельность фактического теста, и можно было бы хоть как-то понять, что он тестирует. Хотя, всего этого можно было бы избежать, помести вы классовые степы в нужное место, связав все воедино - в цепь.

Вопрос скорее в именовании классов. Что такое LoginSteps и почему он заслуживает называться классом? В чем его концептуальное отличие от LoginForm / Page?

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

2 лайка

А как этот баттон назвать надо было? sendToApproveButton?
Или внутри компонента, сделать функцию sentToApprove, сделать ее паблик и юзать типа

scrollerToolbar.sendToApprove();

о чем вы кстати выше говорили…

не понимаю, как объединить операцию внутри формы (и какой именно формы), при которой нажимается баттон в компоненте ScrollerToolbar и затем нажимается кнопка ОК в компоненте HighlightedDialog? Или их вообще не объединять в логическую опреацию, а прямо в тесте создавать объекты этих компонентов и использвоать, типа:

public class Test {

    private ScrollerToolbar scrollerToolbar;
    private HighlightedDialog highlightedDialog;


   public void test(){
          scrollerToolbar.createDocument();
          scrollerToolbar.sentToApprove();          
          highlightedDialog.accept();
    }
}

типа того? то есть - функции по работе с элементами компонентов, описать в самих компонентах, а от степ-классах отказаться?

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

Я для себя вот так решил степы описывать (абстрактный базовый класс от которого наследуются страницы или компоненты, или специальный класс UiUtils):

@Step("Delete cookies")
public T deleteAllCookies() {
    driver.manage().deleteAllCookies();
    return (T) this;
}

@Step("Add parameter for current url \"{0}\" and reload page")
public T addParametrToCurrentUrl(String text) {
    driver.get(driver.getCurrentUrl() + text);
    return (T) this;
}

@Step("Type value \"{1}\" in field \"{0}\"")
protected T typeIn(WebElement element, String text) {
    element.clear();
    element.sendKeys(text);
    return (T) this;
}

@Step("Click element: \"{0}\"")
protected T clickOn(WebElement element) {
    element.click();
    return (T) this;
}

@Step("Select option \"{0}\" from dropdown")
protected T selectFromDropdown(WebElement element, String option) {
    new Select(element).selectByVisibleText(option);
    return (T) this;
}

@Step("Select checkBox \"{0}\"")
protected T selectCheckBox(WebElement element) {
    if (!element.isSelected()) {
        element.click();
    }
    return (T) this;
}

такие степы ложатся в отчет идеально просто, видно по отчету куды ты кликнул или что сделал. Даже если юзаешь метод в методе, как “hideKeyboard” или что-то подобное, то такой степ будет вложенным.

Объект страницы или это может быть компонент:

@Step("Login with name & password")
public PopUp loginWith(String name, String password) {
    typeIn(this.loginField, name);
    typeIn(this.passwordField, password);
    clickOn(this.submitBtn);
    return init(PopUp.class);
}

Но правда такие степы не видны в самом тесте, но мне кажется, что это избыточно степы видеть в тесте самом. Они полезны для отчета, а тест писать более лакончино чтоли, как-то так:

@Title("Main logIn form")
@Test(enabled = true, groups = "login", priority = 10)
public void signIn() {
    //------------------- Test Data -------------------//
    String name = Constants.TestData.LoginData.QA_LOGIN;
    String pass = Constants.TestData.LoginData.QA_PASS;

    // --------------------- Test Case ----------------------//
    LoginPage loginPage = init(LoginPage.class);
    PopUp popUp = loginPage.open("/login.html").then().loginWith(name, pass).thenGoTo(PopUp.class);
    assertThat(popUp.getTitleMessages()).isEqualTo("Title text!");
    assertThat(popUp.getBodyMessages()).isEqualTo("Body text");
}

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

Отчет:

Если у меня на втором степе произошло падение теста, оно покажет на каком именно степе и приложит скриншот…

Опыта у меня очень мало, не знаю как я к этому пришел, но ужасно удобно и переиспользовать код, степы и отслеживать по отчетам ошибки…

1 лайк

а где в тесте степы?

Зачем в тесте степы? Степ - это что-то, что ты делаешь:

  1. открываешь
  2. закрываешь
  3. вводишь
  4. нажимаешь
  5. переходишь
  6. чистишь куки
  7. добавляешь параметр в урл
  8. выполняешь js
  9. выбираешь что-то из выпадалки
  10. … … … … …

Все это не обязательно видеть в коде теста. Это надо видеть в отчете теста. А в коде теста, как по мне, желательно писать очень просто и локанично:

  1. логин с данными (логин, пароль)
  2. проверка

Любому человеку будет понятно что делает тест, а в отчете уже посмотрят на степы…

У меня в тесте получилось: открыть страницу + залогиниться + перейти на попап + проверка текста в попапе.

А вот сами степы, что нажать и т.д., ушли на уровень выше…

1 лайк