Cypress 是一个工具, 它使得你的端对端测试写起来更快
对浏览器中运行的任何内容进行快速, 简单和可靠的测试
让我们来试一试, 并验证这是真的!
我们将把 Cypress 与我们的项目之一 --Eedi 集成在一起 Eedi 是英国教师学生及家长的绝佳教育平台关键是, 任何使用它的人, 在浏览时都有愉快而流畅的体验, 并且所有的功能都能按预期工作
配置
在我们应用程序的根目录下, 让我们添加 Cypress 作为 dev 依赖
$ yarn add --dev cypress
调整 package.json 中的 "scripts":
- "scripts": {
- ...
- "cypress:open": "cypress open"
- }
就像正常开发一样, 在本地运行服务, 然后在新的终端窗口中打开 Cypress:
$ yarn run cypress:open
过了一会儿, Cypress 应该打开了, 我们应该看到一个窗口弹出
在这里, 我们可以访问我们所有的测试, 甚至开箱即用
Cypress 已创建新的文件夹 cypress 与子文件夹 fixtures, integration 和 support 它还添加了一个空配置文件 cypress.json
由于我们经常访问我们的根路径, 因此将它抽象为配置文件是一种很好的做法打开 cypress.json 文件, 并添加一个带有键 baseUrl 和 url 的新条目作为值:
- {
- "baseUrl": "http://localhost:3000"
- }
在 example_spec.js 文件中, 我们可以看到'Kitchen Sink Tests', 当我们想要浏览一些常见的测试场景时可以派上用场但是让我们现在写我们自己的测试
测试登录
登录是任何应用程序最重要的功能之一如果做得不好, 用户将无法看到我们其它的工作, 并且再做其他事情就没有任何意义
创建一个新文件 login_spec.js 在这里, 我们将测试我们关于登录的所有逻辑
让我们写下我们的第一个测试, 让我们来检查一下 happy path 是否如预期一样工作:
- describe('Log In', () =>{
- it('succesfully performs login action', () =>{
- // 访问'baseUrl'
- cy.visit('/');
- // 断言我们是否处于好的位置 - 搜索'smarter world'
- cy.contains('smarter world');
- // 搜索带有'Teachers' 的 div, 并点击它
- cy.get('a[data-testid="main-link-teachers"]').click();
- // 检查 url 是否改变
- cy.url().should('includes', 'teachers');
- cy.contains('more time to teach');
- // 找到 Login 按钮并点击它
- cy.get('button[data-testid="menu-button-login"]').click();
- // 检查 url 是否改变
- cy.url().should('includes', '/login');
- // 提交输入表单并点击提交按钮
- cy.get('input[data-testid="login-form-username"]').type('test@email.com');
- cy.get('input[data-testid="login-form-password"]').type('password');
- cy.get('button[data-testid="login-form-submit"]').click();
- // 验证是否被重定向
- cy.url({
- timeout: 3000
- }).should('includes', '/c/');
- });
- });
现在, 请转到 Cypress 应用程序并选择我们刚刚创建的测试它应该在一个文件中运行所有的测试, 我们可以看到它们的表现如何:
在测试运行器的左侧窗格中, 我们可以看到 Cypress 执行的所有操作, 查找到的元素以及浏览器重定向的元素我们还可以使用漂亮的时间旅行功能, 并检查我们测试的每一步
让我们停下来! 修改测试的第 12 行:
cy.contains('Log In').click()
它失败了这很好, 我们已经确定 happy path 确实很 happyCypress 为我们提供了详细的堆栈跟踪 -- 发生了什么问题以及在哪里发生的问题
添加更多的用例:
不成功的登录操作应该会产生错误消息
未经授权的用户应该无法访问受限制的网址
- describe('Log In', () => {
- it('succesfully performs login action', () => {
- ...
- });
- it('displays error message when login fails', () => {
- // 直接转到登录路径
- cy.visit('/login');
- // 尝试使用不正确的凭证登录
- cy.get('input[data-testid="login-form-username"]').type('test@email.com');
- cy.get('input[data-testid="login-form-password"]').type('fail_password');
- cy.get('button[data-testid="login-form-submit"]').click();
- // 应该出现错误信息
- cy.contains('Something went wrong');
- });
- it('redirects unauthorized users', () => {
- // 转到受保护的路径
- cy.visit('/c');
- // 应该重定向到登录页面
- cy.url().should('contains', '/login');
- });
- });
我们保存测试文件之后, Cypress 应该重新运行所有的测试:
测试注销
下一个要覆盖的功能是注销操作我们希望确定该用户可以正确地从我们的应用程序注销听起来很简单, 对吧?
但是, 让我们再考虑一下... 为了注销, 我们需要先登录, 对吧? 我们是否应该重用先前测试的代码, 然后再添加更多逻辑? 听起来很傻, 我们是开发者, 我们可以做得更好!
Cypress 提供了另一个便利的功能 -- 命令它允许我们创建可以在任何测试中重用的自定义操作而且由于大多数场景应该为登录用户编写, 因此此操作是自定义命令的完美候选
打开位于 support 文件夹中的 commands.js 文件 Cypress 为我们提供了一些示例, 取消注释即可使用!
- // -- This is a parent command --
- // Cypress.Commands.add("login", (email, password) => { ... })
- //
使用我们的自定义行为来增强此登录命令, 但首先让我们考虑一下我们想要做什么
我们已经测试了登录, 不是吗? 所以, 我们接下来要写的每一个测试都是重复相同的步骤, 是没有意义的我们甚至可以阅读文档:
完全测试登录流程 - 但只有一次!
同样的:
在每次测试之前, 请勿使用您的用户界面登录
那我们能做什么呢?
我们可以使用 cy.request()直接向我们的后端服务请求登录, 然后像往常一样继续如下:
- Cypress.Commands.add('login', (email, password) => {
- // 向后端发出 POST 请求
- // 我们正在使用 GraphQL, 因此我们正在通过转变:
- cy
- .request({
- url: 'http://localhost:4000/graphql',
- method: 'POST',
- body: {
- query:
- 'mutation login($email: String!, $password: String!) {loginUser(email: $email, password: $password)}',
- variables: { email, password },
- },
- })
- .then(resp => {
- // 断言来自服务器的响应
- expect(resp.status).to.eq(200);
- expect(resp.body).to.have.property('data');
- // 我们所有的 private 路径都会检查存在 redux store 上的 auth token, 所以让我们把它传递到那里
- window.localStorage.setItem(
- 'reduxPersist:user',
- JSON.stringify({ refreshToken: resp.body.data.loginUser })
- );
- // 到仪表盘
- cy.visit('/c');
- });
- });
现在, 在每个测试中, 我们可以调用
cy.login('username','password')
, 并且它应该执行登录操作而不需要使用 UI
现在我们准备测试注销操作, 创建 logout_spec.js 并添加一些断言:
- const baseUrlMatcher = new RegExp('localhost:3000/$');
- describe('Log out user properly', () => {
- // 在每次测试前登录:
- beforeEach(() => {
- cy.login('test@email.com', 'password');
- });
- it('can select dropdown and perform logout action', () => {
- // 检查我们是否登录:
- cy.url().should('contains', '/c/');
- cy.get('div[data-testid="main-menu-settings"]').click();
- cy
- .get('.Popover-body ul li')
- .first()
- .click();
- cy.url().should('match', baseUrlMatcher);
- });
- it('/logout url should work as well', () => {
- cy.url().should('contains', '/c/');
- cy.visit('/log-out');
- cy.url().should('match', baseUrlMatcher);
- });
- it('should clear auth token from local storage', () => {
- cy.url().should('contains', '/c/');
- cy.visit('/logout');
- cy.url().should('match', baseUrlMatcher);
- const user = JSON.parse(window.localStorage.getItem('reduxPersist:user'));
- assert.isUndefined(user.token, 'refreshToken is undefined');
- });
- });
观察它们失败:
然后修改第 14 行和第 20 行 (将 first() 更改为 last(), 将
cy.visit('log-out')
更改为 cy.visit('logout')并观察测试如何通过:
TL;DR
总之, 用 Cypress 写测试真的很有趣
正如所宣称的, 配置几乎为零, 编写断言很简单, 感觉很自然, 而且 GUI 非常棒! 您可以进行时间旅行, 调试所有步骤, 并且因为它们都作为 Electron 应用程序启动, 所以我们甚至可以访问开发者工具以了解每个动作发生了什么
网络已经进化, 测试依旧会如此
让我们写一些测试吧, 愿原力与你同在!
来源: https://juejin.im/post/5aaa24caf265da23870e8f44