19

刚开始使用 Protractor 进行 E2E 测试,我在测试用例结构上遇到了一些麻烦。

不确定我是否可以将我的测试分成单独的规范,然后从另一个规范中调用它们,或者我怎样才能制作好的辅助函数来处理这个问题。

我正在通过中继器查找元素,然后我想为中继器中的每个元素的每个操作进行测试。有点像这样:

describe('tasty', function () {
    'use strict';
    var ptor;

    beforeEach(function () {
        ptor = protractor.getInstance();
        ptor.get('http://localhost:8000/');
    });

    it('Should sample three tasty fruits of every kind on my shopping list.', function () {
        ptor.findElement(protractor.By.className('fruitstore')).click();
        var fruitshelves = ptor.findElements(protractor.By.repeater('fruit in fruits').column('header'));

        fruitshelves.then(function(arr) {
            for (var i=0;i<arr.length; i++) { 
                // Pick up three fruits of this kind from the shelf and put in shopping cart
                // Should be listed on my shopping list 
                // Open the wallet
                // Should have money
                // Pay for the fruits and put it in your shopping bag
                // Should be able to complete the transaction

                // For each one of the fruits in your shopping bag
                // Take a bite
                // Should be tasty
            }
        });
    });
});
4

4 回答 4

14

根据@langliman 的回答,我已经设法实现了所需的行为。

注意 login.spec.jsLogin.page.js应该位于同一个文件夹中。

Login.page.js 文件:

var LoginPage = function (ptor) {
    //following PageObject pattern define the functions here.
}

module.exports.getLoginPage = function (ptor) {
    return new LoginPage(ptor);
};

login.spec.js 文件:

(function () {
    'use strict';

  describe('login page', function () {

        var ptor = protractor.getInstance();
        var loginPageBuilder = require('./Login.page.js');
        var loginPage = loginPageBuilder.getLoginPage(ptor);

        it('should login as admin', function () {
            loginPage.visit();
            loginPage.enterUsername('user');
            loginPage.enterPassword('password');
            loginPage.login();
        });
  });

}());
于 2014-05-14T10:46:47.613 回答
11

我来这个问题是为了寻找一种在 Protractor 中的规范文件之间共享辅助函数的方法。如果其他人也在寻找相同的东西,事实证明,因为 Protractor 只是在 Node 中运行,你需要做的就是var helpers = require('./your-helper-file').

于 2014-04-14T18:13:55.317 回答
7

如果您想要共享设置和之前/之后的功能以及辅助方法,一种解决方案是从您的规范帮助程序中要求测试,而不是从测试中要求您的规范帮助程序。

conf.js

exports.config = {
  seleniumAddress: 'http://localhost:4444/wd/hub',
  specs: ['e2e/spec.js']
}

e2e/spec.js

var chai = require('chai'),
    homepage = require('./homepage.js'),
    signin = require('./signin.js');

chai.should()
browser.baseUrl = 'http://localhost:3000'

homepage.test()
signin.test()

e2e/homepage.js

exports.test = function() {
  describe('homepage', function() {
    it('should have the right title', function() {
      browser.get('/')

      browser.getTitle().then(function(title){
        title.should.eq('Home')
      })
    });
  });
}

e2e/signin.js

exports.test = function() {
  describe('signin', function() {
    it('should have the right title', function() {
      browser.get('/signin')

      browser.getTitle().then(function(title){
        title.should.eq('Sign in')
      })
    });
  });
}
于 2014-10-30T19:07:07.313 回答
2

我自己也在看同样的事情,在某种程度上我希望你能在这个问题上给我一个答案。:-)

话虽如此,量角器似乎足够新,以至于没有人真正知道答案,我想这使我的答案与下一个人一样好。

首先,我使用量角器入门页面上描述的页面对象表示法,朝向底部:https ://github.com/angular/protractor/blob/master/docs/getting-started.md

这提供了一个模块化的元素,我在这里的观点是,我最终得到了一组类,每页一个,抽象掉了一些细节。因此,例如,我可能有一个“foo”类,其中包含“foo.get”和“foo.validate(id, name, otherData)”等抽象。这将是一种提取重复代码的方法。

