Здравствуйте!
Вопрос.
Бывают ли у вас такие длинные конструкции:
portlet.scroller.toolbar.saveButton.click();
Здравствуйте!
Вопрос.
Бывают ли у вас такие длинные конструкции:
portlet.scroller.toolbar.saveButton.click();
Нет. Более того, подобные конструкции наталкивают на мысль о полном отсутствии ООП.
а такие? )
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 компонентом.
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
?
На мой взгляд, ваша проблема лишь в том, что вы внесли излишнюю путаницу в трактовку ваших бизнес сущностей. Ваш сайт - это не просто набор степов. Попытайтесь для начала ответить на вопрос - а с чем я работаю -, прежде чем искать ответ - а что я могу с этим сделать?
А как этот баттон назвать надо было? 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");
}
В тесте вся логика скрыта, а по тесту понятно о чем идет речь, а сам фреймворк уже берет на себя задачу - сделать отчет и положить в него степы и скриншоты и прочее…
Отчет:
Если у меня на втором степе произошло падение теста, оно покажет на каком именно степе и приложит скриншот…
Опыта у меня очень мало, не знаю как я к этому пришел, но ужасно удобно и переиспользовать код, степы и отслеживать по отчетам ошибки…
а где в тесте степы?
Зачем в тесте степы? Степ - это что-то, что ты делаешь:
Все это не обязательно видеть в коде теста. Это надо видеть в отчете теста. А в коде теста, как по мне, желательно писать очень просто и локанично:
Любому человеку будет понятно что делает тест, а в отчете уже посмотрят на степы…
У меня в тесте получилось: открыть страницу + залогиниться + перейти на попап + проверка текста в попапе.
А вот сами степы, что нажать и т.д., ушли на уровень выше…