前面博客介绍了如何利用cypress提供的api模拟各种web应用页面操作场景,此次课程将学习如何利用cypress完成接口调用。为什么编写UI层的测试要学习接口调用呢?因为在UI层测试中也需要准备和清理测试数据。假设需要测试的场景是“对发表的文章添加comment”,如果整个测试用例全部通过操作页面元素完成,需要完成三个步骤。
- 第一:发表一篇文章。
- 第二:在文章下面添加comment。
- 第三:清理测试数据,即删除发表的文章。
测试路径相对来说很长,页面任何一步出错,整个测试场景就会失败。但该测试场景的验收点在于“添加comment”,如果此时可以通过调用接口创建一篇文章,然后登陆应用,通过点击页面元素完成“添加comment”的操作,添加comment后再调用接口删除测试数据(创建的文章),这样缩短了UI层测试场景路径,且不影响需要验证的测试点,能提升UI层脚本稳定性。为了完成本次课程目标拆分了三个task。
- 利用Cypress调用Get请求接口
- 利用Cypress调用Post请求接口
- 利用Cypress实现不同接口间值传递
利用Cypress完成Get请求调用
首先,访问“https://angular.realworld.io/” 应用中的“Global Feed”页面,Chrome中可以看到访问该页面对应的Get请求
接下来将给大家讲解如何通过Cypress调用Get请求的接口,下面的代码演示了调用Get Method请求的两种方式。
第一种方式将method和url值作为request()的参数直接传入。
第二种方式将method和url值组装成json字符串作为request()参数传入。
可以看到在调用Get请求接口时没有传入token,因为“RealWorld” 应用没有对此接口做权限控制。所以,即便调用接口的时候不传入token值也能调用成功。另外,如果对接口的Response进行校验,需要采用异步编写的方式,即在.then(response =>{......})校验response的值。后面还会讲解如何获取某个接口的Response Body供其他接口使用。
describe("call api demo", () => {
it("call get method api demo", () => {
cy.request('GET', 'https://conduit.productionready.io/api/articles?limit=10&offset=0')
.then((response) => {
expect(response.status).to.eq(200)
})
});
it("call get method api demo2", () => {
cy.request({
method: 'GET',
url: 'https://conduit.productionready.io/api/articles/'
}).then(response => {
expect(response.status).to.eq(200)
})
});
});
Cypress的Test Runner中选择“callGetApi_spec.js”即可运行上面的脚本,如果接口返回状态码200的校验成功了,说明接口调用成功。
利用Cypress完成Post请求调用
查看“https://angular.realworld.io/” 应用,可以看到创建一篇文章背后调用的是一个Post请求接口。
编写调用Post请求接口的代码如下所示,和get请求接口的主要区别是post请求接口需要传递接口的request body。
it("call create article api demo", () => {
cy.request({
method: 'POST',
url: 'https://conduit.productionready.io/api/articles/',
body: {
"article": {
"tagList": [],
"title": "testTitle",
"description": "test",
"body": "test"
}
}
}).then(response => {
expect(response.status).to.eq(200)
})
});
编写好后在Cypress的Test Runner中运行,运行失败,错误信息是接口返回的状态码为401,401状态码表示无权限访问该接口。前面提到过,应用的很多接口都会进行权限控制,此处无法调用成功,说明这个Post 请求接口做了权限控制。对于这种问题如何Debug呢?其实很简单,首先返回“https://angular.realworld.io/” 打开Chrome的DevTools,查看接口的细节,会在Request Header中发现有个authorization字段,后面带了该字段值。如果你对后端接口很熟悉,那么看到这个字段的名字应该就能猜到上面的接口调用失败是因为没有设置authorization值。
接下来,修改Cypress调用Post请求接口的代码,加入authorization,下面的脚本中authorization的值是写死的,值来源于登陆接口的Response Body。如果你copy这份代码调用过程中仍然报401错误,那么可能Token已过期,需要获取最新的Token。
it("call create article api demo2", () => {
cy.request({
method: 'POST',
url: 'https://conduit.productionready.io/api/articles/',
headers: {
'authorization': 'Token eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6NDMzMDksInVzZXJuYW1lIjoiZTJldGVzdCIsImV4cCI6MTU4MjI2ODU1MX0.t-ny2o9fuJxbbLeTDHm0YOaOci4otYM3hi32gQMB0VA'
},
body: {
"article": {
"tagList": [],
"title": "testTitle",
"description": "test",
"body": "test"
}
}
}).then(response => {
expect(response.status).to.eq(200)
})
});
同样,Test Runner中选择“callPostApi_spec.js”运行上面的脚本。运行结果是第一个脚本的状态码校验失败,因为第一个脚本没有添加authorization。第二个脚本校验通过,说明添加authorization后接口能调用成功。
备注:获取登陆后的Token方式:打开Chrome的DevTools,此时在页面上输入账号、密码登陆,在DevTools中的NetWork中查看接口返回的Reponse Body即可获取到最新的Token值。
我们回顾一下前面调用接口的知识点
- 第一:通过cy.request方法调用接口
- 第二:如果要获取接口的response需要采用异步编写方式,也就是.then(response=> {.....})
- 第三:调用Post请求接口时,设置接口的headers,即添加authorizaiton字段
可以看到上面脚本中authorizaiton的值是手动从登陆接口中抓取的,如果token过期了,那么脚本调用就会失败。理想的自动化脚本应该是自动调用登陆接口获取Token并写入header中,下面将学习如何实现Token的自动生成以及设置。
利用Cypress实现不同接口间值传递
为了获取登陆后返回的Token值,首先登陆“https://angular.realworld.io/” 应用打开Chrome的DevTools查看登陆接口的Request和Response,可以看到Token值在接口的Response Body中。
接口返回的Reponse Body是Json字符串,如下图所示,如果要获取token值,直接写“response.body.user.token”即可。
{
"user": {
"id": 43309,
"email": "e2etest@163.com",
"createdAt": "2018-11-28T09:39:12.873Z",
"updatedAt": "2019-11-22T12:43:06.869Z",
"username": "e2etest",
"bio": null,
"image": null,
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6NDMzMDksInVzZXJuYW1lIjoiZTJldGVzdCIsImV4cCI6MTU4MjM2NDA2N30.Ri6M3zj1u5Pod4tzt2XCXmzHOEzJops3GdwAEeo44yw"
下面是在同一个Js文件中编写“登陆接口”和“创建文章接口”脚本。
it('call multiple api', () => {
cy.request({
url: "https://conduit.productionready.io/api/users/login",
method: "POST",
body: {
//这部分是登陆接口的request body
"user": {
"email": "e2etest@163.com",
"password": "12345678"
}
}
}).then(response => {
return response.body.user.token
//返回response body中的token值供下一个接口使用
}).then(token => {
console.log("my token is " + token)
//这里添加了日志打印,主要是为了调试时使用,调试完成后需要把这行内容删除
cy.request({
method: 'POST',
url: 'https://conduit.productionready.io/api/articles/',
headers: {
'authorization': 'Token '+token //登陆返回的token值传入header中
},
body: { //这部分是“创建文章接口”的request body
"article": {
"tagList": [],
"title": "testTitle",
"description": "test",
"body": "test"
}
}
}).then(response => {
expect(response.status).to.eq(200);
//校验调用“创建文章接口”返回的状态码是200,以此证明接口调用成功
})
})
})
})
以下的脚本是完成一个给已有的文章添加Comment的测试场景,创建文章属于测试数据准备阶段,采用接口的方式完成“文章创建”,然后页面操作完成“添加comment”的场景。脚本内容如下所示
const createArticleOne= require('./createArticleOne');
//引入需要的js文件
describe("add comment for a article",()=> {
it('should add comment for a article successfully', () => {
cy.visit("https://angular.realworld.io");
cy.get('app-layout-header ul li a[href="/login"]').click();
cy.get('[formcontrolname=email]').type("e2etest@163.com");
cy.get('[formcontrolname=password]').type("12345678");
cy.get('app-auth-page button[type="submit"]').click();
createArticleOne.createArticleOne();
//调用该js中的方法,通过接口方式完成“创建文章”,即需要的测试数据
cy.get('app-home-page li a').contains('Global Feed').click();
//下面的内容是在已创建的文章上添加comment信息。
cy.get('app-article-list app-article-preview a h1').contains('testTitle').first().click();
cy.get('app-article-page form fieldset textarea').type('my comment');
cy.get('app-article-page button').contains('Post Comment').click();
cy.get('app-article-page app-article-comment .card-text').first().should('contain','my comment');
});
});
Test Runner中选择文件“addComment_spec.js”即可运行上面的脚本,运行上述脚本,可以看到运行脚本是自动化创建了名称为“TestTile的”文章,然后在该文章下成功添加了comment信息。