Cypress是建立在Mocha和Chai之上,因此同时支持Chai的BDD和TDD两种风格。如果你熟悉JavaScript风格的代码,那么在Cypress中写测试用例是很容易上手的。
Mocha是一款适用于Node.js和浏览器的测试框架,可使用异步测试变得简单灵活。
Cypress的测试风格继承于Mocha,提供了describe()、context()、it()、specify()四个关键字,对于一条可执行的测试而言,必须包含以下两个组成部分:
示例如下所示:
describe('我是一个测试集', () => {
it('测试用例-1', () => {
expect(1+2).to.eq(3)
});
it('测试用例-2', () => {
expect(3-2).to.eq(1)
});
it('测试用例-3', () => {
expect(3*2).to.eq(5)
});
});
最终的运行结果如下所示:

Hook中文常翻译为钩子函数,Cypress也提供了Hook(从Mocha继承而来)。这些Hook函数能够在运行每个测试用例或测试集之前,做一些准备操作,也可以每个测试用例或测试集运行完成之后执行一些操作。示例如下所示:
/// <reference types="cypress" />
before(()=>{
// 全局Hook
// 所有用例运行行前执行,但仅执行一次
cy.log("我是全局before Hook,所有测试用例运行前执行我,但仅执行一次")
});
beforeEach(()=>{
// 全局Hook
// 每个测试用例运行前执行
cy.log("我是全局beforeEach Hook,每个测试用例运行前执行我")
});
afterEach(()=>{
// 全局Hook
// 每个测试用例运行完成后执行
cy.log("我是全局afterEach Hook,每个测试用例运行完成后执行我")
});
after(()=>{
// 全局Hook
// 所有测试用例运行完成后执行,但仅执行一次
cy.log("我是全局after Hook,所有测试用例运行完成后执行,但仅执行一次")
});
describe('Test Hooks in TestSuite', () => {
before(()=>{
// 当前测试集Hook
// 当前测试集中,所有测试用例运行前执行,但仅执行一次
cy.log("我是当前测试集before Hook,所有测试用例运行前执行我,但仅执行一次")
});
beforeEach(()=>{
// 当前测试集Hook
// 当前测试集中,每个测试用例运行前执行
cy.log("我是当前测试集beforeEach Hook,每个测试用例运行前执行我")
});
afterEach(()=>{
// 当前测试集Hook
// 当前测试集中,每个测试用例运行完成后执行
cy.log("我是当前测试集afterEach Hook,每个测试用例运行完成后执行我")
});
after(()=>{
// 当前测试集Hook
// 当前测试集中,所有测试用例运行完成后执行,但仅执行一次
cy.log("我是当前测试集after Hook,所有测试用例运行完成后执行,但仅执行一次")
});
it('Test Hook in case-1', () => {
cy.log("我是测试用例-1");
cy.visit("https://www.baidu.com/",{timeout:10000});
});
it('Test Hook in case-2', () => {
cy.log("我是测试用例-2");
cy.visit("https://www.baidu.com/",{timeout:10000});
});
});
最终的运行结果如下所示:

从以上示例代码可以总结出来运行顺序如下所示:
before()是所有测试用例的统一前置动作,而before()在一个describe()内只会执行一次,其执行顺序在所有测试用例it()之前。after()是所有测试用例的统一后置动作,而after()在一个describe()内只会执行一次,其执行顺序在所有测试用例it()之后。示例代码如下所示:
/// <reference types="cypress" />
describe('', () => {
let baseUrl="https://example.cypress.io/todo";
before(()=>{
cy.log("所有用例运行前执行before,仅执行一次");
});
it('测试用例-1', () => {
cy.visit(baseUrl,{timeout:10000});
cy.get(".header input").should("have.class","new-todo");
});
it('测试用例-2', () => {
cy.visit(baseUrl,{timeout:10000});
cy.get('.new-todo').type('todo A{enter}').type('todo B{enter}');
cy.get('.todo-list li').should('have.length', 4);
});
after(()=>{
cy.log("所有用例运行完成后执行after,仅执行一次");
});
});
最终的运行结果如下所示:

beforeEach()是每个测试用例执行前的前置操作,即每个用例执行前都会执行一次。一个describe()有几个用例it()就会执行几次。afterEach()是每个测试用例执行后的后置操作,即每个用例执行完成后都会执行一次。一个describe()有几个用例it()就会执行几次。示例代码如下所示:
/// <reference types="cypress" />
describe('', () => {
let baseUrl="https://example.cypress.io/todo";
before(()=>{
cy.log("所有用例运行前执行before,仅执行一次");
});
beforeEach(()=>{
cy.log("每个用例执行前,均会执行一次beforeEach");
cy.visit(baseUrl,{timeout:10000});
});
it('测试用例-1', () => {
cy.get(".header input").should("have.class","new-todo");
});
it('测试用例-2', () => {
cy.get('.new-todo').type('todo A{enter}').type('todo B{enter}');
cy.get('.todo-list li').should('have.length', 4);
});
afterEach(()=>{
cy.log("每个用例执行完成后,均会执行afterEach");
});
after(()=>{
cy.log("所有用例运行完成后执行after,仅执行一次");
});
});
最终的运行结果如下所示:

通过学习Hook,我们可以把测试的前置和后置条件进行剥离,更好的编写和组织测试用例。在实际项目中,我们也需要仅运行指定的测试用例或跳过某些用例等。Cypress也提供相应的功能,一起来看看吧。
在Cypress提供该功能的是.only()。当使用.only()指定某个测试集/测试用例后,仅当指定了的测试集/测试用例才会运行,其他未指定的测试集/测试用例则不会运行。示例如下所示:
/// <reference types="cypress" />
describe.only('包含only', () => {
let baseUrl="https://example.cypress.io/todo";
before(()=>{
cy.log("所有用例运行前执行before,仅执行一次");
});
beforeEach(()=>{
cy.log("每个用例执行前,均会执行一次beforeEach");
cy.visit(baseUrl,{timeout:20000});
});
it('测试用例-1', () => {
cy.get(".header input").should("have.class","new-todo");
});
it('测试用例-2', () => {
cy.get('.new-todo').type('todo A{enter}').type('todo B{enter}');
cy.get('.todo-list li').should('have.length', 4);
});
afterEach(()=>{
cy.log("每个用例执行完成后,均会执行afterEach");
});
after(()=>{
cy.log("所有用例运行完成后执行after,仅执行一次");
});
});
describe('不包含only', () => {
let tempUrl="https://www.baidu.com";
beforeEach(()=>{
cy.visit(tempUrl);
});
it('不包含only测试用例-1', () => {
cy.get("#kw").type("Surpass{Enter}");
// cy.get("#kw").type("Surpass");
// cy.get("#su").click();
});
afterEach(()=>{
cy.log("每个用例执行完成后,均会执行afterEach");
});
});
最终的运行结果如下所示:

describe.only('包含only', () => {
let baseUrl="https://example.cypress.io/todo";
before(()=>{
cy.log("所有用例运行前执行before,仅执行一次");
});
beforeEach(()=>{
cy.log("每个用例执行前,均会执行一次beforeEach");
cy.visit(baseUrl,{timeout:20000});
});
it('测试用例-1', () => {
cy.get(".header input").should("have.class","new-todo");
});
it.only('测试用例-2', () => {
cy.get('.new-todo').type('todo A{enter}').type('todo B{enter}');
cy.get('.todo-list li').should('have.length', 4);
});
afterEach(()=>{
cy.log("每个用例执行完成后,均会执行afterEach");
});
after(()=>{
cy.log("所有用例运行完成后执行after,仅执行一次");
});
});
最终的运行结果如下所示:

在Cypress提供该功能的是.skip()。当使用.skip()指定某个测试集/测试用例后,则不会运行该测试集/测试用例,其他未指定的测试集/测试用例则会运行。示例如下所示:
/// <reference types="cypress" />
describe('包含only', () => {
let baseUrl="https://example.cypress.io/todo";
before(()=>{
cy.log("所有用例运行前执行before,仅执行一次");
});
beforeEach(()=>{
cy.log("每个用例执行前,均会执行一次beforeEach");
cy.visit(baseUrl,{timeout:20000});
});
it('测试用例-1', () => {
cy.get(".header input").should("have.class","new-todo");
});
it.only('测试用例-2', () => {
cy.get('.new-todo').type('todo A{enter}').type('todo B{enter}');
cy.get('.todo-list li').should('have.length', 4);
});
afterEach(()=>{
cy.log("每个用例执行完成后,均会执行afterEach");
});
after(()=>{
cy.log("所有用例运行完成后执行after,仅执行一次");
});
});
describe.skip('不包含only', () => {
let tempUrl="https://www.baidu.com";
beforeEach(()=>{
cy.visit(tempUrl);
});
it('不包含only测试用例-1', () => {
cy.get("#kw").type("Surpass{Enter}");
// cy.get("#kw").type("Surpass");
// cy.get("#su").click();
});
afterEach(()=>{
cy.log("每个用例执行完成后,均会执行afterEach");
});
});
最终的运行结果如下所示:

describe('顶层测试集', () => {
let baseUrl="https://example.cypress.io/todo";
before(()=>{
cy.log("所有用例运行前执行before,仅执行一次");
});
beforeEach(()=>{
cy.log("每个用例执行前,均会执行一次beforeEach");
cy.visit(baseUrl,{timeout:20000});
});
it('测试用例-1', () => {
cy.get(".header input").should("have.class","new-todo");
});
it.skip('测试用例-2', () => {
cy.get('.new-todo').type('todo A{enter}').type('todo B{enter}');
cy.get('.todo-list li').should('have.length', 4);
});
describe.skip('嵌套测试集', () => {
it('嵌套测试集用例-1', () => {
cy.log("测试嵌套用例集");
});
});
afterEach(()=>{
cy.log("每个用例执行完成后,均会执行afterEach");
});
after(()=>{
cy.log("所有用例运行完成后执行after,仅执行一次");
});
});
最终的运行结果如下所示:

从上面示例中可以看出,标记为被排除的测试集后,则该测试集所有测试用例均不被执行
在实际项目中,only()和skip()通常是结合着使用,这时我们要先看标记describe的是skip()还是only(),其规则如下所示:
前面学习如何排除和执行指定测试集/测试用例,但在一些实际项目中,在执行用例的时候,会根据某一个条件来动态执行测试用例。为了解决这个问题,Cyrpess也提供相应的功能,通过更改相应的测试配置来支持。其语法格式如下所示:
describe(name, config, fn)
context(name, config, fn)
it(name, config, fn)
specify(name, config, fn)
部分配置参数为只读,不支持修改,可参考官网:https://docs.cypress.io/guides/references/configuration##Test-Configuration
示例代码如下所示:
/// <reference types="cypress" />
describe('When not in Chrome',{browser:"!chrome"}, () => {
let baseUrl="https://example.cypress.io/todo";
it.only('Show warnings', () => {
cy.visit(baseUrl,{timeout:20000});
cy.get(".header input").should("have.class","new-todo");
});
it.only('测试用例-2', () => {
cy.visit(baseUrl,{timeout:20000});
cy.get('.new-todo').type('todo A{enter}').type('todo B{enter}');
cy.get('.todo-list li').should('have.length', 4);
});
});
在Chrome中运行时其结果如下所示:

下面的示例,将仅运行在指定的浏览器中,且指定的分辨率和环境变量将覆盖默认的值,如下所示:
/// <reference types="cypress" />
describe('When in Chrome',
{
browser:"chrome",
viewportWidth: 800,
viewportHeight: 600,
env: {
flag: true,
url: "https://www.surpassme.com",
},
},
() => {
let baseUrl="https://example.cypress.io/todo";
it.only('Show warnings', () => {
cy.visit(baseUrl,{timeout:20000});
cy.get(".header input").should("have.class","new-todo");
});
it.only('测试用例-2', () => {
cy.visit(baseUrl,{timeout:20000});
cy.get('.new-todo').type('todo A{enter}').type('todo B{enter}');
cy.get('.todo-list li').should('have.length', 4);
});
});
运行时其结果如下所示:

示例代码如下所示:
/// <reference types="cypress" />
describe('When in Chrome', () => {
let baseUrl="https://example.cypress.io/todo";
it.only('Show warnings',
{
retries: {
runMode: 3,
openMode: 2
}
},
() => {
cy.visit(baseUrl,{timeout:20000});
cy.get(".header input").should("have.class","new-todo");
});
it.only('测试用例-2', () => {
cy.visit(baseUrl,{timeout:20000});
cy.get('.new-todo').type('todo A{enter}').type('todo B{enter}');
cy.get('.todo-list li').should('have.length', 4);
});
});
在实际项目中,会遇到一种情况,就是多个用例的操作步骤、断言均一样,仅是输入和输出不一样。如果我还是一条数据写一个用例,则效率会非常低。此时,我们可以利用Cypress动态生成测试用例来高效完成。我们以登录为例,如下所示:
[
{
"title": "测试数据-1",
"customerName": "Surpass",
"telephone": "18812345678",
"email":"ceo@surpass.com"
},
{
"title": "测试数据-2",
"customerName": "kevin",
"telephone": "17808231169",
"email":"hr@surpass.com"
}
]
/// <reference types="cypress" />
import data from "./postdata.json"
describe("测试发送数据", () => {
let baseUrl="http://httpbin.org/forms/post";
beforeEach(()=>{
cy.visit(baseUrl)
});
data.forEach( item => {
it(item.title, () => {
cy.log(item.title,item.customerName,item.telephone)
cy.get(":nth-child(1) > label > input").type(item.customerName).should("contain.value",item.customerName);
cy.get("form > :nth-child(2) > label > input").type(item.telephone).should("contain.value",item.telephone);
cy.get("form > :nth-child(3) > label > input").type(item.email).should("contain.value",item.email);
});
})
})
运行时其结果如下所示:

如果大家对数据驱动比较了解的话,从上面的示例可以看到,是不是非常像?其最大价值为测试数据更改时,不影响测试代码。
Cypress支持两种风格的断言BDD (expect/should) 和TDD (assert),以下列举常见的断言:
cy.get('.todo-list li').should('have.length', 4);
cy.get("form").find("input").should("not.have.class","surpass");
cy.get("form > :nth-child(3) > label > input").should("contain.value","Surpass");
cy.get("button").should("contain.text","Submit order");
// 或
cy.get("button").should("not.contain","Surpass");
cy.get("button").should("be.visible");
cy.get("button").should("not.exist");
cy.get(":nth-child(4) > :nth-child(2) > label > input").click().should("be.checked");
cy.get(".surpass").should("have.css","line-surpass");
运行单个测试用例,可以获得到更好的性能。在Test Runner界面,点击单个JS文件即可,其操作方法如下所示:

运行所有测试用例,可以在Test Runner中单击运行Run x integration specs

可以通过筛选从而仅运行符合筛选条件的用例。

筛选条件中忽略大小写
在Cypress中,测试用例的运行状态可以分为4种状态:通过(Passed)、失败(Failed)、等待(Pending)、跳过(Skipped)。
Passed表示测试用例的运行状态为通过,没有出现断言失败的情况。如下所示:

Failed表示测试用例的运行状态为失败,有出现断言失败的情况。如下所示:

当一个测试用例没有想好怎么写时,我们可以写一些占位性质的用例,虽然Cypress不会运行,但却会让后续的用例状态处于Pending状态,如下所示:
/// <reference types="cypress" />
describe('测试Pending场景', () => {
it('未完成的测试用例')
it.skip('已经完成的用例', () => {
let url="http://httpbin.org/forms/post";
let customerName="Surpass",telephone="18812345678",email="ceo@surpass.com",textMsg="I Love Surpass",buttonText="Submit order";
cy.visit(url,{timeout:10000});
cy.get(":nth-child(1) > label > input").type(customerName).should("contain.value",customerName);
cy.get("form > :nth-child(2) > label > input").type(telephone).should("contain.value",telephone);
cy.get("form > :nth-child(3) > label > input").type(email).should("contain.value",email);
cy.get("textarea").type(textMsg).should("contain.value",textMsg);
cy.get("button").should("contain.text",buttonText)
});
xit("另一种测试用例",()=>{
expect(false).to.true;
});
});
运行时其结果如下所示:

以上三种情况的用例均会标识为Pending状态
如果用例编写人员故意使用以上三种方式中的其中一种方式来跳过执行用例,则Cypress将视为Pending Test
当一个测试用例本来要执行,但却因为某些原因而未能运行,则Cypress会将该用例的运行状态视为Skipped。我们在一个测试集里面添加beforeEach(),示例代码如下所示:
/// <reference types="cypress" />
describe('测试用例状态为Skipped场景', () => {
let baseUrl="https://example.cypress.io/todo";
beforeEach(()=>{
cy.visit(baseUrl);
});
it('hides footer initially', () => {
cy.get('.filters').should('be.exist');
});
it('adds 2 todos', () => {
cy.get('.new-todo').type('learn testing{enter}').type('be cool{enter}');
cy.get('.todo-list li').should('have.length', 4);
})
});
以上这种情况,所有测试用例能顺利执行并通过,如下所示:

我们修改以上代码,让beforeEach()访问的地址不存在,再来看看,如下所示:
/// <reference types="cypress" />
describe('测试用例状态为Skipped场景', () => {
let baseUrl="https://example.cypress.io/todo";
beforeEach(()=>{
cy.visit(baseUrl+"/does-not-exist");
});
it('hides footer initially', () => {
cy.get('.filters').should('be.exist');
});
it('adds 2 todos', () => {
cy.get('.new-todo').type('learn testing{enter}').type('be cool{enter}');
cy.get('.todo-list li').should('have.length', 4);
})
});
运行结果如下所示:

在上述示例代码中,用例adds 2 todos本来想运行,但却因为beforeEach出现问题,而未能运行,最终被标识为Skipped。
我们先来看看Test Runner的截图如下所示:

Cypress自带的Test Runner功能非常强大,允许在测试运行期间查看测试命令的执行情况,并监控在命令执行时,被测试程序所处的状态。其组成部分如下所示:
在上图标识为1的区域,用于展示测试用例运行成功、失败的数量、运行时间、禁用自动滚动和重新运行按钮等
在上图标识为2的区域,用于记录每个执行的命令。鼠标单击,可以在应用程序预览(标识为5的区域)中查看其对应的元素及其执行详细信息。
在上图标识为3的区域,通过该功能可以快速定位元素。如下所示:

在上图标识为4的区域,通过URL预览栏,可以查看被测试程序运行进所对应的URL地址。
在上图标识为5的区域,通过应用程序预览窗口,可以展示应用程序在测试运行中的实时状态
在上图标识为6的区域,通过视窗大小可以了解当前应用程序运行时的视窗大小。如下所示:

原文地址:https://www.jianshu.com/p/203049b059f4
本文同步在微信订阅号上发布,如各位小伙伴们喜欢我的文章,也可以关注我的微信订阅号:woaitest,或扫描下面的二维码添加关注:

很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
我想用ruby编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序
我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当
我有一个围绕一些对象的包装类,我想将这些对象用作散列中的键。包装对象和解包装对象应映射到相同的键。一个简单的例子是这样的:classAattr_reader:xdefinitialize(inner)@inner=innerenddefx;@inner.x;enddef==(other)@inner.x==other.xendenda=A.new(o)#oisjustanyobjectthatallowso.xb=A.new(o)h={a=>5}ph[a]#5ph[b]#nil,shouldbe5ph[o]#nil,shouldbe5我试过==、===、eq?并散列所有无济于事。
我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere
Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/
我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test
我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("
我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r
我在app/helpers/sessions_helper.rb中有一个帮助程序文件,其中包含一个方法my_preference,它返回当前登录用户的首选项。我想在集成测试中访问该方法。例如,这样我就可以在测试中使用getuser_path(my_preference)。在其他帖子中,我读到这可以通过在测试文件中包含requiresessions_helper来实现,但我仍然收到错误NameError:undefinedlocalvariableormethod'my_preference'.我做错了什么?require'test_helper'require'sessions_hel