Commit e9788739 authored by Oxana Šamanina's avatar Oxana Šamanina
Browse files

Merge branch 'api_tests' into 'master'

Api tests

See merge request !8
parents fd7997f1 2147326c
Pipeline #12596 failed with stages
in 7 minutes and 21 seconds
# Cypress demo
![Alt text](img/pyramid.png?raw=true "Cypress automation testing pyramid")
Let me introduce you our Cypress Automation Framework for unit, component, integration, api and e2e regression tests we are inventing as a presentation of our automation skills. 
Our framework is universal and easy configurable.
......@@ -40,26 +38,17 @@ Run tests with desktop viewport in Firefox
Jobs are run in parallel. We can add more jobs with different configuration here.
![Alt text](img/pipeline.png?raw=true "CI/CD pipeline")
## Reports
You can download HTML report including videos of failed tests from jobs artifacts.
![Alt text](img/report.png?raw=true "Cypress Mochawesome HTML report")
## Cypress dashboard (sorry-cypress)
In our repo we have predefined configuration for [sorry-cypress dashboard](https://sorry-cypress.dev/) which is a very interesting plugin in cypress community for live monitoring of executed test suites.
![Alt text](img/sorry.png?raw=true "Cypress dashboard")
![Alt text](img/sorry2.png?raw=true "Cypress dashboard")
## Code Coverage
The react app is instumented by us, so it allows us to properly follow the code coverage of it. You can download code coverage reports from job artifacts.
![Alt text](img/codecoverage.png?raw=true "Cypress code coverage of app")
## Installation
Use the npm to install all dependencies
......
......@@ -152,23 +152,6 @@
},
"favorited": false,
"favoritesCount": 0
},
{
"title": "10. article",
"slug": "new-year-2021-by-mk-8gxoyy",
"body": "new year 2021 by MK",
"createdAt": "2021-01-11T09:26:12.749Z",
"updatedAt": "2021-01-11T09:26:12.749Z",
"tagList": ["newyear21"],
"description": "new year 2021 by MK",
"author": {
"username": "manjeet99",
"bio": null,
"image": "https://static.productionready.io/images/smiley-cyrus.jpg",
"following": false
},
"favorited": false,
"favoritesCount": 0
}
],
"articlesCount": 10
......
export enum Endpoints {
ARTICLES_API = '*/articles*',
DELETE_ARTICLE_API = '/api/articles/**'
}
\ No newline at end of file
DELETE_ARTICLE_API = '/api/articles/**',
USER_API = '**/user',
ANY_PROFILE_API = '**/profiles/*',
LOGIN_API = '**/login',
ALL_USERS_API = '**/users'
}
/// <reference types="Cypress" />
export enum Urls {
LOGIN_PAGE = '/login',
SETTINGS_PAGE = '/settings',
REGISTER_PAGE = '/register'
}
export function randomString(len, charSet){
charSet = charSet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let randomString = '';
for (let i = 0; i < len; i++) {
let randomPoz = Math.floor(Math.random() * charSet.length);
randomString += charSet.substring(randomPoz,randomPoz+1);
}
return randomString;
export function randomString(len, charSet) {
charSet = charSet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let randomString = '';
for (let i = 0; i < len; i++) {
let randomTxt = Math.floor(Math.random() * charSet.length);
randomString += charSet.substring(randomTxt, randomTxt + 1);
}
return randomString;
}
......@@ -6,7 +6,8 @@ declare namespace Cypress {
getLoginToken(user: string): Chainable<void>;
login(): Chainable<void>;
registerUserIfNeeded(): Chainable<void>;
getByDataId(dataId: string): Chainable;
findByTestId(dataId: string): Chainable;
findByAllTestId(dataId: string): Chainable;
containsDataId(dataId: string): Chainable;
}
}
......@@ -32,7 +32,7 @@ Cypress.Commands.add('login', (user = Cypress.env('user')) => {
});
cy.visit('/');
cy.findAllByTestId('TEST_GLOBAL_FEED').should('be.visible');
cy.findByTestId('TEST_GLOBAL_FEED').should('be.visible');
});
export function getLoginToken(user = Cypress.env('user')) {
......@@ -59,4 +59,12 @@ Cypress.Commands.add('registerUserIfNeeded', (options = {}) => {
},
failOnStatusCode: false,
});
});
Cypress.Commands.add('urlValidation', (url) => {
cy.url({ timeout: 60000 }).should('contain', url);
});
Cypress.Commands.add('getByDataIdWithChild', (dataId, child) => {
cy.get(`[data-testid='${dataId}'] > ${child}`);
});
\ No newline at end of file
/// <reference path="../index.d.ts" />
const ARTICLE_TITLE = "TEST_ARTICLE_TITLE"
const ARTICLE_DESC = "TEST_ARTICLE_DESC"
const ARTICLE_BODY = "TEST_ARTICLE_BODY"
const ARTICLE_TAG = "TEST_ARTICLE_TAG"
const ARTICLE_ADD_BTN = "TEST_PUBLISH_ARTICLE"
const ARTICLE_DEL_BTN = "TEST_DELETE_ARTICLE"
const ARTICLE_TITLE = 'TEST_ARTICLE_TITLE';
const ARTICLE_DESC = 'TEST_ARTICLE_DESC';
const ARTICLE_BODY = 'TEST_ARTICLE_BODY';
const ARTICLE_TAG = 'TEST_ARTICLE_TAG';
const ARTICLE_ADD_BTN = 'TEST_PUBLISH_ARTICLE';
const ARTICLE_DEL_BTN = 'TEST_DELETE_ARTICLE';
class EditorPO {
fillArticleTitle(articleTitle: string) {
cy.findAllByTestId(ARTICLE_TITLE).type(articleTitle)
return this
}
fillArticleDesc(articleDesc: string) {
cy.findAllByTestId(ARTICLE_DESC).type(articleDesc)
return this
}
fillArticleBody(articleBody: string) {
cy.findAllByTestId(ARTICLE_BODY).type(articleBody)
return this
}
fillArticleTag(articleTag: string) {
cy.findAllByTestId(ARTICLE_TAG).type(articleTag)
return this
}
addArticle() {
cy.findAllByTestId(ARTICLE_ADD_BTN).click()
return this
}
deleteCurrentArticle() {
cy.findAllByTestId(ARTICLE_DEL_BTN).click()
return this
}
fillArticleTitle(articleTitle: string) {
cy.findByTestId(ARTICLE_TITLE).type(articleTitle);
return this;
}
fillArticleDesc(articleDesc: string) {
cy.findByTestId(ARTICLE_DESC).type(articleDesc);
return this;
}
fillArticleBody(articleBody: string) {
cy.findByTestId(ARTICLE_BODY).type(articleBody);
return this;
}
fillArticleTag(articleTag: string) {
cy.findByTestId(ARTICLE_TAG).type(articleTag);
return this;
}
addArticle() {
cy.findByTestId(ARTICLE_ADD_BTN).click();
return this;
}
deleteCurrentArticle() {
cy.findByTestId(ARTICLE_DEL_BTN).click();
return this;
}
}
export const Editor = new EditorPO();
/// <reference path="../index.d.ts" />
const ARTICLE_PREVIEW = 'TEST_ARTICLE_PREVIEW';
class FeedPO {
getAllArticles() {
return cy.findAllByTestId(ARTICLE_PREVIEW);
}
getArticlesCount() {
return this.getAllArticles().its('length')
}
getAllArticles() {
return cy.findAllByTestId(ARTICLE_PREVIEW);
}
getArticlesCount() {
return this.getAllArticles().its('length');
}
}
export const Feed = new FeedPO();
/// <reference path="../index.d.ts" />
const SIGNUP_BTN = "TEST_SIGN_UP_BTN"
const ARTICLE_ADD = "TEST_ADD_ARTICLE"
const SIGNUP_BTN = 'TEST_SIGN_UP_BTN';
const ARTICLE_ADD = 'TEST_ADD_ARTICLE';
const SETTINGS = 'TEST_SETTINGS';
import { Feed } from '../pageObjects/feed';
import { Settings } from '../pageObjects/settings';
import { Profile } from './profile';
class HeaderPO {
clickToSignUp() {
return cy.findAllByTestId(SIGNUP_BTN).click();
}
getUserName(user) {
return cy.contains(`${user}`);
}
clickOnMyAcc(user) {
this.getUserName(user).click();
return Profile;
}
getArticleBtn() {
return cy.findByTestId(ARTICLE_ADD);
}
clickToSignUp() {
return cy.findAllByTestId(SIGNUP_BTN).click();
}
getUserLink(username: string) {
return cy.findAllByTestId(`TEST_${username}`);
}
clickToAddArticle() {
return cy.findAllByTestId(ARTICLE_ADD).click();
}
clickToAddArticle() {
this.getArticleBtn().click();
return Feed;
}
getSettingsBtn() {
return cy.findByTestId(SETTINGS);
}
clickOnSettingsBtn() {
this.getSettingsBtn().click();
return Settings;
}
}
export const Header = new HeaderPO();
const EMAIL = 'TEST_EMAIL';
const PASSWD = 'TEST_PASSWD';
class SignInPO {
fillEmail(mail: string) {
cy.findByTestId(EMAIL).type(mail)
return this
}
fillPassword(password: string) {
cy.findByTestId(PASSWD).type(password, { log: false } );
return this
}
}
export const LogIn = new SignInPO();
\ No newline at end of file
/// <reference path="../index.d.ts" />
const TAG = "TEST_TAG_"
const TAG = 'TEST_TAG_';
class PopularTagsPO {
chooseNthTag(nth: number) {
return cy.containsDataId(TAG).eq(nth).click();
}
chooseNthTag(nth: number) {
return cy.containsDataId(TAG).eq(nth).click();
}
}
export const PopularTags = new PopularTagsPO();
const BIO = 'TEST_BIO';
class ProfilePage {
getBiography() {
return cy.findByTestId(BIO);
}
}
export const Profile = new ProfilePage();
/// <reference path="../index.d.ts" />
const USERNAME = "TEST_USERNAME"
const EMAIL = "TEST_EMAIL"
const PASSWORD = "TEST_PASSWORD"
const SUBMIT = "TEST_SUBMIT_BTN"
const ERROR_MSG = "TEST_ERROR_MSG"
const USERNAME = 'TEST_USERNAME';
const EMAIL = 'TEST_EMAIL';
const PASSWORD = 'TEST_PASSWORD';
const SUBMIT = 'TEST_SUBMIT_BTN';
const ERROR_MSG = 'TEST_ERROR_MSG';
class RegistrationFormPO {
fillUserName(username: string) {
cy.findByTestId(USERNAME).type(username);
return this;
}
fillUserName(username: string) {
cy.findAllByTestId(USERNAME).type(username)
return this
}
fillEmail(email: string) {
cy.findByTestId(EMAIL).type(email);
return this;
}
fillEmail(email: string) {
cy.findAllByTestId(EMAIL).type(email)
return this
}
fillPassword(password: string) {
cy.findByTestId(PASSWORD).type(password, { log: false });
return this;
}
fillPassword(password: string) {
cy.findAllByTestId(PASSWORD).type(password)
return this
}
signUpBtn() {
cy.findAllByTestId(SUBMIT).click()
return this
}
getErrorMsg(item: string) {
return cy.findAllByTestId(`TEST_ERROR_${item}`)
}
signUpBtn() {
cy.findByTestId(SUBMIT).click();
return this;
}
getErrorMsg(item: string) {
return cy.findByTestId(`TEST_ERROR_${item}`);
}
}
export const RegistrationForm = new RegistrationFormPO();
const BIO = 'TEST_BIO';
class SettingsPO {
getBioField() {
return cy.findByTestId(BIO);
}
fillBio(text: string) {
this.getBioField().type(text)
return this;
}
}
export const Settings = new SettingsPO();
/// <reference types="react-scripts" />
const apiUrl = Cypress.env('apiUrl');
// simple requests, no authentication required
describe('Simple api tests', () => {
// GIVEN I am unlogged user
// WHEN execute the API call
// THEN returns 10 articles
it('[CD-T4] Get a list of articles', () => {
cy.request('GET', `${apiUrl}/articles?limit=10&offset=0`).then((xhr) => {
expect(xhr.status).to.eq(200);
expect(xhr.body.articles.length).to.eq(10);
expect(xhr.body.articles[0].title).not.to.be.empty;
console.log(xhr.body.articles)
});
});
// GIVEN I am not registered user
// WHEN I execute the API without mandatory value
// THEN the request status code is 422: Unprocessable Entity and I can see 3 error messages
it('[CD-T5] Error when create new user with invalid value', () => {
cy.request({
method: 'POST',
url: `${apiUrl}/users`,
failOnStatusCode: false,
user: {
username: '',
email: 'test@test.test',
password: '1234',
},
}).then((xhr) => {
console.log(xhr.body.errors);
expect(xhr.status).to.eq(422);
expect(xhr.body.errors.username.length).to.eq(3);
cy.log('**Error message:** '+ xhr.body.errors.username[0])
cy.log('**Error message:** '+ xhr.body.errors.username[1])
cy.log('**Error message:** '+ xhr.body.errors.username[2])
});
});
});
import { randomString } from "../../support/helpers";
import {Header} from "../../support/pageObjects/header";
import {RegistrationForm} from "../../support/pageObjects/registration";
describe('Regress scenarios suite', () => {
beforeEach(() => {
cy.visit('/register')
})
//GIVEN Navigate into SignUp page
//WHEN fill in mandatory values
//AND click on Sign up button
//THEN new user is created
it('[CD-T12] Register new user', () => {
const user = randomString(10,"")
RegistrationForm.fillUserName(user)
.fillPassword(`${user}12345`)
.fillEmail(`${user}@cypress-demo.cz`)
.signUpBtn()
Header.getUserLink(user).should('exist')
})
//GIVEN Navigate into Home page
//WHEN click on Sign up button
//THEN new user is not created
it('[CD-T13] Register new user negative path', () => {
RegistrationForm.signUpBtn()
.getErrorMsg('email').should('exist')
RegistrationForm.getErrorMsg('password').should('exist')
RegistrationForm.getErrorMsg('username').should('exist')
})
})
......@@ -15,7 +15,7 @@ describe('Integration tests - check that articles are loaded correctly from fixt
fixture: 'articleList'
})
cy.visit('/')
Feed.getAllArticles().should('have.length', 10)
Feed.getAllArticles().should('have.length', 9)
})
//GIVEN I am a not logged user
......
/// <reference types="Cypress" />
import { Urls } from '../../support/constants/routs';
import { LogIn } from '../../support/pageObjects/login';
import { Endpoints } from '../../support/constants/endpoints';
const login = Endpoints.LOGIN_API;
const user = Cypress._.pick(Cypress.env('user'), 'email', 'password');
describe('Tests on login page', () => {
beforeEach(() => {
cy.visit(Urls.LOGIN_PAGE);
});
// GIVEN I am registered user on login page
// WHEN I fill valid e-mail and passworrd into the inputs AND submit the formular
// THEN I am loged in and I see my feed page
it('[CD-T6] Fill the login form', () => {
cy.intercept('POST', login).as('api');
LogIn.fillEmail(user.email).fillPassword(user.password);
cy.get('form').submit();
cy.wait('@api').its('response.statusCode').should('eq', 200);
cy.url().should('be.eq', Cypress.config().baseUrl + '/');
});
});
import { randomString } from "../../support/helpers";
import { Header } from "../../support/pageObjects/header";
import { RegistrationForm } from "../../support/pageObjects/registration";
import { Endpoints } from "../../support/constants/endpoints";
import { Urls } from "../../support/constants/routs";
const users = Endpoints.ALL_USERS_API;
const register = Urls.REGISTER_PAGE;
describe('Tests on register page', () => {
beforeEach(() => {
cy.visit(register)
})
// GIVEN I am not registered user on register page
// WHEN fill in mandatory values with valid data
// AND click on Sign up button
// THEN new user is created
it('[CD-T12] Register new user', () => {
const user = randomString(10,'')
cy.intercept('POST', users).as('users')
RegistrationForm
.fillUserName(user)
.fillPassword(`${user}12345`)
.fillEmail(`${user}@cypress-demo.cz`)
.signUpBtn()
cy.wait('@users')
Header.getUserName(user).should('exist')
})
// GIVEN I am not registered user on register page
// WHEN I click on Sign up button
// THEN new user is not created and I see error messages
it('[CD-T13] Register new user negative path', () => {
RegistrationForm
.signUpBtn()
.getErrorMsg('email').should('exist')
RegistrationForm.getErrorMsg('password').should('exist')
RegistrationForm.getErrorMsg('username').should('exist')
})
})
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment