作者简介:博主是一位测试管理者,同时也是一名对外企业兼职讲师。
主页地址:【Austin_zhai】
目的与景愿:旨在于能帮助更多的测试行业人员提升软硬技能,分享行业相关最新信息。
声明:博主日常工作较为繁忙,文章会不定期更新,各类行业或职场问题欢迎大家私信,有空必回。

阅读目录

  • 1. 接上回
  • 2. 自定义命令
    • 2.1 参数传递
    • 2.2 链式调用
    • 2.3 自定义断言
    • 2.4 处理异步操作
    • 2.5 Cypress对象
  • 3. 注意点
    • 3.1 关于脚本业务上下文
    • 3.2 抽象的程度

1. 接上回


 上一篇我们介绍了一些Cypress中的一些高频使用技巧,那么今天就由博主我继续来为大家带来关于Cypress的一些高阶技巧。

2. 自定义命令


 在Cypress中,自定义命令是一个强大的辅助功能,说直白点就是它允许你将重复使用的代码片段抽象成可重用的命令。而通过这些自定义的命令,我们可以让我们的自动化测试脚本更加的趋于模块化,可想而知的是,模块化的脚本其自身的可维护性、复用性和可阅读性就会更上一个台阶。

  要使用自定义命令,我们就需要在support/commands.js中建立自己的命令。比如我们需要将登录这个业务动作进行抽象,那就先编写一段登录的相关业务代码。

我们写一个十分简单的登录操作,语法如下,可以看到整个的业务代码十分的简单,只需要将用户名和密码进行传参即可。

Cypress.Commands.add('login', (username, password) => {cy.visit('/login');cy.get('#username').type(username);cy.get('#password').type(password);cy.get('button[type="submit"]').click();});

  那么我们在commands.js中将这段业务代码添加完成后,在实际的测试脚本中就可以直接对其进行使用。
使用起来是不是很方便,因为其本身就是将业务方法继续抽象,所以直接调用其方法名就可以达到登录代码同样的效果。

describe('login_test', () => {it('should log in successfully', () => {cy.login('your_username', 'your_password');});});

2.1 参数传递

  我们在定义业务方法的时候传参不仅仅可以传一些基础的业务参数,还可以在此基础上根据自己的业务场景来定义一些比较灵活的参数类别。比如我们在对特定元素进行业务操作时,我们可以统一的定义一个操作类或方法,来对此进行特定的传参,类似于selenium中find_elelment方法。

  我们先在commands.js中定义,这里我们要传递的参数是一个元素选择器。这样我们就可以灵活的在页面上选择到任何一个能捕捉到的元素。

Cypress.Commands.add('clickAndVerify', { prevSubject: 'element' }, (element, text) => {cy.wrap(element).click();cy.contains(text);});

  使用的时候只需要直接调用即可。

cy.get('.my-button').clickAndVerify('Clicked Button Text');

2.2 链式调用

  自定义命令毫无意外的也支持了链式写法,无疑这让我们在设计脚本的过程中可以更加灵活的应对各类复杂业务场景。

  同样的现在commands.js中定义,这里我们在返回get的时候进行了链式调用。

Cypress.Commands.add('login', (username, password) => {cy.visit('/login');cy.get('#username').type(username);cy.get('#password').type(password);cy.get('button[type="submit"]').click();return cy.get('.user-dashboard'); });

  使用的时候只需要直接调用即可。

cy.login('your_username', 'your_password').should('be.visible');

2.3 自定义断言

  同样的,既然可以进行抽象,我们也完全可以将断言的操作加进自定义命令,以验证特定的状态或条件,包括一些特殊的验证逻辑。

  commands.js中定义,断言元素存在切包含text。

Cypress.Commands.add('shouldBeVisibleAndContain', { prevSubject: 'element' }, (element, text) => {cy.wrap(element).should('be.visible').and('contain', text);});

  直接调用方法即可对元素进行断言。

cy.get('.my-element').shouldBeVisibleAndContain('Expected Text');

2.4 处理异步操作

  对于上一篇末尾处说到的异步操作处理,同样可以在自定义命令中进行抽象,其实在被测对象中异步操作是很常见的,比如等待某个条件成立后再继续执行后续的操作,类似的这种场景我们都可以在自定义命令中继续抽象和服用,以优化脚本的整体运行效率和维护性。

  在commands.js中定义,等待特定的条件后再执行后续的操作。

Cypress.Commands.add('waitForApiResponse', () => {cy.intercept('GET', '/api/data').as('apiCall');cy.wait('@apiCall');});

  调用,不再赘述。

cy.waitForApiResponse();

2.5 Cypress对象

  除了以上说的这些方法外,我们还可以将一些元素和值包装成Cypress对象,这样做的作用就是让这些抽象后的对象可以在自定义命令中使用更多的Cypress自带命令。

  在commands.js中定义,我们使用cy.wrap()将对象包装成Cypress对象,使用自带的日志命令。

Cypress.Commands.add('logAndDebug', (subject) => {cy.wrap(subject).debug().log('Subject:', subject);});

  调用,不再赘述。

cy.get('.my-element').logAndDebug();

3. 注意点


 我们在使用自定义命令的同时也需要注意一些特殊的情况与场景。

3.1 关于脚本业务上下文

  在自定义命令中,当然也存在着上下文的关系,我们要确保了解Cypress中命令的上下文,其中thisprevSubject 是特别觉有代表性的关键字。它们其实是允许你在自定义命令中引用和操作前一个命令的主体,就this这个来说,它在自定义命令中用于引用当前命令的上下文,对于一般的命令,它指向cy对象;对于一些带有{ prevSubject: 'element' }选项的命令,this则会指向前一个命令的主体,这个是需要大家注意的。

  下面我们来举两个例子:
 首先我们来看普通命令中的this,这里的this就是指向cy对象的。

Cypress.Commands.add('customCommand', function () {cy.log(this); });

  调用

cy.customCommand();

  而对面带有{ prevSubject: 'element' }的方法时,这里的this就像我之前说的那样,指向的是前一个命令的主体。简单点来说this指向前一个命令的subject,而cy.log(subject)里的就是前一个命令的主体。

Cypress.Commands.add('customCommandWithSubject', { prevSubject: 'element' }, function (subject) {cy.log(this); cy.log(subject); });

  调用

cy.get('.my-element').customCommandWithSubject();

  prevSubject 的用作为告诉cypress你的自定义命令期望前一个命令的主体作为传参,一般在多个自定义命令中共享同一个元素的场景中会频繁使用到。

  同理,这里我们对前一个命令的主体进行点击操作,所以使用prevSubject 来达到我们所想要的效果。

Cypress.Commands.add('customCommandWithSubject', { prevSubject: 'element' }, function (subject) {cy.wrap(subject).click(); });

  调用

cy.get('.my-element').customCommandWithSubject();

3.2 抽象的程度

  虽然在自定义命令中我们需要对要定义的方法进行抽象,但往往会有些同学在设计的过程中什么都想要,从而导致自己的自定义命令变得过度抽象,这些代码的可读性一般都比较差而且维护起来难度较大,无法适应被测对象界面中的需求更改与样式变更。

  这里我们就举一个过度抽象的例子,让大家了解适度和过度抽象的区别。

  我们先来看一下过度抽象的自定义命令,这里虽然方法中提供了一个登录的基本步骤,但它的步骤过于具体,这样会导致在测试用例中要添加其他的测试逻辑变得困难,本身自定义命令的本质就是用来大量复用的,这样就变得本末倒置了。所以这样的抽象程度限制了自定义命令的灵活性,使得它本身的价值变得可有可无。

Cypress.Commands.add('login', (username, password) => {cy.visit('/login');cy.get('#username').type(username);cy.get('#password').type(password);cy.get('button[type="submit"]').click();cy.get('.user-dashboard').should('be.visible');});

  调用

describe('Login Test', () => {it('should log in successfully', () => {cy.login('testuser', 'password123');});});

  那么接下来我们看一下什么是适度抽象的自定义命令,下面这段乍一看似乎与上面的没什么很大的区别,其实则不然。在这其中我们只保留的基本的登录操作,不进行过多的细化操作,说人话就是我们只把共通与大框架的部分保留了下来,一些根据业务不同而扩展或特定的操作则被丢弃掉了。这样我们就可以在测试用例中添加更多的具体步骤来适应各类业务测试场景的需求。

Cypress.Commands.add('basicLogin', (username, password) => {cy.visit('/login');cy.get('#username').type(username);cy.get('#password').type(password);cy.get('button[type="submit"]').click();});

  调用

describe('Login Test', () => {it('should log in successfully', () => {cy.basicLogin('testuser', 'password123');cy.get('.user-dashboard').should('be.visible');});});