在跟同学们的交流中,我也了解到, 原来除了国外优秀的公司(例如Adobe, 迪士尼,AutoDesk等等), 国内也有很多公司在尝试使用Cypress提升测试效率。
在Cypress中国群内、在公众号iTesting里,我每天都能看到大量关于Cypress的使用讨论和私下问询。这让我感到无比荣幸。(买了书的同学们,公众号回复你的微信号,拉你到Cypress中国群)。
除了日常推荐大家通过阅读我的书来解决日常Cypress使用问题外,我也一直在更新着我这边的Cypress知识图谱, 不夸张的说,目前我总结和实践下来知识点多达200多篇。
本着“雕琢自我,普惠他人”的原则,我决定在公众号iTesting上开设<你不知道的Cypress系列>专栏。此专栏目的是分享一些我自己趟过的坑,走过的弯路、以及在选型时抛弃了的实践。希望让大家在选用Cypress作为前端自动化测试框架方案时, 可以借鉴一下,避免再走我走过的弯路。
”
今天是<你不知道的Cypress系列>的第三篇 -- 是时候重构自己的思维了!
由于Selenium/WebDriver的“荼毒”, 当前在自动化过程中,很多不合理的操作,反而都变成了标准流程。
例如,要进行元素属性值比较,我们首先想到的就是先赋值,再比较。
再比如,自动化过程中,常常会陷入”条件测试(Conditional Testing)“的陷阱! 例如,我见过太多这样的case:”如果我点击了某button,如果弹出框没有出现,我执行A操作,如果出现,我执行B操作“。
由于这种“荼毒”,初次使用Cypress时, 大多数同学都会认为自己掉进坑里了!
下面我们来一个个分析:
(一)诡异的赋值
01
—
赋值不起作用
赋值操作是最常见的了,赋值最常用的场景是获取元素的某个属性供以后使用。
没接触过JavaScript的同学,在第一次写Cypress脚本时,一定会遇见如下问题:
代码语言:javascript复制describe('欢迎关注iTesting', function () {
it('你以为的赋值', function () {
// 定义变量那么,用来获取button的text是什么
let name
cy.visit('/login')
//关注公众号iTesting, 玩转Cypress
cy.get('button[type="submit"]').then(obj => {
name = obj.text()
// 第一次打印
cy.log('循环内有值', name)
})
// 第二次打印
cy.log('循环外没有值', name)
})
})
如上述代码所示,我定义了一个变量name,并尝试把submit button的text即“Submit” 赋值给name。 上面的代码看起来没有任何毛病,但是运行时,你会发现我第一次打印时有值, 但是二次打印时name的值是null。
02
—
赋值不起作用的原因
写惯了Python或Java的同学往往会卡在这里觉得莫名其妙。其实也就是同步执行和异步执行的差异了。
同步执行:
可以简单理解为,当你执行一个操作,在这个操作没有结果之前,其后续的操作不会执行。
异步执行:
可以简单理解为,当你执行一个操作后,其后续的操作可以立即执行, 当这个操作有结果后,再通过状态,通知或者回调来通知这个操作的调用方。
这个解释相当之不准确,不过也足够我们继续下面的内容了。 你如果感兴趣, 可以搜索同步、异步、阻塞、非阻塞来了解更多进程通信和系统调用的知识。
正常情况下,Python代码,Java代码就是同步执行的,JavaScript代码就是异步执行的。
了解了这一点,你就明白了,当执行到第13行时,name的值还没有被返回,所以打印不出来。
03
—
99%的情况都无须赋值
使用Selenium/WebDriver比较熟悉的同学,初次转到Cypress后,很容易就自无劝退:”Cypress好难用, 我还是用回Selenium/WebDriver“吧。
拿对元素属性值进行断言为例,大家很容易就沿用Selenium/WebDriver时代的旧思维,认为,必须先拿出元素的属性值赋给一个变量,然后在用这个变量跟给定的期望结果对比。实际上,根本无需如此!
在Cypress中,99%的操作都无须赋值!
下面分别举例:
Selenium/WebDriver
代码语言:javascript复制//获取元素的属性值,并比较
value = driver.find_element_by_id('kw').get_attribute('innerHTML')
assert value == "iTesting"
Cypress:
代码语言:javascript复制//获取元素的属性值,并比较
cy.get('#kw').should('have.text', 'iTesting')
就这么简单!
(二)Cypress命令是如何运行的?
01
—
先来看一个大家常常会犯的错误:
假设我们定义了一个自定义方法login,最后返回登录后的凭证:
代码语言:javascript复制// cypress/support/index.ts
Cypress.Commands.add('login', (username, password) => {
//各种代码实现登录
// 后返回登录凭证
return auth
})
然后在测试用例里,经常看到这样的使用方式:
代码语言:javascript复制const auth = cy.login("iTesting", "iTesting")
最后会发现auth的值是undefined,很多人百撕不得骑姐。
为什么?
这是因为Cypress命令在它们被调用时不会执行任何操作。它们会自我排队(“enqueue themselves”),最后在统一运行。
来看下官方的解释:
代码语言:javascript复制it('iTesting邀你看Cypres如何运行', () => {
cy.visit('/my/resource/path') // 啥也不干
// 关注iTesting,玩转Cypress
cy.get('.awesome-selector') // 还是啥也不干
.click() // 必须啥也不干
cy.url() // 别想了,就是不干
.should('include', '/hello') // 就在此刻,干!
})
// 好了,运行完了, 哦, 你真是一个快枪手。
// 事实上,所有的Cypress命令会被queue起来,直到所有命令被chain完毕。
// 然后Cypress开始按它们被queue的顺序开始运行。
这个就是Cypress的魔力。这就是为什么JavaScript是异步执行的,但是Cypress命令却能按照你的代码“顺序“执行的原因!
02
—
那么,知道了Cypress命令是如何运行的,再来看上面的登录例子,你就知道了,
代码语言:javascript复制const auth = cy.login("iTesting", "iTesting")
cy.login没有被执行,当然auth里就没有值了。
那么,如何才能确保cy.login被执行呢?
为了让你能够访问到Cypress命令执行的结果,Cypress提供了
代码语言:javascript复制.then()
.then是闭包的一个典型应用。(如果想深入理解,可以查下Promise)
所以,这下,我们知道要访问cy.login的返回值,我们应该这样用:
代码语言:javascript复制cy.login("iTesting", "iTesting").then( auth => {
const secret = auth
//剩余用到auth的代码。
})
这下,你就能愉快的使用Cypress命令的返回值了,不过也带来一个问题,就是代码层次比较深。。。
(三)拒绝条件测试
01
—
前面我提到了条件测试(Conditional Testing),实际上,条件测试常见常景如下:
代码语言:javascript复制1. 我想在元素存在或者不存在时,执行不同的操作。
2. 我的应用程序有A/B Testing,我需要测试到不同的分支。
为了实现这个功能,在Selenium/WebDriver编程中,我们大量使用if...else,我们以为我们Cover住这种情况了,结果我们就发现我们的测试会薛定谔成功:有时候执行能成功,有时候执行不成功, 在你不执行的时候你永远不知道到底执行能不能成功。
我们来看一个例子:
假设你的应用程序代码如下:
代码语言:javascript复制// 你的应用程序代码
// 定义一个随机时间
const random = Math.random() * 100
// 创建一个 <button> 元素
const btn = document.createElement('button')
//关注iTesting,玩转Cypress
// attach 这个元素到body上
document.body.appendChild(btn)
setTimeout(() => {
// 在random的时间内,给btn一个类属性。
btn.setAttribute('class', 'active')
}, random)
你的测试代码就会如下:
代码语言:javascript复制it('薛定谔的测试', () => {
// 多执行几遍,你发现,有时候btn的属性是active,有时候不是active。
cy.get('button').then(($btn) => {
if ($btn.hasClass('active')) {
// active时的代码
} else {
// 非active的代码
}
})
})
这也是Selenium/WebDriver被诟病的原因之一,不稳定!
02
—
为了避免这个情况,Cypress告诉你, 不要去做条件测试(Conditional Testing)!
Cypress说,既然你在测试,那么你就应该知道你的每一步下去,其结果是什么。如果你不能确定你的操作下去结果是什么,那么你就不是在测试!(没毛病吧)
相应的,你就要调整你的测试策略,尽量避免让自己的代码处于条件测试(Conditional Testing)下, 具体来说就一句话:
代码语言:javascript复制事先做一些操作,确保你的某个操作一定只有一个结果!
如何做到呢?别忘记,Cypress是运行在浏览器之内的,是跟你的应用程序运行在同一个生命周期的,你对你的应用程序有完全的控制权!
听起来很好,不过很可惜。这句话主要是对开发说的,对我们QA来说,用处不大(因为我们QA还是不知道改哪行代码啊!)。
不过,这里还是有一些原则, 比如:
代码语言:javascript复制1. A/B Testing, 可以根据AB的策略,构造出一定会走A逻辑的测试数据。
2. 判断元素在不在,一定可以根据业务知道你的什么操作,它一定会在。
总结
当你初次使用Cypress时,特别是当你是从Selenium/WebDrvier转到Cypress来时,你一定会感觉到不习惯。这是必然的。
当你遇见问题时,不妨尝试转换下思维,把老的思维模式抛弃掉,转入到Cypress的思维中来,毕竟,我们做测试是为了:
测试你的代码,而不是你的耐心!