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

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

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

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

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

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

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

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

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

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

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

P.S.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

click(element);

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

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

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

1 лайк

[Resolved] Зачем нужен static в java: Зачем нужен static java - automated-testing - #6 от пользователя mindjin

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

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

Класс 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);
	}
}

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

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

Вы шутите?

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

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

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

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

Selenide

1 лайк

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

1 лайк

Нет такого разреза “web-ui” - есть хорошая архитектура и не очень.
При плохой архитектуре тоже можно писать код - но не много и не долго.

Я бы уже после второго написания строки GoogleHomePage homePage = new GoogleHomePage(getDriver()); выкинул бы этот getDriver() “на мороз”.

	       Driver
  	     /	      \
	Test	---     Page

Test -> Page -> Driver

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

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

В пейджах можете юзать драйвер, хелперах можете юзать драйвер. Как-то так…

Dы главное не переживайте и не паникуйте :slight_smile:

Смотрите, поищите разные реализации на гитхабчике - Search · selenium framework · GitHub

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

как примерчик:

:slight_smile:

рано или поздно придете к Selenide :slight_smile: Придет понимание, что писанина собственного селенида забирает кучу времени и дебага, если вы не оч опытный разработчик :slight_smile:

Я когда-то подумал, зачем тратить столько времени, если в контексте моих проектов, бизнесу пофигу на чем там у меня тесты написаны, но с селениде получается на много быстрее и проще их писать.

Но на собеседованиях вопросы по селениду вряд ли будут, а вот по WebDriver, будут… Как раз анализ чужого кода хорошо помогает в развитии.

5 лайков

Добрался до ПК, а то с телефона вообще ничего не рассмотреть было.
Вообще, код в последнем примере слегка странный. Драйвер объявлен и в BasePage и в BaseTest. То ли опечатка при спешке, то ли путаница у человека.

Пассажи про [quote=“vmaximv, post:41, topic:13952”]
При плохой архитектуре тоже можно писать код - но не много и не долго.
[/quote]
Для меня мало, что значат. Но вот это уже меняет дело: [quote=“vmaximv, post:41, topic:13952”]
Я бы уже после второго написания строки GoogleHomePage homePage = new GoogleHomePage(getDriver()); выкинул бы этот getDriver() “на мороз”.
[/quote]

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

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