柏树研讨会第11部分:报告 - Qase,GitHub动作
#javascript #教程 #cypress #测试

协议

Qase是一种测试管理工具,用于测试文档,测试执行,报告等。在本教程中,您将学习如何在柏树中连接测试运行并将结果推到Qase。这将有一份报告进行手册和自动测试。

步骤:

  • 访问Qase网站并创建帐户:Qase
  • 一旦您创建一个新项目,称为赛普拉斯研讨会 - 书店

Image description

  • 在项目内部创建一个新的测试套件,并将其称为Web。
  • 在Web套件中,创建并记录了我们为书店应用程序自动化的所有测试用例。填写所有细节和步骤。例如,创建一个其他测试案例进行注册,这不是我们自动化套件的一部分,我们想手动执行它,但仍需要在此处记录。

Image description

Image description

  • 创建一个新的测试计划,并将其称为Web回归。在那里包括我们所有的测试用例。

Image description

  • 去测试运行,创建一个新的测试运行,其中包括测试计划中的所有测试用例。开始测试运行。

Image description

  • 转到项目设置 - 并检查这两个点以允许大量推动赛普拉斯结果

Image description

  • 转到API令牌页面并创建一个新的令牌。将令牌值保存在某个地方,我们以后需要它。

Image description

Image description

  • 转到您的柏树项目并在终端执行这3个命令,以安装Qase的依赖项:

npm install cypress-qase-reporter

npm install dotenv

npm install prompt

  • 在项目的根目录下创建新文件夹脚本和新文件cypress-with-qase.js
  • 在内部编写以下代码以管理带有情况的测试运行(每个运行提示都会要求Qase的特定值项目和运行值,它也会要求您要运行哪个自动化套件。我们还提供基本柏树配置)
require('dotenv').config();
const prompt = require('prompt');
const cypress = require('cypress');

prompt.start();
prompt.get(
    [
        {
            name: 'PROJECT_ID',
            description: 'Provide Qase PROJECT_ID',
            type: 'string',
            required: true,
        },
        {
            name: 'RUN_ID',
            description: 'Provide RUN_ID for the Qase test run',
            type: 'number',
            required: true,
        },
        {
            name: 'SUITE',
            description: 'Provide SUITE folder path for the Qase test run',
            type: 'string',
            required: true,
        },
    ],
    function (err, result) {
        cypress.run({
            spec: `cypress/e2e/${result.SUITE}/*.cy.js`,
            browser: 'chrome',
            reporter: 'cypress-qase-reporter',
            headed: true,
            reporterOptions: {
                apiToken: process.env.QASE_API_KEY,
                projectCode: result.PROJECT_ID,
                runId: result.RUN_ID,
                logging: true,
            },
        });
    }
);
  • 转到包装。
{
  "name": "cypress-workshop",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "cypress-cli-prod": "cypress open --env prod=1",
    "cypress-headed-prod": "cypress run --headed -b chrome --env prod=1",
    "cypress-headless-prod": "cypress run --headless -b chrome --env prod=1",
    "cypress-cli-staging": "cypress open",
    "cypress-headed-staging": "cypress run --headed -b chrome",
    "cypress-headless-staging": "cypress run --headless -b chrome",
    "cypress:run:qase": "QASE_REPORT=1 node scripts/cypress-with-qase.js",
    "eslint": "eslint cypress",
    "eslint-fix": "eslint cypress --fix"
  },
  "author": "",
  "license": "ISC",
  "husky": {
    "hooks": {
      "pre-commit": "npm run eslint-fix"
    }
  },
  "devDependencies": {
    "cypress": "^10.0.0",
    "eslint": "^8.16.0",
    "eslint-config-airbnb": "^19.0.4",
    "eslint-config-prettier": "^8.5.0",
    "eslint-plugin-chai-friendly": "^0.7.2",
    "eslint-plugin-cypress": "^2.12.1",
    "eslint-plugin-import": "^2.26.0",
    "eslint-plugin-jsx-a11y": "^6.5.1",
    "eslint-plugin-prettier": "^4.0.0",
    "eslint-plugin-react": "^7.30.0",
    "husky": "^8.0.1",
    "prettier": "^2.6.2"
  },
  "dependencies": {
    "cypress-file-upload": "^5.0.8",
    "cypress-qase-reporter": "^1.4.2-alpha.2",
    "dotenv": "^16.0.1",
    "prompt": "^1.3.0"
  }
}
  • 现在,返回Qase应用,找出每个测试用例的ID。它是项目ID旁边的一个数字(例如,CWBS是一个项目ID,而1个是情况ID等):

