协议
Qase是一种测试管理工具,用于测试文档,测试执行,报告等。在本教程中,您将学习如何在柏树中连接测试运行并将结果推到Qase。这将有一份报告进行手册和自动测试。
步骤:
- 访问Qase网站并创建帐户:Qase
- 一旦您创建一个新项目,称为赛普拉斯研讨会 - 书店
- 在项目内部创建一个新的测试套件,并将其称为Web。
- 在Web套件中,创建并记录了我们为书店应用程序自动化的所有测试用例。填写所有细节和步骤。例如,创建一个其他测试案例进行注册,这不是我们自动化套件的一部分,我们想手动执行它,但仍需要在此处记录。
- 创建一个新的测试计划,并将其称为Web回归。在那里包括我们所有的测试用例。
- 去测试运行,创建一个新的测试运行,其中包括测试计划中的所有测试用例。开始测试运行。
- 转到项目设置 - 并检查这两个点以允许大量推动赛普拉斯结果
- 转到API令牌页面并创建一个新的令牌。将令牌值保存在某个地方,我们以后需要它。
- 转到您的柏树项目并在终端执行这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等):
- 回到您的自动化项目,并从我们之前收到的Qase导出API密钥。这就是您应该写它并在终端执行的方式,但是您的密钥是不同的,请从步骤8粘贴在这里: :
export QASE_API_KEY=46b7d640b6841da28aea575cb6084141661976bcq
- 编辑每个测试案例以包括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:
- 一旦测试运行完成,您将能够在终端中查看结果,也可以在Qase App中看到Qase中的测试页面。
您可以看到所有测试用例结果都从柏树上载到Qase。我们只剩下一个手动测试案例即可执行,您可以在Qase本身中手动执行它。我们合并了自动化和手动测试执行。
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
您可以看到在操作下执行的GITHUB测试运行。当您打开一定的流程时,您将看到其日志和测试运行的所有输出。
上面是我设置的预定运行的示例,每个星期日在UTC上午10点开始。
不要忘记推动您今天在github-记得git命令上所做的一切吗?
git add .
git commit -am "add: qase and github actions support"
git push
感谢您遵循此研讨会。我希望这很有用。无论如何,您知道在哪里可以找到我:My LinkedIn
Completed code for this lesson
如果您学到了新知识,请随时购买咖啡来支持我的工作。