Совместима ли объектная модель страницы с огурцом огурца?
С помощью объектной модели страницы Automation мы связываем страницы следующим образом:
WebDriver driver = new WebDriver()
HomePage homePage = new HomePage(driver);
LoginPage loginPage = homePage.GoToLoginPage();
WelcomePage welcomePage = loginPage.Login();
etc
etc
Большим преимуществом этого является то, что если разработчики изменяют домашнюю страницу, чтобы она больше не ссылалась на страницу входа, я могу обновить класс домашней страницы и просмотреть все тесты, которые мне нужно обновить (с ошибками), прежде чем даже запускать тест.
Однако в случае с огурцом каждая строка выше будет образовывать отдельный "шаг" и, следовательно, отдельный метод. Следовательно, как это сделать?
Является ли единственный способ поместить экземпляры классов объектов страницы (например, homePage, loginPage и т. Д.) В персистентное хранилище операторов перекрестного корнишона (например, как POCO specflow или "World")?
3 ответа
Итак, спросив многочисленных экспертов по разработке и автоматизации тестирования, кажется, что решение - продолжить соединение [e.g. WelcomePage welcomePage = loginPage.loginWithValidUser(validUser)]
это путь
Чтобы сохранить экземпляр объекта страницы на разных этапах (например, welcomePage в примере выше), вы можете использовать инструмент внедрения зависимостей (создание функциональности, аналогичной расширениям World в реализации огурца в Ruby).
Вот больше информации: https://cukes.info/docs/reference/java-di
Тем не менее, большинство проектов получат выгоду от модуля внедрения зависимостей, чтобы лучше организовать ваш код и разделить состояние между определениями шагов.
Больше информации от SpecFlow (официальная реализация огурца.net):
http://specflow.org/getting-started/beyond-the-basics/
И, наконец, я создал целый блог вокруг этой области, который мог бы помочь людям, так как взаимодействие объекта "корнишон / страница" представляет для меня большой интерес:
http://www.seligmanventures.com/dev-blog/test-automation-page-object-model-with-gherkin
Другой вариант, который я видел недавно, - это сохранение экземпляров объекта страницы как статических переменных, к которым можно получить доступ из любого класса?
Когда речь идет о большинстве веб-сайтов (где можно использовать URL-адреса), на мой взгляд, лучше всего просто использовать URL-адрес вместо действия, чтобы получить тот же URL-адрес.
Например:
# Suggested by OP:
driver = Selenium::Webdriver.for :chrome, prefs: prefs
homepage = Homepage.new(driver)
login = homepage.go_to_login
welcome = login.log_in_as('dave4429')
# My Suggestion:
homepage = Url.new('/')
login = Url.new('/login')
welcome = Url.new('/welcome')
Это означает, что вы начинаете с URL-адреса вместо того, чтобы начинать с домашней страницы для каждого теста. У вас все еще будут методы, которые вы предложили, но они будут использоваться в других областях, чтобы гарантировать, что пользователь может получить доступ к странице с помощью средств, отличных от URL.
Тем не менее, это не универсальное решение. В случае мобильных и настольных приложений единственным вариантом может быть переход на домашний экран, и в этом случае предложенный вами метод определенно является подходящим.
"Сами объекты страницы никогда не должны проверять или утверждать. Это является частью вашего теста и всегда должно быть в коде теста, а не в объекте страницы". - Selenium HQ
Пример, который я привел, был очень простым, и я бы, скорее всего, обернул их в модули и классы, чтобы включить кодирование следующим образом:
google = Project::Pages::Google.new
google.search_for('Hello, World!')
expect(google.found_result?).to_equal(true)
редактировать
В дополнение к этому, у вас, похоже, неправильное представление о том, как огурец работает с огурцом.
Вы можете иметь несколько строк кода на шаг, так как сам шаг является описанием действий внутри шага.
Например:
Given I am logged in as "dave4429"
When I have submitted the "Contact Us" form with the following data:
| dave4429@example.com | David McBlaine | I want to find out more about your Data Protection services, can I talk to a staff member or get a PDF? |
Then an email should be sent to "support@example.com" with the details specified
Определение "Когда" может выглядеть так:
When(/^I have submitted the "Contact Us" form with the following data:$/) do |table|
rows = table.raw
row = rows[0]
contact_us.fill_form({email: row[0], username: row[1], message: row[2]})
contact_us.submit_message
expect(browser.title).to_equal("Message Sent!")
end
Все зависит от того, сколько вы разбиваете шаги в определении.
Редактировать № 2
Мне также ясно, что вы хотите сделать цепочку методов, что-то вроде contact_us.fill_form({email: row[0], username: row[1], message: row[2]}).submit_message
что, опять же, не исключено при использовании методов, которые я предлагаю, но вопрос о том, должна ли эта цепочка быть для каждой отдельной страницы, или все должно быть включено в один класс или модуль, может быть только ответил вашими потребностями.
Я просто считаю, что это поместит слишком много в один класс, и что разрушение этого класса даст больший контроль тестерам и будет написан менее избыточный код.
После долгих обсуждений по этой теме одинаково правдоподобная альтернатива - не возвращать экземпляры новых страниц при использовании шаблона объекта страницы с корнишоном. Вы потеряете преимущество связывания, которое вы обычно получаете с POM, но код, вероятно, будет читаться лучше и будет менее сложным. Публикуя этот альтернативный ответ, мы, как тестовое сообщество, можем голосовать, какой метод является предпочтением людей.
Это может быть немного сложно с огурцом и селеном. Я разработал шаблон, который включает методы расширения дляIWebDriver
интерфейс для Selenium, позволяющий мне переходить к определенным страницам с помощью объектов страницы. Я регистрируюIWebDriver
объект со структурой внедрения зависимостей SpecFlow, а затем мои классы определения шагов могут свободно инициализировать те объекты страницы, которые им нужны.
Регистрация веб-драйвера Selenium с помощью SpecFlow
Вам просто нужно подключиться к хукам сценария до / после для управления объектом веб-драйвера:
[Binding]
public class WebDriverFactory
{
private readonly IObjectContainer container;
public WebDriverFactory(IObjectContainer container)
{
this.container = container;
}
[BeforeScenario]
public void CreateWebDriver()
{
var driver = new ChromeDriver(...);
// Configure Chrome
container.RegisterInstanceAs<IWebDriver>(driver);
}
[AfterScenario]
public void DestroyWebDriver()
{
var driver = container.Resolve<IWebDriver>();
if (driver == null)
return;
// Capture screenshot if you want
// var photographer = (ITakeScreenshot)driver;
driver.Quit();
driver.Dispose();
}
}
Затем нужно склеить определения шагов и объекты страницы вместе с помощью некоторых расширений на IWebDriver
интерфейс.
Объекты страницы Selenium
Следите за тем, чтобы объекты вашей страницы перемещались друг к другу. Например, HomePage позволяет перейти на страницу "Создать сообщение в блоге" и возвращает объект страницы для этой страницы:
public class HomePage
{
private readonly IWebDriver driver;
private readonly WebDriverWait wait;
private IWebElement CreatePostLink => driver.FindElement(By.LinkText("Create New Blog Post"));
public HomePage(IWebDriver driver)
{
this.driver = driver;
wait = new WebDriverWait(driver, 30);
}
public AddEditBlogPostPage ClickCreatePostLink()
{
CreatePostLink.Click();
wait.Until(d => d.Title.Contains("Create new blog post"));
return new AddEditBlogPostPage(driver);
}
}
И впоследствии AddEditBlogPostPage возвращает BlogPostListingPage при создании нового сообщения в блоге:
public class AddEditBlogPostPage
{
private readonly IWebDriver driver;
private IWebElement Title => driver.FindElement(By.Id("Title"));
private IWebElement PostDate => driver.FindElement(By.Id("Date"));
private IWebElement Body => driver.FindElement(By.Id("BodyText"));
private IWebElement SaveButton => driver.FindElement(By.XPath("//button[contains(., 'Save Blog Post')]"));
public AddEditBlogPostPage(IWebDriver driver)
{
this.driver = driver;
}
public BlogPostListingPage CreateBlogPost(BlogPostDataRow data)
{
Title.SendKeys(data.Title);
PostDate.SendKeys(data.Date.ToShortDateString());
Body.SendKeys(data.Body);
SaveButton.Click();
return new BlogPostListingPage(driver);
}
}
Определения шагов для склейки
Шаг:
When I create a new blog post:
| Field | Value |
| Title | Selenium Page Objects and Cucumber |
| Date | 11/1/2019 |
| Body | ... |
Было бы это определение:
[Binding]
public class BlogPostSteps
{
private readonly IWebDriver driver;
public BlogPostSteps(IWebDriver driver)
{
this.driver = driver;
}
[When(@"I add a new blog post:")]
public GivenIAmAddingANewBlogPost(Table table)
{
var addBlogPostPage = driver.GoToCreateBlogPostPage();
var blogPostData = table.CreateInstance<BlogPostDataRow>();
addBlogPostPage.CreateBlogPost(blogPostData);
}
}
В driver.GoToCreateBlogPostPage();
это метод расширения на IWebDriver
который запускает переход от одного объекта страницы к другому:
public static class SeleniumPageNavigationExtensions
{
public static AddEditBlogPostPage GoToCreateBlogPostPage(this IWebDriver driver)
{
var homePage = new HomePage(driver);
return homePage.ClickCreatePostLink();
}
}
Это дает вам возможность сохранять объекты вашей страницы "чистыми" и лишенными SpecFlow, Cucumber и Gherkin. Вы можете использовать те же методы расширения и объекты страницы в других тестах, которые не используют Gherkin или разработку, управляемую поведением. Это позволяет легко повторно использовать ваши тестовые классы. Ваши тестовые проекты должны быть спроектированы так же целенаправленно, как и собственное тестируемое приложение.