Image description

  • 回到您的自动化项目,并从我们之前收到的Qase导出API密钥。这就是您应该写它并在终端执行的方式,但是您的密钥是不同的,请从步骤8粘贴在这里:

export QASE_API_KEY=46b7d640b6841da28aea575cb6084141661976bcq

  1. 编辑每个测试案例以包括Qase中的ID,我们将文档与自动化匹配:

addbookToprofile.cy.js:

/// <reference types="Cypress" />

import { qase } from 'cypress-qase-reporter/dist/mocha';
import { bookActions } from '../../support/bookstore_page_objects/book_store';
import { navigateTo } from '../../support/bookstore_page_objects/navigation';

describe('Collections: Add Book To Collection', () => {
  // Perform login
  beforeEach('Perform login', () => {
    cy.createUser();
    cy.generateToken();
  });

  // Delete user
  afterEach('Delete user', () => {
    cy.deleteUser();
  });

  qase(
    1,
    it('Check adding book to profile collection', () => {
      // Navigate to book store
      navigateTo.bookStore();
      // Load books fixture
      cy.fixture('books').then((books) => {
        // Add first books to collection
        bookActions.addBookToCollection(books.collection1.Git);
        // Handle alert and verify alert message
        cy.verifyWindowAlertText(`Book added to your collection.`);
        // Navigate to user profile and verify that book is in collection table
        navigateTo.profile();
        cy.get('.rt-tbody').find('.rt-tr-group').first().should('contain', books.collection1.Git);
      });
    })
  );
});

Checkbookinfo.cy.js:

/// <reference types="Cypress" />

import { qase } from 'cypress-qase-reporter/dist/mocha';
import { bookActions } from '../../support/bookstore_page_objects/book_store';
import { profileActions } from '../../support/bookstore_page_objects/profile';
import { navigateTo } from '../../support/bookstore_page_objects/navigation';

describe('Collections: Check Book Info', () => {
  // Perform login
  beforeEach('Perform login', () => {
    cy.createUser();
    cy.generateToken();
  });

  // Add book to book collection
  beforeEach('Add book to profile collection', () => {
    navigateTo.bookStore();
    cy.fixture('books').then((books) => {
      bookActions.addBookToCollection(books.collection1.DesignPatternsJS);
      cy.verifyWindowAlertText(`Book added to your collection.`);
    });
  });

  // Delete user
  afterEach('Delete user', () => {
    cy.deleteUser();
  });

  qase(
    2,
    it('Check book info from profile table', () => {
      // Navigate to user profile
      navigateTo.profile();
      // Load books fixture
      cy.fixture('books').then((books) => {
        // Click on book in collection to open book info
        profileActions.checkBookData(books.collection1.DesignPatternsJS);
      });
      // Define book info elements
      const bookDataElements = [
        '#ISBN-label',
        '#title-label',
        '#subtitle-label',
        '#author-label',
        '#publisher-label',
        '#pages-label',
        '#description-label',
        '#website-label',
      ];
      // Check book info elements
      cy.elementVisible(bookDataElements);
      // Define data about the book
      const bookData = [
        '9781449331818',
        'Learning JavaScript Design Patterns',
        `A JavaScript and jQuery Developer's Guide`,
        'Addy Osmani',
        `O'Reilly Media`,
        '254',
      ];
      // Check data about the book
      cy.textExists(bookData);
    })
  );
});

deletebookfromprofile.cy.js:

/// <reference types="Cypress" />

import { qase } from 'cypress-qase-reporter/dist/mocha';
import { bookActions } from '../../support/bookstore_page_objects/book_store';
import { profileActions } from '../../support/bookstore_page_objects/profile';
import { navigateTo } from '../../support/bookstore_page_objects/navigation';

