【发布时间】:2017-05-07 15:45:36
【问题描述】:
问题:
在量角器中定义嵌套页面对象的规范方法是什么?
用例:
我们有一个由多个部分组成的复杂页面:过滤器面板、网格、摘要部分、侧面的控制面板。将所有元素和方法定义放入单个文件和单个页面对象中是行不通的,并且无法扩展——它变得一团糟,难以维护。
【问题讨论】:
标签: javascript selenium protractor pageobjects project-organization
问题:
在量角器中定义嵌套页面对象的规范方法是什么?
用例:
我们有一个由多个部分组成的复杂页面:过滤器面板、网格、摘要部分、侧面的控制面板。将所有元素和方法定义放入单个文件和单个页面对象中是行不通的,并且无法扩展——它变得一团糟,难以维护。
【问题讨论】:
标签: javascript selenium protractor pageobjects project-organization
这个想法是将页面对象定义为一个包 - 以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是如何传递给子页面对象构造函数的。 如果子页面对象需要访问 父页面对象的元素或方法。
子页面对象如下所示:
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 的 Prototypal Inheritance 概念。这有很多好处,但首先让我展示一下我们如何实现它:
首先我们将创建父页面对象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
我们在 getter 函数中定义定位器,这些函数在您实际访问属性时进行评估,而不是在您生成对象时进行评估。这样,您总是在对其执行操作之前请求该元素。
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's 开发者指南中找到了这种设计模式,我解释的上述大部分方法都取自该指南。随意探索它,让我知道你的想法!
【讨论】:
parentPage.childPage.someElement.click(); 是一件好事 - 在示例中它可能看起来很笨重,但在实际项目中它是,例如,queue.filters.refresh.click() - 非常好 - 我们立即了解这是什么来的来自..只是想法。
我不使用 Protractor,但也许你可以试试下面的想法 - 至少,到目前为止它对我来说效果很好:
我使用可以称为“组件对象”的东西——我将页面划分为组件或部分,假设我被赋予了每个组件的范围,我根据它们的范围搜索并添加元素到组件。这样,我可以轻松地在不同页面中重用相同/相似的组件。
以页面http://google.com为例,我将其分为3部分: 假设我们将这 3 个部分命名为:Header、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")));
}
}
}
【讨论】: