Commit 90281f30 authored by Oxana Šamanina's avatar Oxana Šamanina
Browse files

Merge branch 'registration_scenarios' into 'master'

smoke and regress suite added also with registration test

See merge request !7
parents f934fb1d 90b02f07
Pipeline #12545 passed with stages
in 11 minutes and 15 seconds
import '@testing-library/cypress/add-commands';
const apiUrl = Cypress.env('apiUrl')
import "@testing-library/cypress/add-commands";
const apiUrl = Cypress.env('apiUrl');
declare global {
namespace Cypress {
interface Greeting {
greeting: string;
name: string;
}
interface Chainable {
/**
* Yields "foo"
*
* @returns {typeof foo}
* @memberof Chainable
* @example
* cy.foo().then(f = ...) // f is "foo"
*/
foo: typeof foo;
foo2: typeof foo2;
getLoginToken(user: string): Chainable<void>;
login(): Chainable<void>;
registerUserIfNeeded(): Chainable<void>;
getByDataId(dataId: string): Chainable<Element>;
/**
* Yields sum of the arguments.
*
* @memberof Cypress.Chainable
*
* @example
* ```
* cy.sum(2, 3).should('equal', 5)
* ```
*/
sum: (a: number, b: number) => Chainable<number>;
/**
* Example command that passes an object of arguments.
* @memberof Cypress.Chainable
* @example
* ```
* cy.greeting({ greeting: 'Hello', name: 'Friend' })
* // or use defaults
* cy.greeting()
* ```
*/
greeting: (options?: Greeting) => void;
}
}
interface Chainable {
getLoginToken(user: string): Chainable<void>;
login(): Chainable<void>;
registerUserIfNeeded(): Chainable<void>;
getByDataId(dataId: string): Chainable;
containsDataId(dataId: string): Chainable;
}
}
/**
* An example function "getByDataId()"
*
* @returns {string} "foo"
* @example
* foo() // "foo"
*/
export function getByDataId(dataId: string) {
return cy.get(`[data-testid='${dataId}']`)
}
/**
* Uses another custom command `cy.foo()` internally.
*
* @returns {string} "foo"
* @example cy.foo() // "foo"
*/
export function foo2() {
return cy.foo()
}
/**
* Adds two numbers
* @example sum(2, 3) // 5
*/
export function sum(a: number, b: number): number {
return a + b
return cy.get(`[data-testid='${dataId}']`);
}
const defaultGreeting: Cypress.Greeting = {
greeting: 'hi',
name: 'there'
}
/**
* Prints a custom greeting.
* @example printToConsole({ greeting: 'hello', name: 'world' })
*/
export const printToConsole = (options = defaultGreeting) => {
const {greeting, name} = options
console.log(`${greeting}, ${name}`)
export function containsDataId(dataId: string) {
return cy.get(`[data-testid^='${dataId}']`);
}
// add commands to Cypress like "cy.foo()" and "cy.foo2()"
Cypress.Commands.add('getByDataId', getByDataId)
Cypress.Commands.add('foo2', foo2)
Cypress.Commands.add('sum', sum)
Cypress.Commands.add('greeting', printToConsole)
Cypress.Commands.add('getByDataId', getByDataId);
Cypress.Commands.add('containsDataId', containsDataId);
// a custom Cypress command to login using XHR call
// and then set the received token in the local storage
// can log in with default user or with a given one
Cypress.Commands.add('login', (user = Cypress.env('user')) => {
cy.getLoginToken(user).then(token => {
localStorage.setItem('jwt', token)
// with this token set, when we visit the page
// the web application will have the user logged in
})
cy.getLoginToken(user).then((token) => {
localStorage.setItem('jwt', token);
});
cy.visit('/')
cy.getByDataId("TEST_GLOBAL_FEED").should('be.visible')
})
cy.visit('/');
cy.getByDataId('TEST_GLOBAL_FEED').should('be.visible');
});
// custom Cypress command to simply return a token after logging in
// useful to perform authorized API calls
Cypress.Commands.add('getLoginToken', (user = Cypress.env('user')) => {
return cy
.request('POST', `${apiUrl}/users/login`, {
user: Cypress._.pick(user, ['email', 'password'])
})
.its('body.user.token')
.should('exist')
})
// creates a user with email and password
// defined in cypress.json environment variables
// if the user already exists, ignores the error
// or given user info parameters
Cypress.Commands.add('registerUserIfNeeded', (options = {}) => {
const defaults = {
image: 'https://robohash.org/6FJ.png?set=set3&size=150x150',
// email, password
...Cypress.env('user')
}
const user = Cypress._.defaults({}, options, defaults)
cy.request({
method: 'POST',
url: `${apiUrl}/users`,
body: {
user
},
failOnStatusCode: false
return cy
.request('POST', `${apiUrl}/users/login`, {
user: Cypress._.pick(user, ['email', 'password']),
})
})
/**
* Dispatches a given Redux action straight to the application
*/
Cypress.Commands.add('dispatch', action => {
expect(action)
.to.be.an('object')
.and.to.have.property('type')
cy.window()
.its('store')
.invoke('dispatch', action)
})
.its('body.user.token')
.should('exist');
});
Cypress.Commands.add('registerUserIfNeeded', (options = {}) => {
const defaults = {
image: 'https://robohash.org/6FJ.png?set=set3&size=150x150',
// email, password
...Cypress.env('user'),
};
const user = Cypress._.defaults({}, options, defaults);
cy.request({
method: 'POST',
url: `${apiUrl}/api/users`,
body: {
user,
},
failOnStatusCode: false,
});
});
......@@ -3,6 +3,7 @@ const articleDescId = "TEST_ARTICLE_DESC"
const articleBodyId = "TEST_ARTICLE_BODY"
const articleTagId = "TEST_ARTICLE_TAG"
const articleAddBtnId = "TEST_PUBLISH_ARTICLE"
const articleDelBtnId = "TEST_DELETE_ARTICLE"
class EditorPage {
......@@ -21,5 +22,8 @@ class EditorPage {
addArticle() {
return cy.getByDataId(articleAddBtnId).click();
}
deleteCurrentArticle() {
return cy.getByDataId(articleDelBtnId).click();
}
}
export const editor = new EditorPage();
const articleAddId = "TEST_ADD_ARTICLE"
const articleId = "TEST_ARTICLE_"
class YourFeedPage {
class FeedsPage {
public clickToAddArticle() {
clickToAddArticle() {
return cy.getByDataId(articleAddId).click();
}
getArticlesList() {
return cy.containsDataId(articleId);
}
}
export const yourFeed = new YourFeedPage();
export const feedsPage = new FeedsPage();
const signUpBtnId = "TEST_SIGN_UP_BTN"
class HeaderPage {
clickToSignUp() {
return cy.getByDataId(signUpBtnId).click();
}
getUserLink(username: string) {
return cy.getByDataId(`TEST_${username}`);
}
}
export const headerPage = new HeaderPage();
const tagId = "TEST_TAG_"
class PopularTagsPage {
chooseNthArticle(nth: number) {
return cy.containsDataId(tagId).eq(nth).click();
}
}
export const popularTag = new PopularTagsPage();
const usernameId = "TEST_USERNAME"
const emailId = "TEST_EMAIL"
const passwordId = "TEST_PASSWORD"
const submitBtnId = "TEST_SUBMIT_BTN"
const errorMsgId = "TEST_ERROR_MSG"
class RegistrationPage {
fillUserName(username: string) {
return cy.getByDataId(usernameId).type(username);
}
fillEmail(email: string) {
return cy.getByDataId(emailId).type(email);
}
fillPassword(password: string) {
return cy.getByDataId(passwordId).type(password);
}
signUpBtn() {
return cy.getByDataId(submitBtnId).click();
}
getErrorMsg(item: string) {
return cy.getByDataId(`TEST_ERROR_${item}`);
}
}
export const registrationPage = new RegistrationPage();
import {editor} from "../../support/pages/editor.page";
import {feedsPage} from "../../support/pages/feeds.page";
import {randomString} from "../../support/helpers";
import {popularTag} from "../../support/pages/popular.tags.page";
import {headerPage} from "../../support/pages/header.page";
import {registrationPage} from "../../support/pages/registration.page";
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,"")
registrationPage.fillUserName(user)
registrationPage.fillPassword(`${user}12345`)
registrationPage.fillEmail(`${user}@babis.cz`)
registrationPage.signUpBtn()
headerPage.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', () => {
registrationPage.signUpBtn()
registrationPage.getErrorMsg('email').should('exist')
registrationPage.getErrorMsg('password').should('exist')
registrationPage.getErrorMsg('username').should('exist')
})
})
import {editor} from "../../support/pages/editor.page";
import {yourFeed} from "../../support/pages/your.feed.page";
import {feedsPage} from "../../support/pages/feeds.page";
import {randomString} from "../../support/helpers";
import {popularTag} from "../../support/pages/popular.tags.page";
describe('My First Test', () => {
describe('Smoke scenarios suite', () => {
beforeEach(() => {
cy.registerUserIfNeeded()
cy.login()
})
it('writes a post', () => {
//GIVEN Navigate into Home page
//WHEN choose add article option
//AND fill in mandatory fields of article
//AND click on add article btn
//THEN click on delete article
//AND delete api ended with response code 200
it('CD-T10 Add & delete article', () => {
const title = randomString(7,"")
yourFeed.clickToAddArticle()
feedsPage.clickToAddArticle()
editor.fillArticleTitle(title)
editor.fillArticleDesc(`about ${title}`)
editor.fillArticleBody('this post is **important**.')
editor.fillArticleTag('**important**')
editor.addArticle();
cy.server();
cy.route('DELETE','/api/articles/**').as('deleteApi')
editor.deleteCurrentArticle();
cy.wait('@deleteApi').then(xhr=>{
expect(xhr.status).eq(200)
})
})
//GIVEN Navigate into Home page
//WHEN choose some article
//THEN list of articles contains 20 items
it('Open article according specific tag', () => {
popularTag.chooseNthArticle(0)
feedsPage.getArticlesList().should('have.length',20)
})
})
......@@ -24,7 +24,7 @@ const ArticleActions = props => {
<i className="ion-edit"></i> Edit Article
</Link>
<button className="btn btn-outline-danger btn-sm" onClick={del}>
<button className="btn btn-outline-danger btn-sm" onClick={del} data-testid={`TEST_DELETE_ARTICLE`}>
<i className="ion-trash-a"></i> Delete Article
</button>
......
......@@ -57,7 +57,7 @@ const ArticlePreview = props => {
</div>
<Link to={`/article/${article.slug}`} className="preview-link">
<h1 data-testid={`TEST_ARTICLE`}>{article.title}</h1>
<h1 data-testid={`TEST_ARTICLE_${article.title.replace(" ","_")}`}>{article.title}</h1>
<p>{article.description}</p>
<span>Read more...</span>
<ul className="tag-list">
......
......@@ -13,13 +13,13 @@ const LoggedOutView = props => {
</li>
<li className="nav-item">
<Link to="/login" className="nav-link">
<Link to="/login" className="nav-link" data-testid={`TEST_LOG_IN_BTN`}>
Sign in
</Link>
</li>
<li className="nav-item">
<Link to="/register" className="nav-link">
<Link to="/register" className="nav-link" data-testid={`TEST_SIGN_UP_BTN`}>
Sign up
</Link>
</li>
......@@ -56,7 +56,9 @@ const LoggedInView = props => {
<li className="nav-item">
<Link
to={`/@${props.currentUser.username}`}
className="nav-link">
className="nav-link"
data-testid={`TEST_${props.currentUser.username}`}
>
<img src={props.currentUser.image} className="user-pic" alt={props.currentUser.username} />
{props.currentUser.username}
</Link>
......
......@@ -18,6 +18,7 @@ const Tags = props => {
href=""
className="tag-default tag-pill"
key={tag}
data-testid={`TEST_TAG_${tag.replace(' ','_')}`}
onClick={handleClick}>
{tag}
</a>
......
......@@ -9,7 +9,7 @@ class ListErrors extends React.Component {
{
Object.keys(errors).map(key => {
return (
<li key={key}>
<li key={key} data-testid={`TEST_ERROR_${key}`}>
{key} {errors[key]}
</li>
);
......
......@@ -70,6 +70,7 @@ class Register extends React.Component {
className="form-control form-control-lg"
type="text"
placeholder="Username"
data-testid={`TEST_USERNAME`}
value={this.props.username}
onChange={this.changeUsername} />
</fieldset>
......@@ -79,6 +80,7 @@ class Register extends React.Component {
className="form-control form-control-lg"
type="email"
placeholder="Email"
data-testid={`TEST_EMAIL`}
value={this.props.email}
onChange={this.changeEmail} />
</fieldset>
......@@ -88,6 +90,7 @@ class Register extends React.Component {
className="form-control form-control-lg"
type="password"
placeholder="Password"
data-testid={`TEST_PASSWORD`}
value={this.props.password}
onChange={this.changePassword} />
</fieldset>
......@@ -95,6 +98,7 @@ class Register extends React.Component {
<button
className="btn btn-lg btn-primary pull-xs-right"
type="submit"
data-testid={`TEST_SUBMIT_BTN`}
disabled={this.props.inProgress}>
Sign up
</button>
......
{
"compilerOptions": {
"target": "es5",
"lib": ["es5", "dom"],
"types": ["cypress"],
"lib": [
"es5",
"dom"
],
"types": [
"cypress"
],
"baseUrl": ".",
"esModuleInterop": true
"esModuleInterop": true,
"allowJs": true,
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"**/*.ts"
]
}
\ No newline at end of file
}
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