Наиболее частые ошибки проектирования + [Selenium/Java/PageObject] - на пути к идеалу.

Doing it wrong:

Тест-кейсы:

  1. В тестах присутствуют локаторы. Если это так, то не стоит продолжать написание новых тестов, даже если вы не используете PageObject pattern. Когда количество тестов приблизится к 100+ то поддерживать всю эту кашу из локаторов будет, мягко говоря, не просто. И большая часть времени работы над проектом будет заключаться в использовании Find&Replace+FireBug(IEDevTool)
  2. В тестах присутствуют операторы ветвления/циклов/try{}catch etc. Тесты должны быть атомарными (по-крайней мере с виду), а все остальное "непотребство" должно быть спрятано в недрах PageObject'a. 
  3. В тестах присутствуют Assert'ы, Sleep'ы, Wait'ы, создание объектов не являющимися PageObject'ами и прочее. См п.2

Если вы добьётесь выполнения этих трех пунктов, пол-дела сделано.

Пример теста:

 

public void test() {
   Manager m = new Manager(driver);
   SelectColumnsDialog d = m.clickSelectColumns();
   d.includedColumns.verifyContains("Title");
   d.includedColumns.verifyItemPosition("Title", 1);
   d.includedColumns.selectItem("Title");
   d.includedColumns.verifyItemSelected("Title");
   d.moveDown.click();
   d.includedColumns.verifyItemPosition("Title", 2);
}

Локаторы:

  1. [Не читабельные]/[Сгенерированные] локаторы. Залог стабильных, легко поддерживаемых тестов - хорошее знание Xpath/CSS language. Поэтому если вы видите в своём проекте что-либо похожее на //div/table[2]/div[4]/td/a, бросайте все и беритесь за изучение Xpath [CSS опционально]. Многие искренне удивляются, когда узнают что Xpath - это в своем роде язык, и что он не ограничивается простым //sometag[@id='id']
  2. Локаторы основаны исключительно на id, class, name и прочих эфемерностях. Да-да, тут в меня полетят камни, но товарищи - мы ж не white-box testing'ом занимаемся. Over 9000 вопросов можно найти в selenium google groups про динамические/не уникальные id и прочее, т.к. в умах прочно сидит, что "если есть id - больше ничего не надо". Почему мы можем искать UI элементы основываясь на той информации, которая скрыта от "живых" пользователей? Если п.1 успешно пройдет, то для вас не будет проблемой построить локатор, так, как-бы его искал пользователь на странице - а именно основываясь на визуальной информации. Да - в некоторых случаях придется пренебрегать этим пунктом, но этого следует избегать.

PageObject:

Разработчики подарили пользователям WebDriver'а этот замечательный паттерн, но не довели его до логического завершения и... убили его потенциал введя аннотации аля @FindBy перемешав у большинства пользователей понятия PageObject и PageFactory

  1. У PageObject есть филды с аннотацией @FindBy. Да - в начале автоматизации все будет чудесно и просто. Потом вы станете замечать, что все чаще и чаще обращаетесь к driver.findElement(s). Потом увидите, что почти все аннотации похожи как две капли воды и зачастую отличаются один словом. Потом столкнетесь с ограничениями в различных ожиданиях/кондишенах. Так что, если вы начинаете работать над более-менее серьезным проектом - можете сразу исключить использование @FindBy, сократив время на последующий рефакторинг. Как альтернатива - используйте филды  типа By, другой подход будет описан ниже.
  2. Sleep'ы. Ну тут всем понятно, чем их меньше - тем лучше. И что точно лучше не пытаться делать - это решить проблему StaleElementException через слипы.
  3. Wait'ы реализованные через операторы циклов, аля int i=0;while(i<10){sleep(1);i++}. Используйте WebDriverWait - не нужно изобретать велосипед.
  4. PageObject'ы слишком громоздки. Устаете скролить класс, что бы добраться до нужного метода? Много повторяющегося кода в разных PageObject'ах? Несмотря на то, что в оффициальных туториалах отмечается что PageObject не обязательно должен представлять целую страницу - это вполне может быть блок/секция/фрейм, большинство пытаются сделать из PageObject - GodObject забывая про наследование и про то, что у PageObject'а вполне может быть PageObject-филд.

Но... Даже если ваш проект будет удовлетворять всем этим условиям - все-равно будет ощущение что чего-то не хватает...

 

1 лайк

На мой взгляд в этой цепочке Test-Page-AUT PageObject паттерна катастрофически не хватает еще одной прослойки, а именно UIObject. В различных оупен-сорс фреймворках я видел зачатки этого слоя - но нигде он не был доведен до ума. Даже в самом вебдрайвере есть подобие - например org.openqa.selenium.support.ui.Select, но опять-таки не самый удачный пример.

По сути PageObject должен являтся лишь контейнером для элементов, где каждый элемент это самостоятельный объект со своими экшенами/верифаями и прочим.

Да и сам элемент может(должен) быть контейнером для других элементов.

Упрощенно будет иметь место такая схема:

class BaseTest{

public WebDriver dirver;

}

 

class BasePage{

public BasePage(BaseTest _parentTest){

...................

}

public BasePage(BasePage _parentPage){

..................

}

}

 

class BaseElement implements WebElement, Locatable{

public BaseElement(BasePage _parentPage){

................

}

public BaseElement(BaseElement _parentElement){

...............

}

}

Да, реализация BaseElement будет не простым делом, однако когда она будет решена дальше будет гораздо легче.

Например класс чекбокса будет выглядеть так:

public class Checkbox extends BaseElement {

private static final String CHECKBOX = "//*[contains(text(),'%s') or ./TD[contains(text(),'%1$s')]]//INPUT[@type='checkbox']";
public Checkbox(BasePage _page, String name) {
super("\"" + name + "\" checkbox", _page, CHECKBOX, name);
}}

Или например PageObject:

public class Manager extends BasePage{

private leftPanel = new Panel(this,"Left");

private Button clearButton = new Button(leftPanel, "Clear");

private Button okButton = new Button(leftPanel, "Ok");

private Checkbox rememberMe = new Checkbox(this,"Remember me");

}

public class Editor extends BasePage {

private class Template extends BaseElement {

public Button clearButton = new Button(this, "Clear");
private static final String DOCUMENT_TEMPLATE = "//div[text()='Templates']//TD[.//*[text()='%s']]";
public DocumentTemplate(ContentEditor _page, String templateName) {
super("document tamplate \"" + templateName + "\"", _page,
DOCUMENT_TEMPLATE, templateName);
}

public void selectTamplate(String templateName){

 DocumentTamplate docTemplate =   new DocumentTemplate(this, templateName);

docTemplate.clearButton.click()

docTemplate.click();

}
}


и т.д.


Стоит ли игра свеч? Стоит. Более того, я в этом уверен, - необходима. Без такого подхода весь код, рано или позно, начнет обрастать всяческими заплатками/затычками/вокэраундами/длинными локаторами и прочей некрасивостью.

Простейший пример: графический чекбокс, с которым isSelected() вебдравера никак не прокатит, что заставит городить огороды. А при использовании UIObject нужно будет отверрайдить всего-лишь один метод.

По поводу UIObject:

Как я понимаю, имеется ввиду что-то вроде создания отдельных классов, работающих с каждым отдельным типом элементов страницы. Отличный пример такой реализации http://habrahabr.ru/post/134462/

Сейчас же в силу причин используя Yandex Html Elements framework, для описания элементов мы, уж не помню почему, такого не делаем, но хотелось бы. :)

Было бы отлично, если бы это все было легко и непринужденно обьеденено, в каком-нибудь open source решении "из коробки", чтобы не приходилось все это "скрещивать".

