Page Object Pattern [пример написания своего фреймворка]

webdriver
page-object
testng
java
Теги: #<Tag:0x00007fedc12da850> #<Tag:0x00007fedc12da6c0> #<Tag:0x00007fedc12da3a0> #<Tag:0x00007fedc12da170>

(Andrey) #21

Я понимаю, что так логично наследовать, я не понимаю утверждения, что “1. Тесты не должны ничего знать о драйвере”. Что значит не должны и как это возможно если класс тестов наследуется от класса BaseTest в котором, в частности, находится статический экземпляр драйвера…
Вы, кстати, не ответили с чего у вас там новый объект создается без ключевого слова new?
@CrispusDH, да, все это логично по названиям.

Короче, переписываю третий вариант с базовыми классами для тестов и обджектов…

P.S. Ну и опять же два моих класса BaseTest и BasePage будут наследоваться от моего абстрактного APIClass


(Eugene Moskalenko) #22

ну я считаю, что не красиво, когда в тестах идет инициализация, я считаю, что этим должен заниматься фремворк, а в тестах должно быть все красиво и лаконично, “без лишней суеты” :slight_smile:

Посмотрите то, что скинул на гитхабе, там есть реализация, посредством которой это реализовано… :slight_smile:

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

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


(Oleksii Ihnatiuk) #23

“P.S. Ну и опять же два моих класса BaseTest и BasePage будут наследоваться от моего абстрактного APIClass …”
Объясните пожалуйста что там у вас?


(Andrey) #24

В APIClass? Так выше код я приводил этого класса. Что опять неправильно?


(Sergey Korol) #25

Я задам ряд наводящих вопросов. Надеюсь, ответы натолкнут вас в нужное русло.

  • Опишите своими словами, что вы понимаете под “фреймворком”?
  • Как вы думаете, зачем придумали основные концепции ООП (в частности - наследование и полиморфизм)?

Возьмем, к примеру, среднестатистического человека, у которого есть мобильный телефон. Мы знаем, что “в теории” некий абстрактный телефон “умеет” звонить. Как именно это происходит - человека не особо волнует.

Внимание, вопрос: если забрать у человека телефон, сможет ли он осуществить связь по тем же протоколам передачи данных, что и мобильный телефон?

А теперь проведите параллель с программированием. У нас есть классы Human, Phone, MobilePhone. Логично ли будет осуществлять наследование Human -> Phone? А что? Человек ведь умеет звонить, не так ли? :wink:

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

Тестовые сценарии мы пишем от чьего имени? Конечного пользователя или браузера?

  • Если проанализировать среднестатистический пользовательский сеанс в браузере, какие типовые операции выполняет юзер по отношению к компонентам сайта? Учитывая, что в его арсенале имеются только клавиатура и мышка. Найдется ли место в этом списке операции find (в том виде, в котором она представлена у вас)?
  • А что за зверь вообще такой - этот static? Каковы были первопричины его использования? Как вы думаете, чем вообще может быть чревато неправильное его использование?

О технических ошибках, пожалуй, упоминать не буду, т.к. тут итак хватает информации для пищеварения. :wink:


(Oleksii Ihnatiuk) #26

Ваш APIClass не нужен.
Хорошо задумайтесь над каждым словом Сергея.


(Andrey) #27

Каркас, в котором я смогу добавлять, удалять либо изменять тесты.

Как вы думаете, зачем придумали основные концепции ООП (в частности - наследование и полиморфизм)?

Ну, если глобально, то эти две концепции - для уменьшения количества кода. Инкапсуляция для сокрытия данных.

А теперь проведите параллель с программированием. У нас есть классы Human, Phone, MobilePhone. Логично ли будет осуществлять наследование Human -> Phone? А что? Человек ведь умеет звонить, не так ли? :wink:

Не логично, но мой вопрос выше был не совсем про это, ввиду того что я наковырял один проект этого паттерна (и при этом довольно известный человек его писал) и реализовывал у себя похожую ситуацию. Так вот там что-то сродни этому… Там я вижу одно, а тут я слышу другое. И хотя, то что я здесь слышу, вполне логично, у меня возникает вопрос, почему человек который 10 лет (или около того) пишет фреймворки, пишет их по-другому и наследуется и пейджами и тестами от одного класса ConciseAPI? Вы как люди опытные наверняка сможете объяснить почему так, может есть какие-то причины?

Если проанализировать среднестатистический пользовательский сеанс в браузере, какие типовые операции выполняет юзер по отношению к компонентам сайта? Учитывая, что в его арсенале имеются только клавиатура и мышка. Найдется ли место в этом списке операции find (в том виде, в котором она представлена у вас)?

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

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

