【问题标题】:What's the correct Protractor's syntax for Page Objects?页面对象的正确量角器语法是什么?
【发布时间】:2016-08-03 09:01:37
【问题描述】:

我遇到了 Protractor 的页面对象的不同类型的语法,我想知道它们的背景是什么,建议采用哪种方式。

这是 Protractor 教程中的官方 PageObject 语法。我最喜欢它,因为它清晰易读:

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 上的一个人那里听说,第一个条目效率低下。谁能给我解释一下为什么?

【问题讨论】:

    标签: protractor pageobjects


    【解决方案1】:

    页面对象模型框架之所以流行主要是因为:

    1. 减少重复代码
    2. 易于长期维护
    3. 高可读性

    所以,通常我们会根据测试范围和需求,按照合适的框架(pom)模式来开发测试框架(pom)。没有这样的规则说,我们应该严格遵循任何框架。

    注意:框架是为了让我们的任务变得简单、以结果为导向且有效

    在你的情况下,第一个看起来又好又容易。并且在维护阶段不会导致混乱或冲突。

    示例第一种情况->元素定位器的声明发生在每个页面的顶部。万一将来任何元素定位器发生更改,更改将很容易。

    而在第二种情况中,定位器在块级别声明(分散在页面上)。如果将来需要,识别和更改定位器将是一个耗时的过程。

    所以,根据以上几点选择你觉得舒服的那个。

    【讨论】:

    • 非常感谢,确实有道理:)
    • @anks,如果您觉得我的帖子对您​​有帮助并更正,请勾选正确。
    【解决方案2】:

    我更喜欢使用 ES6 类语法 (http://es6-features.org/#ClassDefinition)。在这里,我准备了一些简单的例子,我如何使用 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')
    

    【讨论】:

    • 在我发布这篇文章后,我的朋友也建议我使用 ES6,我同意它看起来非常好并且对我来说更熟悉(我以前用 Ruby 编写过页面对象)。但是有一个问题 - 你不使用 Protractor 的元素语法(如 element(by.css('someclass')) - 这是因为 $('') 更短还是其他原因?
    • 是的,我经常使用 element(by.css('')),$/$$ 是一个很好的全局快捷方式。没有任何功能差异。
    • 这个对我不起作用。我尝试使用 javascript 类,最后使用 module.exports = ClassName;在我的 test.js 中,我使用 var Ahomepage = require('./AngularHomePagePOM.js'); Ahomepage = new POM_HomePage();但总是得到错误 undefined [17:53:41] E/launcher - Error: ReferenceError: POM_HomePage is not defined
    猜你喜欢
    • 1970-01-01
    • 2021-12-26
    • 1970-01-01
    • 2014-02-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-15
    • 2011-09-23
    相关资源
    最近更新 更多