describe('Collections: Delete Book From Collection', () => {
  // Perform login
  beforeEach('Perform login', () => {
    cy.createUser();
    cy.generateToken();
  });

  // Add book to collection
  beforeEach('Add book to profile collection', () => {
    navigateTo.bookStore();
    cy.fixture('books').then((books) => {
      bookActions.addBookToCollection(books.collection1.SpeakingJS);
      cy.verifyWindowAlertText(`Book added to your collection.`);
    });
  });

  // Delete user
  afterEach('Delete user', () => {
    cy.deleteUser();
  });

  qase(
    3,
    it('Check deleting book from profile collection - confirm deletion', () => {
      cy.fixture('books').then((books) => {
        // Navigate to user profile
        navigateTo.profile();
        // Check if book is in the collection table
        cy.get('.rt-tbody')
          .find('.rt-tr-group')
          .first()
          .should('contain', books.collection1.SpeakingJS);
        // Delete book from table - confirm deletion
        profileActions.deleteBookFromTable(books.collection1.SpeakingJS, 'ok');
        // Handle delete alert and verify message
        cy.verifyWindowAlertText(`Book deleted.`);
        // Verify that book is no longer in collection table and that table is empty
        cy.get('.rt-tbody').should('not.contain', books.collection1.SpeakingJS);
        cy.get('.rt-noData').should('contain', 'No rows found').should('be.visible');
      });
    })
  );

  qase(
    6,
    it('Check deleting book from profile collection - decline deletion', () => {
      cy.fixture('books').then((books) => {
        // Navigate to user profile
        navigateTo.profile();
        // Check if book is in the collection table
        cy.get('.rt-tbody')
          .find('.rt-tr-group')
          .first()
          .should('contain', books.collection1.SpeakingJS);
        // Cancel book deletion
        profileActions.deleteBookFromTable(books.collection1.SpeakingJS, 'cancel');
        // Verify that book is still in the table
        cy.get('.rt-tbody').should('contain', books.collection1.SpeakingJS);
      });
    })
  );
});

login.cy.js:

/// <reference types="Cypress" />

import { qase } from 'cypress-qase-reporter/dist/mocha';
import { auth } from '../../support/bookstore_page_objects/auth';
import { navigateTo } from '../../support/bookstore_page_objects/navigation';

describe('Auth: Login user', () => {
  // Navigate to login page
  beforeEach('Navigate to Login page', () => {
    navigateTo.login();
  });
  qase(
    7,
    it('Check valid user credentials', () => {
      // Load users fixture
      cy.fixture('users').then((users) => {
        // Perform login
        auth.login(users.user2.username, users.user2.password);
      });
      // Verify that user is redirected to profile page (user is logged in)
      cy.url().should('contain', Cypress.env('profile'));
    })
  );

  qase(
    8,
    it('Check invalid user credentials', () => {
      // Perform login
      auth.login('invalid345', 'invalid345');
      // Verify that user is still on login page (user is not logged in)
      cy.url().should('contain', Cypress.env('login'));
      // Verify that error message is displayed
      cy.get('#output').should('contain', 'Invalid username or password!');
    })
  );

  qase(
    9,
    it('Check login with invalid username and valid password', () => {
      // Load users fixture
      cy.fixture('users').then((users) => {
        // Perform login
        auth.login('invalid345', users.user2.password);
      });
      // Verify that user is still on login page (user is not logged in)
      cy.url().should('contain', Cypress.env('login'));
      // Verify that error message is displayed
      cy.get('#output').should('contain', 'Invalid username or password!');
    })
  );

  qase(
    10,
    it('Check login with valid username and invalid password', () => {
      // Load users fixture
      cy.fixture('users').then((users) => {
        // Perform login
        auth.login(users.user2.username, 'invalid345');
      });
      // Verify that user is still on login page (user is not logged in)
      cy.url().should('contain', Cypress.env('login'));
      // Verify that error message is displayed
      cy.get('#output').should('contain', 'Invalid username or password!');
    })
  );
});

logout.cy.js:

/// <reference types="Cypress" />

import { qase } from 'cypress-qase-reporter/dist/mocha';
import { auth } from '../../support/bookstore_page_objects/auth';
import { navigateTo } from '../../support/bookstore_page_objects/navigation';

describe('Auth: Log out user', () => {
  // Perform login
  beforeEach('Perform login', () => {
    cy.createUser();
    cy.generateToken();
  });

  // Delete user
  afterEach('Delete user', () => {
    cy.deleteUser();
  });

  qase(
    11,
    it('Check logging out user', () => {
      // Navigate to user profile
      navigateTo.profile();
      // Perform log out
      auth.logout();
      // Assert that user is on login page
      cy.url().should('contain', Cypress.env('login'));
    })
  );
});

搜索bookstore.cy.js:

/// <reference types="Cypress" />

import { qase } from 'cypress-qase-reporter/dist/mocha';
import { bookActions } from '../../support/bookstore_page_objects/book_store';
import { navigateTo } from '../../support/bookstore_page_objects/navigation';

describe('Bookstore: Search For Book', () => {
  // Perform login
  beforeEach('Perform login', () => {
    cy.createUser();
    cy.generateToken();
  });

  // Delete user
  afterEach('Delete user', () => {
    cy.deleteUser();
  });

  qase(
    12,
    it('Check searching for existing book in book store', () => {
      // Navigate to bookstore
      navigateTo.bookStore();
      // Load books fixture
      cy.fixture('books').then((books) => {
        // Perform book search
        bookActions.searchCollection(books.collection1.DesignPatternsJS);
        // Verify that there is a book in filtered table (in search result)
        cy.get('.rt-tbody')
          .find('.rt-tr-group')
          .first()
          .should('contain', books.collection1.DesignPatternsJS);
      });
    })
  );

  qase(
    13,
    it('Check searching for non-existing book in book store', () => {
      // Define invalid book name
      const invalid_book_name = 'Game of Thrones';
      // Navigate to bookstore
      navigateTo.bookStore();
      // Perform book search
      bookActions.searchCollection(invalid_book_name);
      // Assert that there are no search results (no book in the table and table is empty)
      cy.get('.rt-tbody').should('not.contain', invalid_book_name);
      cy.get('.rt-noData').should('contain', 'No rows found').should('be.visible');
    })
  );
});
  • 最终让S进行测试。在您的终端写执行命令中

npm run cypress:run:qase

并回答问题,提供项目ID,运行ID和您要运行的套件,在这种情况下,书店套件。

导航到测试运行页面时,您只能在URL中找到url的运行ID:

Image description

Image description

  • 一旦测试运行完成,您将能够在终端中查看结果,也可以在Qase App中看到Qase中的测试页面。

Image description

Image description

您可以看到所有测试用例结果都从柏树上载到Qase。我们只剩下一个手动测试案例即可执行,您可以在Qase本身中手动执行它。我们合并了自动化和手动测试执行。

Image description

Image description

github动作

github操作是连续集成和连续交付(CI/CD)平台,可自动化构建,测试和部署管道。您可以创建工作流程,以建立和测试每个拉的请求到您的存储库,也可以将合并后的拉请请求部署到生产中。

github动作超越了DevOps,可以在存储库中发生其他事件时运行工作流程。例如,您可以在某人在存储库中创建新问题时运行工作流程以自动添加适当的标签。

GitHub提供Linux,Windows和MacOS虚拟机来运行工作流程,或者您可以在自己的数据中心或云基础架构中托管自己的自托管跑步者。

- ¹了解有关github动作的更多信息:Github Actions

âKIthubAction + Cypress:GA and Cypress

您可以根据您的项目需求自定义CI/CD管道。在此演示中,我们将创建一个示例github操作,该操作将使我们能够执行一些简单的操作:

  • 安排Cron Job在每个星期日上午10点在UTC进行柏树测试
  • 在每次推动到主分支上运行柏树测试

柏树测试运行作业将包括以下内容:

  • 定义测试将运行的容器和机器
  • 安装依赖项
  • 在Chrom上以默认无头模式运行所有测试

那么,我们如何写这个?

  • 在根目录中创建一个新文件夹.github。
  • 在.github下创建一个新的文件夹工作流
  • 创建一个新文件main.yml

在内部写下以下内容(最好复制粘贴,因为此文件对空格敏感等):

name: Cypress Tests

on:
  schedule:
  #schedule at 10:00 on Sunday
  - cron: '0 10 * * sun'
  push:
    branches:
      - main
jobs:
  cypress-run:
    runs-on: ubuntu-latest
    container: cypress/browsers:node12.18.3-chrome87-ff82
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Install dependencies
        run: npm install    

      - name: 'Run all tests'
        uses: cypress-io/github-action@v4
        with:
          # we have already installed all dependencies above
          install: false
          wait-on: 'https://demoqa.com'
          wait-on-timeout: 120
          browser: chrome
          spec: cypress/e2e/**/*

将此文件推到远程GITHUB存储库后,GitHub将立即开始测试运行。您可以在我的Cypress演示文稿存储库中看到它,其中包含以前课程的所有作品:link

Image description

您可以看到在操作下执行的GITHUB测试运行。当您打开一定的流程时,您将看到其日志和测试运行的所有输出。

Image description

Image description

上面是我设置的预定运行的示例,每个星期日在UTC上午10点开始。

不要忘记推动您今天在github-记得git命令上所做的一切吗?

git add .

git commit -am "add: qase and github actions support"

git push

感谢您遵循此研讨会。我希望这很有用。无论如何,您知道在哪里可以找到我:My LinkedIn

Completed code for this lesson

如果您学到了新知识,请随时购买咖啡来支持我的工作。

Buy Me A Coffee