我还没有解决的一点是如何创建一个模块库,然后将它们组装成一组场景。不过我有几个想法:

  1. 潜在的问题是相互包含javascript文件的能力——这实际上并不作为一种能力存在。有一些我不想使用的第三方库,而且我还没有看到使用 Angular 的模块功能来做到这一点的方法。
  2. End 2 end 测试可能非常依赖于测试的顺序。因此,一个测试可能会创建数据,然后另一个测试可能会使用该数据。例如,如果您想要一个让人们登录的测试,您可能需要一个首先注册人们的测试。您可能不想将注册放在您运行的每个测试的前面。因此,无论如何,您可能需要对测试场景的顺序进行大量控制
  3. 因此,一种选择是将所有内容放在一个非常大的文件中。这与我们在学校学到的一切都背道而驰,但我还没有真正想出一个行不通的理由。然后,您可以编写函数和抽象来满足您的需求。
  4. 如果你按照它进入下一阶段,另一种选择是编写一系列具有严格命名约定的 javascript 文件,然后在执行它们之前使用 grunt 为你连接它们。因此,例如:
    • 一组名为 xxxx.page.scenario.js 的文件,其中包含“页面对象”定义 - 基本上是每个页面的辅助方法
    • 一组名为 xxxx.functions.scenario.js 的文件,其中包含您的场景的常见组件 - 所以也许您有一个注册和登录操作集,并且您将其制作成一个库函数
    • 一组名为 nnxx.scenarios.scenario.js 的文件,其中包含实际的脚本本身。它们在开头(nn)编号,因此我们可以将它们以可靠的顺序连接起来,从而控制我们的脚本运行的顺序

我还没有说这是一个好主意,只是至少从表面上看它可以工作,并且会产生预期的结果。我主要担心的是它感觉很脆弱——所以随着测试套件规模的扩大,它可能会变得非常难以维护。也许另一种方法是,而不是对场景进行编号,而是将它们定义为依赖项,并确保任何给定的脚本在它声明自己依赖的任何脚本之后运行。这可能也允许对脚本进行子集化 - 所以你可以说“运行 bar 脚本”,框架会知道 bar 脚本需要首先运行 foo 脚本,也许是登录脚本。但是可以忽略所有其他脚本。

编辑:我认为 astrolabe 在这里可能是一个很好的答案,看起来它明确允许您将测试模块化。 https://github.com/stuplum/astrolabe。我刚刚用它完成了一个概念验证,它似乎做了我希望的一切。它的代码最终类似于:

clubs.part.scenario.js:

/**
 * Partial for the page objects associated with clubs
 */
var Page = require('astrolabe').Page;

module.exports = Page.create({
  url: { value: 'UI/index.html#clubs' },
  title: { get: function() { return this.findElement(this.by.id('title')); } },
  description: { get: function() { return this.findElement(this.by.id('description')); } },
  clubTableElement: { value: function(rowNum, columnBinding) { 
    return this.findElement(this.by.repeater('club in clubs').row(rowNum).column(columnBinding)); } }
  }
);

clubs.scenario.js:

/**
 * End to end tests for the club functionality
 */

var homePage = require('../home/home.part.scenario.js');
var clubsPage = require('./clubs.part.scenario.js');

describe( 'Navigate to club list page', function() {
  it ( 'should allow navigation to the club list page', function() {

    homePage.go();
    expect(homePage.clubsLink.getText()).toEqual('Clubs');

    homePage.clubsLink.click();

    expect(clubsPage.title.getText()).toEqual('Club functions');
    expect(clubsPage.description.getText()).toEqual('Soon this will show a list of all the clubs, based on information from the server');
    expect(clubsPage.clubTableElement(0, 'name').getText()).toEqual('First club');    
    expect(clubsPage.clubTableElement(0, 'contact_officer').getText()).toEqual('A Person');    
    expect(clubsPage.clubTableElement(1, 'name').getText()).toEqual('Second club');    
    expect(clubsPage.clubTableElement(1, 'contact_officer').getText()).toEqual('J Jones');    
  }); 

  it ( 'should allow us to go directly to the club list page', function() {
    clubsPage.go();

    expect(clubsPage.title.getText()).toEqual('Club functions');
    expect(clubsPage.description.getText()).toEqual('Soon this will show a list of all the clubs, based on information from the server');
    expect(clubsPage.clubTableElement(0, 'name').getText()).toEqual('First club');    
    expect(clubsPage.clubTableElement(0, 'contact_officer').getText()).toEqual('A Person');    
    expect(clubsPage.clubTableElement(1, 'name').getText()).toEqual('Second club');    
    expect(clubsPage.clubTableElement(1, 'contact_officer').getText()).toEqual('J Jones');    
  }); 
});

我对这种结构很满意,它不能做所有事情,但可以做大部分事情。我提供的示例代码来自我已经使用 angularjs 研究了一段时间的教程,如果您想要与此相关的上下文,我目前正在为 e2e 测试和 Rails 4 更新该教程:http ://technpol.wordpress.com/2013/11/16/5-end-to-end-testing/

于 2013-11-16T06:08:42.160 回答