前端自动化测试(三)- angular和protracor

protractor用于前端UI自动化测试,特别为angular程序定制

特点

  • 端对端(e2e)测试
  • 采用jasmine作为测试框架
  • 基于WebDriverJS,(selenium-webdriver)
  • 针对angular应用增加定位器,更加方便实用
  • 实现自动等待,告别sleep wait,变异步为同步
  • 支持测试代码的调试
  • 支持多浏览器的并行UI测试

使用方法

准备工作

  • 1 安装protractor: npm install -g protractor
  • 2 安装selenium-standlone: webdriver-manager update
  • 3 启动selenium服务器: webdriver-manager start

spec书写

spec.js是用于书写测试用例的文件, protractor默认使用jasmine作为测试框架,举最简单的例子来说,一个spec文件可以这样写,使用describe作为
测试程序”块”, it定义一个用例,expect作为断言,其中browser这个全局的变量,用于操作浏览器.

describe('Protractor Demo App', function() {
    it('should have a title', function() {
        browser.get('http://juliemr.github.io/protractor-demo/');
        expect(browser.getTitle()).toEqual('Super Calculator');
    });
});

运行

  • 配置conf.json
    在测试之前,我们需要建立一个conf.json的文件,在这个文件中,可以配置测试的相关内容,例如:
    • multiCapabilities:使用哪些浏览器测试
    • chromeOptions:chrome浏览器的运行参数(使用哪些插件等)
    • framework:使用哪种测试框架: cucumber macha 还是jasmine
    • specs:测试哪些文件
      详细的配置信息请参考
exports.config = {
   // directConnect: true,

    // Capabilities to be passed to the webdriver instance.
    //capabilities: {
    //    'browserName': 'chrome'
    //},
    multiCapabilities: [ {
        'browserName': 'chrome',
        //'chromeOptions': {
        //    'args': ['--load-extension=/opt/local/share/nginx/html/radar/tanxtag'],
        //}
    }],
    // Framework to use. Jasmine is recommended.
    framework: 'jasmine',

    // Spec patterns are relative to the current working directly when
    // protractor is called.
    specs: ['basic/demo_spec.js','basic/angular_spec.js'],

    // Options to be passed to Jasmine.
    jasmineNodeOpts: {
        defaultTimeoutInterval: 30000
    }
};
  • 运行测试程序
protractor conf.json

grunt运行测试

有时我们需要使用grunt来配置测试任务,下面就是使用grunt-concurrent 模块实现并行运行多浏览器(也可通过conf.json中配置multiCapabilities解决)测试程序的代码:

module.exports = grunt => {
    //This module will read the dependencies/devDependencies/peerDependencies/optionalDependencies in your package.json
    // and load grunt tasks that match the provided patterns.
    require('load-grunt-tasks')(grunt);
    grunt.initConfig({
        concurrent: {
            protractor_test: ['protractor-chrome', 'protractor-firefox', 'protractor-safari']
        },
        protractor: {
            options: {
                keepAlive: true,
                singleRun: false,
                configFile: "conf.js"
            },
            run_chrome: {
                options: {
                    args: {
                        browser: "chrome"
                    }
                }
            },
            run_firefox: {
                options: {
                    args: {
                        browser: "firefox"
                    }
                }
            },
            run_safari: {
                options: {
                    args: {
                        browser: "safari"
                    }
                }
            }
        }
    });

    grunt.registerTask('protractor-chrome', ['protractor:run_chrome']);
    grunt.registerTask('protractor-firefox', ['protractor:run_firefox']);
    grunt.registerTask('protractor-safari', ['protractor:run_safari']);
    grunt.registerTask('protractor-e2e', ['concurrent:protractor_test']);
};

调试测试程序

除了非常方便的运行机制,protractor还提供便捷的调试方式, 使用selenium-webdriver操纵浏览器的时候,调试是非常困难的,在这里protractor就提供调试方式
在代码中加入 browser.pause(); 并且在终端输入 “repl” 就可以使用WebDriver commands来调试程序了:

wd-debug> repl
> element
function (locator) {
    return new ElementArrayFinder(ptor).all(locator).toElementFinder_();
  }
> 

测试非angular的应用

protractor内置方法测试angular的程序,例如它会自动检测angular页面加载完毕才会执行测试程序,当测试非angular程序的时候需要:
– 1 使用 browser.driver 代替 driver
– 2 添加 browser.driver.ignoreSynchronization = true
参考

protractor的详细使用

在protractor中,有几大类用于测试代码,详情请见protractorAPI
– browser: 浏览器的操作
– element & by: 定位获取页面元素
– ExpectedConditions:用于页面操作的逻辑函数,一般同wait连用
– webdriver: selenium 原生的语法函数
– promise:selenium内置的promise方法

浏览器的操作-browser

常用操作代码如下:

  • browser.get:
  • browser.findElement
  • browser.switchTo().frame()
  • browser.executeScript:
  • browser.executeAsyncScript
  • browser.wait:
  • browser.sleep:

选择器- by & element

支持多源选择器
– by.css()
– by.id()
– by.xpath()
– by.name()
– by.tagName()
– by.model():angular专用
– by.binding():angular专用
– by.repeater():angular专用

通过element获取:element(by.id(‘frameId’))或者element.all(by.css(‘some-css’));
在非angular应用中使用browser.driver.findElement(by.id(‘frameId’))

ExpectedConditions

预定义了wait的条件,常用的有
– elementToBeClickable: 按钮可以点击
– presenceOf: 元素出现在dom中
– titleContains: title含有某个字符串
– visibilityOf: 某个元素显示

    var EC = protractor.ExpectedConditions;
    var button = $('#xyz');
    var isClickable = EC.elementToBeClickable(button);

    browser.get(URL);
    browser.wait(isClickable, 5000); //wait for an element to become clickable
    button.click();

综合实例


it ('test login error', function () { _driver.get('http://subway.simba.taobao.com/#!/login'); _driver.wait(protractor.until.elementLocated(by.css('.login-ifr')),1000).then(function (elem) { _driver.switchTo().frame(elem); _driver.findElement(by.name('TPL_username')).sendKeys('zhangmeng1986712'); _driver.findElement(by.name('TPL_password')).sendKeys('xxxxx'); _driver.findElement(by.id('J_SubmitStatic')).click(); _driver.sleep(1000); browser.driver.findElement(by.css('.error')).then(function (elem) { return elem.getInnerHtml().then(function(text) { expect(text).toMatch('密码和账户名不匹配'); }); }); }); });

page object pattern

page object的模式大家一定不陌生,通过合理的配置可以使测试代码更容易维护,举例来说可以这样:

//书写一个input操作类
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();
  };
};
//测试代码
describe('angularjs homepage', function() {
  it('should greet the named user', function() {
    var angularHomepage = new AngularHomepage();
    angularHomepage.get();
    angularHomepage.setName('Julie');
    expect(angularHomepage.getGreeting()).toEqual('Hello Julie!');
  });
});

mobile端的测试

详情参考
这个例子是使用Appium作为server端进行测试的,由于selenium-webdriver不能直接联Appium, 所以需要使用wd-bridge进行折衷.

e2e测试程序设计准则

参考

参考代码

本文的参考代码见 Github