Подход интересный и правильный, имхо. Минус в том, что такие вопросы решаются индивидуально в рамках отдельно взятого проекта. Невозможно просто так взять и написать универсальные UIObjects. Взять тот же Select, который идёт из коробки. Далеко не всегда он работает и нужно писать что-то своё.

 

А первый и второй пример по линкам по сути одно и тоже - есть движение в нужную сторону (UIObject), но в совокупности с @FindBy, всяческими fieldDecorator, locatorFactory, и прочей кашей, приправленной в добавок java.lang.reflect.* сводит все на нет. Да и сути нету:

https://github.com/yandex-qatools/htmlelements/blob/master/htmlelements-java/src/main/java/ru/yandex/qatools/htmlelements/element/Button.java

public class Button extends TypifiedElement {
public Button(WebElement wrappedElement) {
super(wrappedElement);
}
public void click() {
getWrappedElement().click();
}
}

 

http://habrahabr.ru/post/134462/

@FindBy(css = "input[type=\"submit\"]")
private Button searchButton;



А почему это buttons? Почему не линки? Не панели? Не целлы в гриде? Потому что на каждый такой баттон придется писать //input[@type='button' and @value='somthing']?

А представим, например, что заказчик захотел "красивые" кнопочки - и все (а в худшем случае часть из них) input[@type='button'] резко превратились в div[@class='button']. Что ожидает человека, который построил свой фреймворк на подобии указаных выше? Кропотливый Find&Replace...

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

И в случае с "красивыми" кнопками достаточно будет внести небольшие изменения всего-лишь в одном месте - изменить

private static final String BUTTON = "//INPUT[(@type='button' or @type='submit') and (@value='%s']";

,на

private static final String BUTTON = "//*[(@type='button' or @type='submit' or @class='button') and (@value='%s' or text()='%1$s'])]";

Можно, и даже нужно, и чем универсальнее - тем лучше. В самом худшем случае придется подправить locator pattern, либо экстенднуть существующий объект, настроив его поведение. Select  "из коробки"  потому и не всегда работает - он не до конца вписывается в данную концепцию - это простой wrapper для WebElement c хардкодом, но никак не полноценный UI object:

public Select(WebElement element) {
String tagName = element.getTagName();

if (null == tagName || !"select".equals(tagName.toLowerCase())) {
throw new UnexpectedTagNameException("select", tagName);
}
..........................

public List<WebElement> getOptions() {
return element.findElements(By.tagName("option"));
}

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

http://www.gwt-ext.com/demo/#basicComboBox

Думаю то, что вы считаете самом худшим случаем, встречается сплошь и рядом.

Я не говорил про "супер-универсальные" классы, а про легкость расширения/изменения стандартных.

class GWTComboBox extends CheckBox{

class Option extends BaseElement{

  String optionPattern = "//div[contains(@class,'combo-list-item') and text()='%s']";

  public Option(BaseElement parentElement, optionName){

   super(parentElement, optionPattern, optionName);

  }

}

private Input text = new Input(parentPage, "State:");

private BaseElement picker = new BaseElement(this, "..//img");

private void expand(){

  picker.click();

}

@Override

public select(String optionName){

expand();

Option option = new Option(this,optionName);

option.waitForElementIsVisible();

option.click();

}

@Override

public String getSelectedOption(){

  return text.getValue();

}

}

Но наверное в этом случае правильнее пойти другим путем - наследовать от Input и добавить метод select

В итоге мы возвращаемся к тому, о чём я и говорил. Для каждого проекта понадобится набор уникальных UIObjects. Да, их можно отнаследовать от уже имеющихся, но факт остаётся фактом: полная универсальность почти недостижима.

Повторюсь, ваш подход я считаю абсолютно правильным и нужным.

не совсем понятно про @FindBy, не могли бы вы подробней описать, почему так делать нельзя? я на работке именно так и сделал, удобно ведь и просто