工具 – Cypress

介绍

Cypress 是一款 e2e 测试工具。每当我们写好一个组件或者一个页面之后,我们会想对整体做一个测试。

在不使用工具的情况下,我们会开启 browser,然后做一系列点击、滚动、填 form 等等交互,然后观察看看是否全部运行正常,这就是 e2e 测试。

而借助 Cypress,我们可以把这套测试流程写成代码封装起来。让它变成自动化测试。若以后代码修改了,我们就不需要人工测试(费劲)。

 

参考

YouTube – Cypress in a Nutshell

Docs – Get Started

 

Cypress 环境

Cypress 的环境是独立于我们程序的。我们甚至可以用 Cypress 测试别人的程序,比如测试 google.com

Cypress 基于 Node.js,只要 Node.js 就可以了,不需要 Webpack、Vite 之类的。

当然我们也可以把 Cypress 和我们的程序放到一块。它们也不冲突。

 

Get Started

create project

创建一个项目。这里我用 Vite TypeScript。你可以用其它的,Cypress 只要有 Node.js 就可以了。

写一个简单的页面

.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + TS</title>
  </head>
  <body>
    <form autocomplete="off">
      <input placeholder="First Name">
      <input placeholder="Last Name">
      <button>Submit</button>
      <p class="thank-you">Thank you! We have received your enquiry.</p>
    </form>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>
View Code

.scss

* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
  &:focus {
    outline: none;
  }
  button {
    border-width: 0;
  }
}

body {
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;

  form {
    min-width: 320px;

    display: flex;
    flex-direction: column;
    gap: 16px;

    input {
      border-radius: 4px;
      border: 1px solid black;
      padding: 16px;
    }

    button {
      border-radius: 4px;
      background-color: pink;
      color: red;
      padding: 20px;
      font-size: 20px;
      text-transform: uppercase;
      letter-spacing: 1px;
    }

    .thank-you {
      line-height: 1.5;

      &:not(.shown) {
        display: none;
      }
    }
  }
}
View Code

.ts

import './style.scss'

document.querySelector('form')!.addEventListener('submit', e => {
  e.preventDefault();
  document.querySelector('.thank-you')!.classList.add('shown');
});
View Code

运行

yarn run vite

效果

install cypress

yarn add cypress --dev

添加 cypress 到 tsconfig.json

"compilerOptions": {
  "types": [
    "cypress"
  ]
},

如果是 vite 的话,inclde 也需要添加 cypress folder

运行

yarn run cypress open

它会打开一个 App

进入 E2E Testing。

for 第一次,它会创建一些 folder and file

folder and file

选一个 browser 做测试

创建第一个 test file

文件的位置

write test

describe('test form', () => {
  it('common use', () => {
    cy.visit('https://192.168.1.152:4200/src/home/home.html'); // 访问我们的程序页面
    cy.get('.thank-you').should('not.be.visible'); // 检查 thank you message 必须是 hidden
    cy.get('input[placeholder="First Name"]').type('Derrick'); // 查找 input firstName 输入 'Derrick'
    cy.get('input[placeholder="Last Name"]').type('Yam');
    cy.get('button').click(); // 点击 submit
    cy.get('.thank-you').should('be.visible'); // 检查 thank you message 必须是 shown
  });
});

Cypress 的测试代码一般上都是 UI 操作。然后检查 UI 的变化。当然它也可以发 request 检查资料是否输入到 database 等等。

反正人怎么 test 它就怎么 test 就对了啦。

测试结果

它会一行一行跑,去操作 UI。并且检查 UI display 是否正确。

注: Cypress 的 event 都是模拟的,比如 click 它是通过 JavaScript 触发的。有一个插件 cypress-real-events 可以实现 real click 和 keyboard tab

 

常用招数

参考: Docs – Using Cypress FAQ 

base URL

去 cypress.config.ts 加上 baseUrl

import { defineConfig } from 'cypress';

export default defineConfig({
  e2e: {
    baseUrl: 'https://192.168.1.152:4200',
  },
});

测试文件就不需要写长长的 path 了

it('common use', () => {
  cy.visit('https://192.168.1.152:44200/src/module-e2e-test/form/accessor/accessor.html'); // before
  cy.visit('/src/module-e2e-test/form/accessor/accessor.html'); // after
});

specPattern

Cypress 默认测试文件的路径时 cypress/e2e/**/*.cy.{js,jsx,ts,tsx}

有时候我们更希望把 .cy.ts 和测试页面放到一块

可以这样配置

export default defineConfig({
  e2e: {
    specPattern: [
      'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
      'src/module-e2e-test/**/*.cy.{js,jsx,ts,tsx}',
    ],
  },
});

长这样

expect + equal & deep.equal

熟悉单元测试的人对 expect 应该很熟悉了。Cypress 也是有这些冬冬,不过语法和 Jasmine 有些出入。

eq、equal、equals 

expect(1).eq(1); // true
expect('Hello').equals('Hello'); // true
expect({}).equal({}); // false
expect(NaN).equal(NaN); // false
expect(0).equal(-0); // true
expect(null).equal(undefined); // false

equal 有多个别名,上面三个方法其实都是等价的。

equal 是 ===,不是 Object.is 哦。

deep.equal

expect({ name: 'Derrick' }).deep.equal({ name: 'Derrick' }); // true
expect({ name: 'Derrick' }).eqls({ name: 'Derrick' }); // true
expect({ name: 'Derrick' }).eql({ name: 'Derrick' }); // true

deep.equal 是比较正规的写法,另外 2 个是 alias,尽量不要用,它容易和 equal 混淆。

cy.log

想 debug Cypress 可以使用 cy.log

cy.visit('/src/module-e2e-test/form/accessor/accessor.html');
cy.log('debug...');

效果

query element and doing something / check with element

输入字到 input

cy.get('input').type('value');

check 是否可见

cy.get('.card').should('be.visible');
cy.get('.card').should('not.be.visible');

select parent and then select child

// 下面这样是错误的
const form = cy.get('.grandparent .parent .child form');
form.find('input[type="firstName"]').type('value');
form.find('input[type="lastName"]').type('value');

// 下面这样才是正确的,我也不清楚为什么,懒得去查
const formSelector = '.grandparent .parent .child form ';
cy.get(formSelector + 'input[type="firstName"]').type('value');
cy.get(formSelector + 'input[type="lastName"]').type('value');

delay on page load

有时候 page load 需要等一下才操作,不然容易报错。

before(() => cy.visit('/').wait(1000));

检查 after window.open,更复杂的可以参考这篇 Stack Overflow – Access a new window - cypress.io

cy.window().should('have.length', 2);

each & wrap

当 get multiple element 时可以用 each 遍历,each 之后可以用 cy.wrap 把 $el 变成 Cypress element,这样就可以操作了。

cy.get('.pricing-section-component .pricing .service-cta-component').each($el => {
  $el.addClass('dada'); // $el is jQuery element
  const nativeElement = $el.get(0); // get native element
  cy.wrap($el).click(); // wrap become cypress elemenet
});

native element

Cypress select element 用的是 jQuery,想拿到 native element 需要多一个 step

cy.get<HTMLInputElement>('input[name="text"]').then($input => {
  cy.log($input.get(0).validationMessage); // Please fill out this field.
});

.then + $input.get(0) = native element

它也经常搭配 .should + expect 使用

cy.get<HTMLInputElement>('input[name="text"]').should($input => {
  expect($input.get(0).validationMessage).equal('Please fill out this field.');
});

 

Vitest(Jasmine) 和 Cypress(Mocha) 的语法区别

Vitest 是 beforeAll,Cypress 是 before

Vitest 是 toBe, Cypress 是 equal (而且前者是 Object.is,后者是 ===)

 

Cypress tsconfig

当程序和测试都在一个项目里时,可能 tsconfig 不一致,那么可以通过上面这个方法来做 override。

目前有一个 Bug – TypeScript 5 support for sourceMap option 我们必须得这么做才行。

 

posted @ 2023-08-02 12:35  兴杰  阅读(280)  评论(0编辑  收藏  举报