Совместима ли объектная модель страницы с огурцом огурца?

С помощью объектной модели страницы 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 или разработку, управляемую поведением. Это позволяет легко повторно использовать ваши тестовые классы. Ваши тестовые проекты должны быть спроектированы так же целенаправленно, как и собственное тестируемое приложение.

Другие вопросы по тегам