Архитектура. Основные элементы.
Что же такое архитектура? Это, по сути своей, структура разрабатываемой программы, описание того, как внутри устроены компоненты и как они друг с другом взаимодействуют. Кто-то может подумать , мол это же просто автотесты, зачем тут такие сложные процессы, проектирование архитектуры. Но это очень большое заблуждение.
Отсутсвие архитекуры при проектирование тестового фреймворка приводит к многочисленным переписываниям кода,что в свою очередь требует серьезных временых затрат. Но, как говориться, "Время - деньги". Следствие всего этого потеря прибыли и недовольство заказчиков. Перейдём непосредственно к важным частям архитектуры.
Центральное место в архитектуре для автоматизации занимает паттерн Page Object, с которым вы уже знакомы. Он позволяет отделить логику описания веб-страниц от логики тестов. Так как же не будем забывать и о Page Element, который позволяет инкапсулировать работы с кастомными элементами.
Работая с паттерном Page Object, у нас возникает проблема: где удобнее хранить локаторы? Решить её можно создав UI Map. Для этого можно воспользоваться вариантами описаными ниже. Один из них это использовать файлы .properties.
.properties — текстовый формат и одноименное расширение имени файла, применяемое для сохранения конфигурационных параметров
Каждый параметр сохраняется как пара строк, первая содержит имя параметра (ключ), вторая значение параметра. Ключ и его значение в файле разделяются знаком равенства (ключ=значение). К примеру вы создали файл locators.properties, а его содержимое будет выглядеть так:
LoginPage.title=Login Page
LoginPage.userNameInput=id=Login
LoginPage.userPassInput=id=Password
LoginPage.loginButton=css=input[value=Login]
Grid.FieldFrom=xpath=.//input[contains(@placeholder,'From')]
Grid.Datepicker=css=.datepicker
HomePage.title=Games
А чтобы использовать содержимое Properties файла, мы напишем парсер, который нам в этом поможет:
public class Locators {
private static final Properties locators;
private enum LocatorType{
id, name, css, xpath, tag, text, partText;
}
static {
locators = new Properties();
InputStream is = Locators.class.getResourceAsStream("/locators.properties");
try {
locators.load(is);
}
catch (Exception e) {
System.out.println(e.getMessage());
}
}
public static String title(String pageName) {
return locators.getProperty(pageName);
}
public static By get(String locatorName) {
String propertyValue = locators.getProperty(locatorName);
return getLocator(propertyValue);
}
public static By get(String locatorName, String parameter) {
String propertyValue = locators.getProperty(locatorName);
return getLocator(String.format(propertyValue, parameter));
}
private static By getLocator(String locator){
String[] locatorItems = locator.split("=",2);
LocatorType locatorType = LocatorType.valueOf(locatorItems[0]);
switch(locatorType) {
case id :{
return By.id(locatorItems[1]);
}
case name:{
return By.name(locatorItems[1]);
}
case css:{
return By.cssSelector(locatorItems[1]);
}
case xpath:{
return By.xpath(locatorItems[1]);
}
case tag:{
return By.tagName(locatorItems[1]);
}
case text:{
return By.linkText(locatorItems[1]);
}
case partText:{
return By.partialLinkText(locatorItems[1]);
}
default:{
throw new IllegalArgumentException("No such locator type: " + locatorItems[0]);
}
}
}
Другой способ организации работы с локаторами - это использование Html Elements, котрый объединяет элементы в блоки. Вот как выглядит пример такого блока:
@Name("Search form")
@Block(@FindBy(xpath = "//form"))
public class SearchArrow extends HtmlElement {
@Name("Search request input")
@FindBy(id = "searchInput")
private TextInput requestInput;
@Name("Search button")
@FindBy(className = "b-form-button__input")
private Button searchButton;
public void search(String request) {
requestInput.sendKeys(request);
searchButton.click();
}
}
Тогда Page Object будет описывать следующим образом:
public class SearchPage {
private SearchArrow searchArrow;
// Other blocks and elements here
public SearchPage(WebDriver driver) {
PageFactory.initElements(new HtmlElementDecorator(driver), this);
}
public void search(String request) {
searchArrow.search(request);
}
// Other methods here
}
Еще одной важной частью архитектуру является создание Helper классов (вспомогательных классов). В них мы можем пометить методы, которые мы применяем на многих страницах, чтобы избежать дублирования кода, следуя принципу Don't repeat youself. В такие классы можно поместить реалтзацию кастомных ожиданий или реализацию сложных действий:
public class Waiter {
private static final int DEFAULT_TIME_OUT = 10;
public static void waitFor(final ExpectedCondition condition){
getWaiter().until(condition);
}
public static void waitForJquery(){
getWaiter().until(new ExpectedCondition<Boolean>() {
@Override
public Boolean apply(WebDriver webDriver) {
JavascriptExecutor js = (JavascriptExecutor) webDriver;
return (Boolean) js.executeScript("return jQuery.active == 0");
}
});
}
//other waiters
}
Так же полезным окажется применение шаблон Flow. Он заключается в том, что описывая функционал веб-страниц, в методах мы возвращаем либо саму сираницу, либо объект другой страницы. Такой подход еще больше похож на то, как работает пользователь: он либо выполняет действия на странице и никуда не переходит, либо нажав на каку-либо кнопку оказывается на другой странице веб-приложения.
Вот, пожалуй, одни из основных архитектурных решений, которые упростят вам жизнь, как автоматизаторам. Однако хочется сразу сказать, что универсального решения нет. Все завситит от особеностей проекта, используемых технологий и желания заказчика.