Использовал статик исключительно в целях того, что это должен был быть единственный объект для всего фреймворка. Мало ли я где-нибудь по незнанию передам не то что надо, или еще какой экземпляр драйвера нарисуется - в общем, говорю как на духу - первопричину.
За неправильное использование не знаю… Читал какие-то статьи, но давненько было, думаю такие вещи чреваты в более серьезных проектах уровня интерпрайз, а не в простых тестовых фреймворках))

P.S.

О технических ошибках, пожалуй, упоминать не буду, т.к. тут итак хватает информации для пищеварения.

А я бы и за технические упомянул - чем больше, тем лучше.
Кто мне еще скажет?


(Andrey) #28

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


(Oleksii Ihnatiuk) #29

Уберите параметр драйвера из конструктора


(Sergey Korol) #30

Я смотрю, что нынче стало модным обзывать “фреймворком” все, что только в голову придет. Как вы думаете, что означают слова “generic” / “reusable” в классическом определении фреймворка? И как вообще сочетаются эти понятия с добавлением, удалением или изменением тестов?

Тесты, ровно как и PageObjects, являются domain specific слоем, который ну никак нельзя отнести к универсальной составляющей какого-либо абстрактного фреймворка.

Завтра вас попросят автоматизировать процессы поиска ошибок для другого доменного слоя / браузеров / ОС, и что тогда вы будете делать со своим “фреймворком”? Попросите месяцок времени, чтобы адаптировать “готовое” решение под новые требования?

Никому такое не говорите на собеседовании только. Пока вы не разберетесь, как / в какой момент / зачем применять все эти концепции + паттерны проектирования, об изобретении своих фреймворков можно смело забыть.

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

К тому же, хорошему специалисту свойственно постоянно совершенствовать свои скиллы, переосмысливать подходы, подстраиваться под современные тенденции разработки и тестирования. Ведь мир не стоит на месте.

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

И опять мимо. Дело вовсе не в названии методов, а в уровне детализации, которую вы выносите на поверхность. Зачем тому, кто создает пейджи, метод find? Вы проектируете фреймворк, который по задумке должен упрощать жизнь тем, кто его использует.

Внимание, вопрос: зачем заставлять пользователя фреймворка вызывать 2 (!) операции, вместо одной, каждый раз, когда необходимо выполнить определенное действие над элементом?

Разве не проще написать:

click(element);

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

Ну если вы, как разработчик, не понимаете, как / что и куда передавать, чтобы не возникало этих “мало ли”, то что же тогда другим ожидать от вашего фреймворка?

Вы выбрали совершенно верный путь к полной потери контроля над своим “детищем”. Вопрос времени. Но иногда и это полезно для того, чтобы осознать и преосмыслить многие вещи, а затем вернуться в строй с новыми силами и энтузиазмом. :wink:


(Eugene Moskalenko) #31

[Resolved] Зачем нужен static в java: [Resolved] Зачем нужен static в java


(Andrey) #32

Вот сделал следующую версию.

Структура проекта такая:

Класс GoogleTest

package com.google.test;

import com.google.pages.*;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;

public class GoogleTest extends BaseTest {
  
	@Test
	public void googleSearchTextByEnter() {
		GoogleHomePage homePage = new GoogleHomePage(getDriver());
		GoogleSearchResultsPage resultPage = homePage.searchByEnter("Selenium");
		assertEquals(resultPage.getFirstLinkText(), "Selenium - Web Browser Automation");
	}
	
	@Test
	public void googleSearchTextByClick() {
		GoogleHomePage homePage = new GoogleHomePage(getDriver());
		GoogleSearchResultsPage resultPage = homePage.searchByClick("Selenium");
		assertEquals(resultPage.getFirstLinkText(), "Selenium - Web Browser Automation");
	}
	
	@Test
	public void googleSignInWrongEmail() {
		GoogleHomePage homePage = new GoogleHomePage(getDriver());
		GoogleSignInPage signInPage = homePage.clickOnSignInButton().inputEmail("selenium@selenium.org");
		assertEquals(signInPage.getEmailStatus(), "Couldn't find your Google Account");
	}
}

Класс BaseTest

package com.google.test;

import java.util.concurrent.TimeUnit;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;

public class BaseTest {
	
	protected WebDriver driver;

	protected WebDriver getDriver() {
		return driver;
	}
	
	@BeforeTest
	public void setUp() {
		initializeDriver();
		setPropertyWindow();
		setPropertyTimeOut(); //можно закоментить при использовании assertThat() для всех элементов 
	}
	
