时间紧迫,先罗列一些内容
- Protractor的安装与使用
- Protractor的语法解释
- 术语以及原理
- 一些普遍的JS调试语法
安装与使用
API接口
- by与locator
- element与elementFinder/
- promise
- element.all
- browser
基于Protractor编写的测试角度,核心就是找到DOM元素,并与其交互,然后验证其交互结果是否与预期一致(说白了,就是写一个模拟器,模拟用户在网页上的操作动作)。所以查找DOM元素(这还涉及网页的功能逻辑)并与之交互(网页的交互逻辑)非常重要。
by与locator
首先是在DOM树中找到目标的DOM元素。可以才用by函数,它提供了多种查找DOM元素的方式。以下面这行代码为例
by.id('gobutton')
by函数用查找id的方式确定一个locator对象(也就是说,这个locator对象,应该是网页DOM树中、id为‘gobutton’的元素)。
element与elementFinder
接下来需要有函数去接收这个locator对象,才能对DOM元素进行交互操作。以下面这行代码为例
element(by.id('gobutton'))
Protractor提供了一个全局的对象(类或者什么都可以,现在看来都差不多)element,它可以去接收上述的locator对象,并返回一个elementFinder对象。需要说明的是,这个时候对于elementFinder对象无法操作。只有对它发出相关的指令后,才有交互。举个例子:
element(by.id('gobutton')).click();
该代码在获得elementFinder对象之后,要寻求与其进行交互,交互动作为click事件。除了click方法外,elementFinder对象有很多方法,如
//--view code-- <ul class="items"> <li>First</li> <li>Second</li> <li>Third</li> </ul> //--testing code-- element.all(by.css('.items li')).then(function(arr) { expect(arr.length).toEqual(3); });
promise
所有elementFinder对象的方法,返回值都是一个promise对象,举个例子:
getTitleText() { return element(by.css('.navbar-brand')).getText() as Promise<string>; }
这个函数(方法)封装了elementFinder的getText方法,并返回了promise对象(字符型)。所以当想要验证交互结果的时候,需要对promise对象再进行操作。
//--view code-- <ul class="items"> <li>First</li> <li>Second</li> <li>Third</li> </ul> //--testing code-- element.all(by.css('.items li')).then(function(arr) { expect(arr.length).toEqual(3); });
测试代码先获取样式为item li的所有元素(array对象),然后获取这个对象,再去计算对象包含的元素数量,并判断是否等于3。该代码传递出两个信息:
- 可以使用then方法输出值。假设输出的对象(数组、文本、属性值都可以)为object,实际上形式应该是.then(function(object)){...}。花括号内可以对传入的参数(对象object)进行操作。
- 直接使用expect方法来操作值。示例代码其实是先输出值,再操作值。但是完全可以将两步合并成为一步。将arr这个数组对象,用element.all(by.css('.items li')).then(function(arr)代替
element.all
顺便说说element.all。尽管是element对象的一种方法,但是返回值是elementFinder array,即elementFinder的数组集合。所以,其实这种方法就是另一类elementFinder对象了。它的方法首先是针对array对象的,然后若提取出其中的元素,也可以调用elementFinder的方法。举些例子:
//--view code-- <ul class="items"> <li class="one">First</li> <li class="two">Second</li> <li class="three">Third</li> </ul> //--testing code-- //1.filter, actually it is a iteration element.all(by.css('.items li')).filter(function(elem, index) { return elem.getText().then(function(text) { return text === 'Third'; }); }).first().click(); //this paragraph of code would act on each element in the array and find/filter the ones whose text is 'Third'. //btw, return the first element and click it //2.get-->Get an element within the ElementArrayFinder by index. expect(element.all(by.css('.items li')).get(0).getText()).toBe('First'); //3.first-->Get the first matching element for the ElementArrayFinder. //4.last-->Get the last matching element for the ElementArrayFinder. //5.count-->Count the number of elements represented by the ElementArrayFinder. expect(element.all(by.css('.items li')).count()).toBe(3); //6.each-->Calls the input function on each ElementFinder represented by the ElementArrayFinder. element.all(by.css('.items li')).each(function(element, index) { // Will print 0 First, 1 Second, 2 Third. element.getText().then(function (text) { console.log(index, text); }); }); //this function is equal to iteration //7.map-->Apply a map function to each element within the ElementArrayFinder. let items = element.all(by.css('.items li')).map(function(elm, index) { return { index: index, text: elm.getText(), class: elm.getAttribute('class') }; }); expect(items).toEqual([ {index: 0, text: 'First', class: 'one'}, {index: 1, text: 'Second', class: 'two'}, {index: 2, text: 'Third', class: 'three'} ]); //this function reminder me of HashMap? //8.reduce-->Apply a reduce function against an accumulator and every element found using the locator (from left-to-right). let value = element.all(by.css('.items li')).reduce(function(acc, elem) { return elem.getText().then(function(text) { return acc + text + ' '; }); }, ''); expect(value).toEqual('First Second Third '); //this is like string joint? more information, see the official document:http://www.protractortest.org/#/api?view=ElementArrayFinder
特别说明的是,对于elementFinder对象(无论是一个对象还是数组对象),都可以用$$(elementFinder array object)/$(elementFinder object)缩写简化(主要是有时候看到这样的缩写,要是不知道的话,就懵了)
element.all(by.css('.items li')).then(function(arr) { expect(arr.length).toEqual(3); }); // Or using the shortcut $$() notation instead of element.all(by.css()): $$('.items li').then(function(arr) { expect(arr.length).toEqual(3); });
还有对浏览器的操作browser。
术语以及原理
- 异步
- 链式调用
这两个术语是Protractor框架代码运行以及编写的特点。我不太懂,所以也写一写。
异步
说个不成熟的理解。异步特别像多任务同时进行,具体说,你用element对象的不同方法,又是按照id找,又是按照样式找,又是点击,又是键入数值,这些都可以一起完成。谁先谁后,完全取决于代码执行的速度。那么同步呢?就是一个任务完成后再是另一个任务。(特别有意思的是,我研究生学优化的时候,同步的概念和计算机这里的异步是一个意思)。知乎上,我选了一个很有趣的例子,可以说明这个问题。
链式调用
Protractor对象的采用链式调用。一开始觉得这个名词很高深,仔细了解过恍然大悟,其实早有接触过。先说非链式调用,以C#.NET为例,比如
double val = zero2null2(double.Parse(tb_val.Rows[i][0].ToString()));
ToString是第一个函数,double.Parse是第二个函数,zero2null2是第三个函数。这样阅读代码,是从里往外读,带来一定的不便利,而且书写代码也很麻烦。如果没有事先想好,那么多半是写了里面,而是剪切掉,写外面的函数在粘进来。
那么链式调用呢?还是之前的代码,假想一下每个方法函数的返回值都返回object对象,有n多种方法,然后就这么写
double val = tb_val.Rows[i][0].ToString().ToDouble().ToNull().
这样一来,代码不管是阅读还是编写都很流畅。要声明的是,这个理解不专业,但是足够用了。
普遍的JS语法
- describe
- it
- beforeAll, beforEach, afterAll, afterEach
看到团队代码里面有这些语法内容,不甚了解。所以也一并记录在这里。
descibe
describe是Jasmine的全局函数,Jasmine是JS的一种单元测试框架。describe的参数有两个:1)字符串-作为测试内容名字或者标题;2)包含实现测试的代码
describ(string, function(){ //pass } );
可以理解成,string就是一个user story的名字,function就是这个user story的故事情节。当然,describe可以嵌套,就是一个大故事里有很多的章节。
it
it也是Jasmine的全局函数。it的参数也是两个:1)字符串;2)测试的主体
it(string, function(){ //pass });
it可以理解成一个故事中的一段实质的故事内容,人物对话,动作行为等等,都是在it中发生。所以it中自有的变量是私有变量,it通常包含在一个describe中。
expect
前面介绍Protractor的时候有提到过expect,elementFinder对象发出交互指令后,会得到一个promise对象,这个对象的值无法直接获取(动作无所谓啦),只能通过1)then;2)expect来操作。其中then方法也是取值,只有expect才是方法命令。expect也可以作用在it中,就是对行为的断言,返回结果true/false。关于expect的断言方法还有很多,详见参考资料。https://www.ibm.com/developerworks/cn/web/1404_changwz_jasmine/
Setup与Teardown:beforeEach,beforeAll,afterEach与afterAll
在执行测试用例时,会准备大量的工作。建立数据库,建立用户交互场景等等,同时测试完成后,我们也需要释放资源。如果对一个user story(如果很长、很复杂的话)中每个行为都如此准备,显然代码很冗余、模块的复用性也不好。因此可以使用setup和teardown,前者是测试之前的准备工作,后者是测试之后的清理工作。Jasmine中共有4个全局函数来执行这两种操作。
beforeAll [在测试套件(describe块)中所有测试用例执行之前执行一遍beforeAll函数] beforeEach [在每一个测试用例(it块)执行之前都执行一遍beforeEach 函数] afterEach [在每一个测试用例(it块)执行之后都执行一遍afterEach 函数] afterAll [在测试套件(describe块)中所有测试用例执行之后执行一遍afterAll函数]
这些函数都只有一个输入参数,就是内容的执行函数。这些函数和it块之间有运行的逻辑先后,理解它很有必要。
desribe("Check the priority", function(){ beforAll(function(){ console.log('this is beforall'); }); beforeEach(function(){ console.log('this is beforeeach'); }); it("It1",function(){ console.log('I am the 1st it'); }); it("It2",function(){ console.log('I am the 2nd it'); }); describe("I am the child story",function(){ console.log('I am the child story'); }); it("It3",function(){ console.log('I am the 3rd it'); }); console.log('I am the parent story'); afterEach(function(){ console.log('this is the aftereach'); }); afterAll(function(){ console.log('this is the afterAll'); }); });
以上测试代码的结果是:
I am the parent story this is beforall this is beforeeach I am the 1st it this is the aftereach this is beforeeach I am the 2nd it this is the aftereach this is beforeeach I am the child story this is the aftereach this is beforeeach I am the 3rd it this is the aftereach this is the afterAll
所以比较清楚了,describe中不属于任何it块或者setup/teardown的内容先运行。然后是beforall。之后从上往下找到第一个it(嵌套的describe这时也属于一个it),前面是beforeeach,再运行it的代码,最后是aftereach的代码。接着再是第二个it,前后分别是beforeeach和aftereach。之后的it同样如此。最后是afterall。
参考资料: