Вложенные объекты страницы в транспортире
Вопрос:
Каков канонический способ определения вложенных объектов страницы в транспортире?
Случай использования:
У нас есть сложная страница, которая состоит из нескольких частей: панель фильтра, сетка, сводная часть, панель управления сбоку. Помещение всех определений элементов и методов в один файл и один объект страницы не работает и не масштабируется - это становится беспорядком, который трудно поддерживать.
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")));
}
}
}