	@AfterTest
	public void tearDown() {
		delay(3000);
		driver.close();
	}
	
	public void initializeDriver() {
		DesiredCapabilities capabilitiesFirefox = new DesiredCapabilities();
		capabilitiesFirefox.setCapability("marionette", true);
		System.setProperty("webdriver.gecko.driver", "e:\\Autotests\\TestGoogle\\src\\test\\resources\\Geckodriver 0.16.1\\geckodriver.exe");
		driver = new FirefoxDriver(capabilitiesFirefox);
	}
	
	public void setPropertyWindow() {
		driver.manage().window().maximize();
	}

	public void setPropertyTimeOut() {
		driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
	}
	
	public void delay (long millisec) {
		try {
			Thread.sleep(millisec);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

Класс GoogleHomePage

package com.google.pages;

import org.openqa.selenium.Keys;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.How;

public class GoogleHomePage extends BasePage {

	@FindBy (how = How.XPATH, using = "//input[@name='q']")
	private WebElement fieldSearch;
	@FindBy (how = How.NAME, using = "btnG")
	private WebElement buttonSearch;
	@FindBy (how = How.XPATH, using = "//a[text()='Sign in']")
	private WebElement buttonSignIn;
	
	public GoogleHomePage(WebDriver driver) {
		super(driver);
	}
	
	public GoogleSearchResultsPage searchByEnter(String text) {
		driver.get(baseURL);
		fieldSearch.sendKeys(text, Keys.ENTER);
		return new GoogleSearchResultsPage(driver);
	}

	public GoogleSearchResultsPage searchByClick(String text) {
		driver.get(baseURL);
		fieldSearch.sendKeys(text);
		buttonSearch.click();
		return new GoogleSearchResultsPage(driver);
	}
	
	public GoogleSignInPage clickOnSignInButton() {
		buttonSignIn.click();
		return new GoogleSignInPage(driver);
	}
}

Класс GoogleSearchResultsPage

package com.google.pages;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.How;
import static org.openqa.selenium.support.ui.ExpectedConditions.*;


public class GoogleSearchResultsPage extends BasePage {

	@FindBy (how = How.XPATH, using = "//div[@class='srg']//a[text()='Selenium - Web Browser Automation']")
	private WebElement firstLink;
	
	public GoogleSearchResultsPage(WebDriver driver) {
		super(driver);
	}

	public String getFirstLinkText() {
		return assertThat(visibilityOf(firstLink)).getText();
	}
}

Класс GoogleSignInPage

package com.google.pages;

import org.openqa.selenium.Keys;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.How;
import static org.openqa.selenium.support.ui.ExpectedConditions.*;


public class GoogleSignInPage extends BasePage {

	@FindBy (how = How.ID, using = "identifierId")
	private WebElement emailInputField;
	@FindBy (how = How.XPATH, using = "//div[contains(text(), 'find your Google Account')]")
	private WebElement emailStatusField;
	
	public GoogleSignInPage(WebDriver driver) {
		super(driver);
	}
	
	public GoogleSignInPage inputEmail(String email) {
		emailInputField.sendKeys(email, Keys.ENTER);
		return this;
	}

	public String getEmailStatus() {
		return assertThat(visibilityOf(emailStatusField)).getText();
	}
}

Класс BasePage

package com.google.pages;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.WebDriverWait;

public class BasePage {

	protected WebDriver driver;
	protected String baseURL = "https://www.google.com/ncr";
	protected long timeOutInSeconds = 10;
	
	public BasePage(WebDriver driver) {
		this.driver = driver;
		PageFactory.initElements(driver, this);
	}
	
	public WebElement assertThat(ExpectedCondition<WebElement> condition) {
		return (new WebDriverWait(driver, timeOutInSeconds)).until(condition);
	}
}

(vmaximv) #33

Осталось сделать


(Andrey) #34

Они и не знают.


(vmaximv) #35

Вы шутите?

public class BaseTest {
	
	protected WebDriver driver;
	.....................................
	public void initializeDriver() {

(Andrey) #36

А вы? Хотите провести инициализацию драйвера без указания самого двайвера?


(vmaximv) #37

Вам что-бы сесть в машину и поехать нужно знать как работает ГРМ?


(Andrey) #38

Если у вас есть, чем мне помочь, тогда помогите и подскажите как.
Если вы только самоутвердиться заходите в эту тему на фоне меня, тогда ладно…


(Dmitrii Demin) #39

Selenide


(Михаил Братухин) #40

Если не секрет, то в чем проблема, если оставить драйвер в базовом тест-классе? Конкретно в разрезе тестов для web-ui?