کارگاه سرو قسمت 11: گزارش – قاس، اقدامات Github
یک آشفتگی
Qase یک ابزار مدیریت تست است که برای مستندسازی تست، اجرای تست، گزارش گیری و غیره استفاده می شود. در این آموزش، نحوه اتصال تست خود را در Cypress و فشار دادن نتایج به Qase یاد خواهید گرفت. این به داشتن یک گزارش برای آزمایش دستی و خودکار کمک می کند.
مراحل:
- به وب سایت قاس بروید و حساب کاربری ایجاد کنید: Qase
- پس از انجام این کار، یک پروژه جدید به نام کارگاه Cypress – فروشگاه کتاب ایجاد کنید
- در داخل پروژه یک مجموعه آزمایشی جدید ایجاد کنید و به عنوان مثال آن را وب نامید.
- در داخل مجموعه وب، همه موارد آزمایشی را که برای برنامه فروشگاه کتاب خودکار کردهایم ایجاد و مستند کنید. آنها را با تمام جزئیات و مراحل پر کنید. یک مورد آزمایشی اضافی به عنوان مثال برای ثبت نام ایجاد کنید، که بخشی از مجموعه اتوماسیون ما نیست، ما می خواهیم آن را به صورت دستی اجرا کنیم، اما هنوز باید در اینجا مستند شود.
- یک طرح آزمایشی جدید ایجاد کنید و به عنوان مثال آن را رگرسیون وب نامید. تمام موارد آزمایشی ما را در آنجا قرار دهید.
- به اجرای آزمایشی بروید و یک اجرای آزمایشی جدید ایجاد کنید که شامل تمام موارد آزمایشی از طرح آزمایشی است. اجرای آزمایشی را شروع کنید.
- به تنظیمات پروژه ← تنظیمات بروید و این دو نقطه را بررسی کنید تا امکان فشار انبوه نتایج سرو فراهم شود
- به صفحه api tokens بروید و یک توکن جدید ایجاد کنید. ارزش رمز را در جایی ذخیره کنید که بعداً به آن نیاز خواهیم داشت.
- به پروژه cypress خود بروید و در ترمینال این 3 دستور را برای نصب وابستگی برای Qase اجرا کنید:
npm install cypress-qase-reporter
npm install dotenv
npm install prompt
- در زیر پوشه اصلی پروژه، اسکریپت های پوشه جدید و فایل جدید ایجاد کنید
cypress-with-qase.js
- برای مدیریت اجرای آزمایشی با حروف بزرگ، کد زیر را در داخل آن بنویسید (در هر فرمان اجرای، مقادیر خاصی از پروژه و اجرا را درخواست میکند، و همچنین میپرسد کدام مجموعه اتوماسیون را میخواهید اجرا کنید. ما همچنین پیکربندی اولیه Cypress را ارائه میدهیم)
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,
},
});
}
);
- به فایل package.json بروید و دستور جدیدی برای اجرای Cypress با Qase اضافه کنید (خط 13):
{
"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 پروژه است (به عنوان مثال، در اینجا CWBS یک ID پروژه است و 1 ID case و غیره):
- به پروژه اتوماسیون خود برگردید و کلید API را از Qase که قبلاً دریافت کرده بودیم صادر کنید. به این صورت است که باید آن را بنویسید و در ترمینال اجرا کنید، اما کلید شما متفاوت است، کلید خود را از مرحله 8 در اینجا جایگذاری کنید:
export QASE_API_KEY=46b7d640b6841da28aea575cb6084141661976bcq
- هر مورد آزمایشی را ویرایش کنید تا شناسههای شما را از Qase درج کند، ما در اینجا اسناد را با اتوماسیون مطابقت میدهیم:
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'));
})
);
});
searchBookstore.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');
})
);
});
- در نهایت بیایید تست ها را اجرا کنیم. در ترمینال خود دستور execute را بنویسید
npm run cypress:run:qase
و به سوالات پاسخ دهید، شناسه پروژه، شناسه اجرا و مجموعه ای را که می خواهید اجرا کنید، در این مورد مجموعه کتابفروشی ارائه دهید.
وقتی به صفحه اجرای آزمایشی خود می روید، فقط در URL به عنوان آخرین شماره می توانید شناسه اجرا را پیدا کنید:
- پس از اتمام اجرای آزمایشی، میتوانید نتایج را در ترمینال و همچنین در برنامه Qase ببینید، فقط صفحه اجرای آزمایشی را در Qase بازخوانی کنید.
همانطور که می بینید تمام نتایج آزمایشات از Cypress در Qase بارگذاری می شود. ما فقط یک تست دستی برای اجرا داریم و می توانید آن را به صورت دستی در خود قاس اجرا کنید. که ما اجرای تست خودکار و دستی را با هم ترکیب کردیم.
اقدامات Github
GitHub Actions یک پلتفرم یکپارچه سازی و تحویل مداوم (CI/CD) است که به شما امکان می دهد خط لوله ساخت، آزمایش و استقرار خود را خودکار کنید. میتوانید گردشهای کاری ایجاد کنید که هر درخواست کششی را در مخزن شما ایجاد و آزمایش کند، یا درخواستهای کشش ادغامشده را در تولید مستقر کنید.
GitHub Actions فراتر از DevOps است و به شما اجازه می دهد تا زمانی که رویدادهای دیگری در مخزن شما اتفاق می افتد، گردش کار را اجرا کنید. به عنوان مثال، میتوانید یک گردش کار را اجرا کنید تا هر زمان که شخصی مشکل جدیدی در مخزن شما ایجاد کرد، برچسبهای مناسب را بهطور خودکار اضافه کنید.
GitHub ماشینهای مجازی لینوکس، ویندوز و macOS را برای اجرای گردشهای کاری شما فراهم میکند، یا میتوانید در مرکز داده یا زیرساخت ابری خود میزبان خود میزبانی کنید.
ℹ️ درباره Github Actions بیشتر بدانید: Github Actions
ℹ️ Github Actions + Cypress: GA و 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 بلافاصله اجرای آزمایشی را شروع می کند. می توانید آن را در مخزن ارائه سرو من که شامل تمام کارهای درس های قبلی است ببینید: پیوند
می توانید اجرای آزمایشی github خود را در زیر عملیات مشاهده کنید. هنگامی که جریان خاصی را باز می کنید، گزارش آن و تمام خروجی اجرای آزمایشی را خواهید دید.
در بالا مثالی از اجرای برنامه ریزی شده است که من برای شروع هر یکشنبه در ساعت 10 صبح UTC تنظیم کردم.
فراموش نکنید که تمام کارهایی را که امروز انجام دادید در Github فشار دهید 😉 دستورات git را به خاطر دارید؟
git add .
git commit -am "add: qase and github actions support"
git push
از اینکه این کارگاه را دنبال کردید متشکرم امیدوارم مفید بوده باشه برای هر سوالی، میدانید کجا میتوانید مرا پیدا کنید: لینکدین من
کد تکمیل شده این درس
اگر چیز جدیدی یاد گرفتید با خرید یک قهوه از کار من حمایت کنید ☕