转眼之间,你不知道的Cypress系列已经到第15篇了。在Cypress中国群内、在公众号iTesting里,我每天都能看到大量关于Cypress的使用讨论和私下问询。这让我感到无比荣幸。
说起Cypress,读者朋友们应该对“下一代Web端自动化测试技术”,“弯道超车首选”等等早已烂熟于心了。Cypress独特的运行机制(运行在浏览器内)也使得它吊打Webdriver之类的UI自动化测试工具。但是Cypress并不是完美无瑕,我们在使用Cypress做自动化测试时,经常会提的一个问题就是,Cypress不支持跨域访问,而我的测试需要跨域怎么办?
今天在Cypress中国群内,有同学抛出了以下这个待发行的解决方案,我看了后顿时觉得好香,特记录之。(永远不要怀疑Cypress开发团队的愿景:The web has evolved. Finally, testing has too.)
跨域访问的问题
看过我Cypress书的同学都应该明白,Cypress里进行跨域访问会报错:
代码语言:javascript复制// 关注iTesting,跟万人测试团一起成长。
describe('iTesting测试', function () {
it(`[This is a demo test`, function () {
cy.visit('https://www.cypress.io/')
cy.contains('Login').invoke('removeAttr', 'target').click({force: true})
//这里点击google登录,会跨域,所以报错
cy.contains('Log in with Google').click()
});
});
报错的信息通常如下所示:
为了避免这个错误,如果我们使用的是Chrome浏览器进行测试,我们通常在cypress.json文件夹里添加如下配置:
代码语言:javascript复制chromeWebSecurity:false
有时候,我们不想在cypress.json里配置,也可以直接在运行命令行参数时,指定参数:
代码语言:javascript复制yarn cypress open --config chromeWebSecurity=false
但这个方案不总是有效。像当前这个情况就是无效的。
这个时候怎么办?
当前的解决方案是尽量的拆Case,从而保证在一条测试运行里不进行跨域访问。比如,我的这条case实际上是通过google登录,那么我可以在这条case里直接访问登录的那个url,而不必访问cypress.io, 但是这个是很简单的情况,实际测试中,很复杂,我们必须要拆分测试用例,而这个操作很不方便。好在Cypress团队也注意到了这个问题。在即将发布的9.6.0版本中,Cypress将支持跨域访问。
Cypress支持跨域 -- cy.origin()
在即将发布的9.6.0版本中,我们可以通过cy.origin()命令来支持跨域访问。要启用cy.origin(),我们需要在cypress.json中配置如下:
代码语言:javascript复制{ "experimentalSessionAndOrigin": true}
此时,你就可以使用了,cy.origin()的语法如下:
代码语言:javascript复制cy.origin(url, callbackFn)
cy.origin(url, options, callbackFn)
注意:
url: 这个url是cy.origin执行回调的那个次要来源(假设在A domain下进行某个操作会跳转到domain B,那么A就是主要来源,B就是次要来源)。
options: 这个参数是一个普通的 JavaScript 对象,它将被序列化并从主要来源发送到次要来源。从那里它将被反序列化并作为第一个也是唯一的参数传递给回调函数。该参数的args对象(注意这个对象,看后续的代码)是唯一可以将数据注入回调的机制,因为回调不是闭包,并且不保留对声明它的 JavaScript 上下文的访问。
callbackFn: 此参数包含要在次要来源中执行的Cypress命令的函数。Cypress将触发此函数并从当前Cypress实例传递到次要源并进行评估。
实践
举个例子吧:
代码语言:javascript复制// 关注iTesting,跟万人测试团一起成长。
describe('iTesting测试', function () {
it(`This is a demo`, function () {
cy.visit('https://www.cypress.io/')
cy.contains('Login').invoke('removeAttr', 'target').click({force: true})
//这里点击google登录,会跨域,所以报错
cy.contains('Log in with Google').click()
// 这个url填你的地址,可以手工copy过来
cy.origin("https://accounts.google.com/o/oauth2/auth/identifier?response_type=code&redirect_uri=https://auth.cypress.io/login/callback&scope=email profile&state=vJHVGMmWHCZr7cEG-u8Si9JuLhh2uD9V&client_id=976680234070-jlotbgdqfah46f34o4oheoqkrng39jn9.apps.googleusercontent.com&hl=zh-CN&flowName=GeneralOAuthFlow", () => {
cy.get("input[type="email"]").type('kevin.cai@google.com');// 这里用户名写死了。
cy.contains('Next').click()
cy.get("input[type='password']").type('你的密码')//密码也是一样写死了。
cy.contains('Next').click()
//登录成功,断言
cy.url().should('include', 'https://dashboard.cypress.io/welcome')
});
});
更进一步 -- 重用
可以看到,上面的是个通过SSO登录的例子,那么实际上,登录应该是个通用的操作,我们把它写到support/commands.js
文件下:
Cypress.Commands.add("googleLogin", (email, password) => {
cy.visit('https://www.cypress.io/')
cy.contains('Login').invoke('removeAttr', 'target').click({force: true})
//这里点击google登录
cy.contains('Log in with Google').click()
// 关注iTesting,跟万人测试团一起成长。
cy.origin(
"登录地址",
{ args: [email, password] }, // args 对象,划重点
([email, password]) => {
cy.get("input[type="email"]").type(email); // args对象用法
cy.contains('Next').click()
cy.get("input[type='password']").type(password);
cy.contains('Next').click()
}
);
//登录成功,断言
cy.url().should('include', 'https://dashboard.cypress.io/welcome')
});
通过这样的方式,我们就可以直接在测试用例里调用:
代码语言:javascript复制beforeEach(() => {
cy.googleLogin('用户名iTesting', '密码');
});
使用cy.session()加速鉴权
记得 你不知道的Cypress系列(11) -- 使用cy.session()加速鉴权 这篇文章不?我们来再次update下我们的最终代码。
代码语言:javascript复制Cypress.Commands.add("login", (email, password) => {
cy.session([email, password], () => {
cy.visit('https://www.cypress.io/')
cy.contains('Login').invoke('removeAttr', 'target').click({force: true})
//这里点击google登录
cy.contains('Log in with Google').click()
// 关注iTesting,跟万人测试团一起成长。
cy.origin(
"登录地址",
{ args: [email, password] },
([email, password]) => {
cy.get("input[type="email"]").type(email);
cy.contains('Next').click()
cy.get("input[type='password']").type(password);
cy.contains('Next').click()
}
);
//登录成功,断言
cy.url().should('include', 'https://dashboard.cypress.io/welcome')
});
});
使用的方式一样,直接在测试代码里调用即可:
代码语言:javascript复制beforeEach(() => {
cy.googleLogin('用户名iTesting', '密码');
});
注意:
cy.origin()需要升级Cypress到最新的9.6.0,截止本文发布时(2022/04/27),官方还没有release 9.6.0版本。
作者: Kevin Cai, 江湖人称蔡老师。 过期两性情感专家,非著名测试开发。 技术路线的坚定支持者,始终相信Nobody can be somebody。