Каков правильный синтаксис транспортира для объектов страницы?
Я сталкивался с различными типами синтаксиса для объектов Page Protractor, и мне было интересно, каково их происхождение и какой способ предлагается.
Это официальный синтаксис PageObject из учебника Protractor. Мне это нравится больше всего, потому что оно понятно и читаемо:
use strict;
var AngularHomepage = function() {
var nameInput = element(by.model('yourName'));
var greeting = element(by.binding('yourName'));
this.get = function() {
browser.get('http://www.angularjs.org');
};
this.setName = function(name) {
nameInput.sendKeys(name);
};
this.getGreeting = function() {
return greeting.getText();
};
};
module.exports = AngularHomepage;
Тем не менее, я также нашел этот вид:
'use strict';
var AngularPage = function () {
browser.get('http://www.angularjs.org');
};
AngularPage.prototype = Object.create({}, {
todoText: { get: function () { return element(by.model('todoText')); }},
addButton: { get: function () { return element(by.css('[value="add"]')); }},
yourName: { get: function () { return element(by.model('yourName')); }},
greeting: { get: function () { return element(by.binding('yourName')).getText(); }},
todoList: { get: function () { return element.all(by.repeater('todo in todos')); }},
typeName: { value: function (keys) { return this.yourName.sendKeys(keys); }} ,
todoAt: { value: function (idx) { return this.todoList.get(idx).getText(); }},
addTodo: { value: function (todo) {
this.todoText.sendKeys(todo);
this.addButton.click();
}}
});
module.exports = AngularPage;
Каковы плюсы / минусы этих двух подходов (кроме читабельности)? Второй актуален? Я видел, что WebdriverIO использует этот формат.
Я также слышал от одного парня на Gitter, что первая запись неэффективна. Может кто-нибудь объяснить мне, почему?
2 ответа
Инфраструктура объектной модели страницы становится популярной в основном благодаря:
- Меньше дубликата кода
- Легко поддерживать долго
- Высокая читаемость
Итак, как правило, мы разрабатываем тестовую среду (pom) для нашего удобства на основе объема и потребностей тестирования, следуя подходящим шаблонам (pom). Нет НИКАКИХ таких правил, которые говорят, что строго мы должны следовать любым рамкам.
ПРИМЕЧАНИЕ: Framework, чтобы сделать нашу задачу легкой, ориентированной на результат и эффективной
В вашем случае1-й выглядит хорошо и легко. И это не приводит к путанице или конфликту на этапе его обслуживания.
Пример: 1-й регистр-> объявление локатора элемента происходит в верхней части каждой страницы. Было бы легко изменить, если бы в будущем изменился какой-либо локатор элементов.
Тогда как во 2-м случае локаторы объявлены на уровне блоков (разбросаны по странице). Это займет время, чтобы определить и изменить локаторы, если это потребуется в будущем.
Итак, выберите тот, который вы чувствуете себя комфортно на основе вышеуказанных пунктов.
Я предпочитаю использовать синтаксис класса ES6 ( http://es6-features.org/). Здесь я подготовил простой пример того, как я работаю с объектами страницы, используя классы ES6 и некоторые полезные приемы.
var Page = require('../Page')
var Fragment = require('../Fragment')
class LoginPage extends Page {
constructor() {
super('/login');
this.emailField = $('input.email');
this.passwordField = $('input.password');
this.submitButton = $('button.login');
this.restorePasswordButton = $('button.restore');
}
login(username, password) {
this.email.sendKeys(username);
this.passwordField.sendKeys(password);
this.submit.click();
}
restorePassword(email) {
this.restorePasswordButton.click();
new RestorePasswordModalWindow().submitEmail(email);
}
}
class RestorePasswordModalWindow extends Fragment {
constructor() {
//Passing element that will be used as this.fragment;
super($('div.modal'));
}
submitEmail(email) {
//This how you can use methods from super class, just example - it is not perfect.
this.waitUntilAppear(2000, 'Popup should appear before manipulating');
//I love to use fragments, because they provides small and reusable parts of page.
this.fragment.$('input.email').sendKeys(email);
this.fragment.$('button.submit')click();
this.waitUntilDisappear(2000, 'Popup should disappear before manipulating');
}
}
module.exports = LoginPage;
// Page.js
class Page {
constructor(url){
//this will be part of page to add to base URL.
this.url = url;
}
open() {
//getting baseURL from params object in config.
browser.get(browser.params.baseURL + this.url);
return this; // this will allow chaining methods.
}
}
module.exports = Page;
// Fragment.js
class Fragment {
constructor(fragment) {
this.fragment = fragment;
}
//Example of some general methods for all fragments. Notice that default method parameters will work only in node.js 6.x
waitUntilAppear(timeout=5000, message) {
browser.wait(this.EC.visibilityOf(this.fragment), timeout, message);
}
waitUntilDisappear(timeout=5000, message) {
browser.wait(this.EC.invisibilityOf(this.fragment), timeout, message);
}
}
module.exports = Fragment;
// Then in your test:
let loginPage = new LoginPage().open(); //chaining in action - getting LoginPage instance in return.
loginPage.restorePassword('batman@gmail.com'); // all logic is hidden in Fragment object
loginPage.login('superman@gmail.com')