Вложенные объекты страницы в транспортире

Вопрос:

Каков канонический способ определения вложенных объектов страницы в транспортире?

Случай использования:

У нас есть сложная страница, которая состоит из нескольких частей: панель фильтра, сетка, сводная часть, панель управления сбоку. Помещение всех определений элементов и методов в один файл и один объект страницы не работает и не масштабируется - это становится беспорядком, который трудно поддерживать.

3 ответа

Решение

Идея состоит в том, чтобы определить объект страницы как пакет - каталог с index.js в качестве точки входа. Родительский объект страницы будет действовать как контейнер для дочерних объектов страницы, которые в этом случае имеют значение "часть экрана".

Объект родительской страницы будет определен внутри index.js и он будет содержать все определения объектов дочерней страницы, например:

var ChildPage1 = require("./page.child1.po"),
    ChildPage2 = require("./page.child2.po"),

var ParentPage = function () {
    // some elements and methods can be defined on this level as well
    this.someElement = element(by.id("someid"));

    // child page objects
    this.childPage1 = new ChildPage1(this);
    this.childPage2 = new ChildPage2(this);
}

module.exports = new ParentPage();

Обратите внимание, как this передается в конструкторы объекта дочерней страницы. Это может потребоваться, если дочернему объекту страницы потребуется доступ к элементам или методам объекта родительской страницы.

Дочерний объект Page будет выглядеть так:

var ChildPage1 = function (parent) {
    // element and method definitions here
    this.someOtherElement = element(by.id("someotherid"));
}

module.exports = ChildPage1;

Теперь было бы очень удобно использовать этот тип объекта страницы. Вам просто требуется родительский объект страницы и использование точечной нотации, чтобы получить доступ к объектам подстраницы:

var parentPage = requirePO("parent");

describe("Test Something", function () {
    it("should test something", function () {
        // accessing parent
        parentPage.someElement.click();

        // accessing nested page object
        parentPage.childPage1.someOtherElement.sendKeys("test");
    });
});

requirePO() вспомогательная функция для облегчения импорта


Пример структуры каталогов вложенных объектов страницы из одного из наших проектов автоматизации тестирования:

введите описание изображения здесь

Это более общая тема, когда речь идет об объектах страницы и о том, как их поддерживать. Некоторое время назад я наткнулся на одну из техник "Шаблон дизайна объекта страницы", которая мне понравилась и имела для меня большой смысл.

Вместо того, чтобы создавать экземпляры объектов дочерней страницы в объектах родительской страницы, было бы идеально следовать концепции наследования прототипа javascript. Это имеет целый ряд преимуществ, но сначала позвольте мне показать, как мы можем достичь этого:

Сначала мы создадим объект родительской страницы ParentPage:

// parentPage.js
var ParentPage = function () {
// defining common elements
this.someElement = element(by.id("someid"));

// defining common methods
ParentPage.prototype.open = function (path) {
browser.get('/' + path)
}
}

module.exports = new ParentPage();  //export instance of this parent page object

Мы всегда экспортируем экземпляр объекта страницы и никогда не создадим этот экземпляр в тесте. Поскольку мы пишем сквозные тесты, мы всегда видим страницу как конструкцию без сохранения состояния так же, как каждый http-запрос является конструкцией без сохранения состояния.

Теперь давайте создадим наши дочерние объекты страницы ChildPage мы бы использовали Object.create Метод для наследования прототипа нашей родительской страницы:

//childPage.js
var ParentPage = require('./parentPage')
var ChildPage = Object.create(ParentPage, {
/**
 * define elements
 */
username: { get: function () { return element(by.css('#username')); } },
password: { get: function () { return element(by.css('#password')); } },
form:     { get: function () { return element(by.css('#login')); } },
/**
 * define or overwrite parent page methods
 */
open: { value: function() {
    ParentPage.open.call(this, 'login'); // we are overriding parent page's open method
} },
submit: { value: function() {
    this.form.click();
} }
});
module.exports = ChildPage

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

Object.create Метод возвращает экземпляр этой страницы, поэтому мы можем сразу начать использовать его.

// childPage.spec.js
var ChildPage = require('../pageobjects/childPage');
describe('login form', function () {
it('test user login', function () {
    ChildPage.open();
    ChildPage.username.sendKeys('foo');
    ChildPage.password.sendKeys('bar');
    ChildPage.submit();
});

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

  • устраняет тесную связь между родительскими и дочерними объектами страницы
  • способствует наследованию между объектами страницы
  • ленивая загрузка элементов
  • инкапсуляция методов и действий
  • чище и гораздо проще понять отношения элементов вместо parentPage.childPage.someElement.click();

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

Я не использую Protractor, но, может быть, вы можете попробовать идею ниже - по крайней мере, до сих пор она работала хорошо для меня:

Я использую то, что вы можете назвать "Компонентный объект" - я делю страницу на компоненты или части, и, предположим, мне дана область действия каждого компонента, я ищу и добавляю элементы к компонентам на основе их областей. Таким образом, я могу легко использовать одни и те же / похожие компоненты на разных страницах.

Например, со страницей http://google.com/ я делю ее на 3 части: введите описание изображения здесь Допустим, мы назовем эти 3 части как: Заголовок, SearchForm, Footer

Код для каждой части будет примерно таким:

class Header {
   public Header(SearchContext context){
       _context = context;
   }

   WebElement GmailLink {
       get {
           return _context.FindElement(By.CssSelector("[data-pid='23']"));
       }
   }
   WebElement ImagesLink {
       get {
           return _context.FindElement(By.CssSelector("[data-pid='2']"));
       }
   } 

   SearchContext _context;
}

class SearchForm{
   public Header(SearchContext context){
       _context = context;
   }

   WebElement SearchTextBox {
       get {
           return _context.FindElement(By.Name("q")):
       }
   }

   WebElement SearchButton {
       get {
           return _context.FindElement(By.Name("btnK")):
       }
   }

   SearchContext _context;
}
..

И код для страницы google.com будет выглядеть так:

class GoogleComPage{
   WebDriver _driver;
   public GoogleCompage(driver){
       _driver = driver;
   }
   public Header Header{
       get {
           return new Header(_driver.FindElement(By.Id("gb")));
       }
   }

   public SearchForm SearchForm{
       get {
           return new SearchForm(_driver.FindElement(By.Id("tsf")));
       }
   }
}
Другие вопросы по тегам