Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rachel - QA - Cypress Automation Assessment #6

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions IMPROVEMENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#Suggestions and Improvements

# POM
1. POM design pattern isn't implemented and framework lacks it.
- For better maintainability , resusability and keeping the code clean it is always advisable to keep the locators in page class methods for every screen else it will be very difficult to maintain when there are any UI changes in the future.
- Locators must never be hardcoded in the test execution script and should be kept separated [For each of the new test case created in repo, the suggestion for not hardcoding locators is provided as comments along with example].
2. Hard coding of data parameters / values must be avoided in the tests. Instead use fixtures folder with example.json provided by cypress to pass the data attributes for testing.
3. Usually utils/utility test file for various other testing frameworks gives an impression to have common methods inside it [like scrollUp, scrollDown, scrollToElement,getPageTitle, waitForVisibilityAndClick, etcetera]. However in this setup, utils isn't used for the purpose it is usually intended for [did not want to disrupt it so kept it as it is and utilised it the same without changing the framework structure].
4. Utils/utility test file should generally be kept outside of page object folder.
5. Under support folder, the index.js file can be deleted as cypress-xpath is not used anywhere [did not want to disrupt, so kept it without changing the framework structure].
6. Tests should be run independently from one another and succeed but that is not the case here for test.cy.js.

# DRY
- Some methods can be improvised and used for a generic purpose instead of creating redundcy for it [eg: fillPaymentCC()]. By passing the card details as parameter/argument inside fillPaymentCC() we can use it for both success and failure cases.
- Separating hardcoded locators/selectors into corresponding page class methods rather tha in actual tests can also help in avoiding redundancy [while they are called from one page class method into multiple test files wherever necessary]

# General
- When we try to automate any application, we need to have ample data for debuggin/testing our script or preferably have any means/tool that can discard the data used so as to use it multiple times. The entries chosen once during execution become invalidated on the next run as the data is consumed. Mechanism to reuse the data would be helpful.
- BDD approach is good to have as the simple gherkin language syntax keywords [like Given, When, Then, Scenario, Background, etcetera] to test requirements will be understandble by all when scripted and presented. However it may add a little bit of additional task / complexity pairing it with cypress.
- Test execution report generation is suggested.
- Pipeline jobs and integration with CI / CD will be beneficial.

# Test Execution
1. Added commands in package.json to open cypress
use > npm openCypress
2. Added command to view all the test file execution on a UI [for chrome browser]
use > npm viewTestOnChromeBrowser
[Please note data for nextDay, nextMonth etc are already consumed/exhausted and are unavailable for booking hence the results will be failed after execution]
3. To execute all test files present in e2e folder
use > npm test
[Please note data for nextDay, nextMonth etc are already consumed/exhausted and are unavailable for booking hence the results will be failed after execution]
4. Test execution videos for each test file are available as part of videos folder.
5 changes: 5 additions & 0 deletions cypress.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ module.exports = defineConfig({
setupNodeEvents(on, config) {
// implement node event listeners here
},
baseUrl:"https://demo.fareharbor.com",
testIsolation : false,
specPattern: 'cypress/e2e/*.js',
trashAssetsBeforeRuns : false,
video: true
},

});
47 changes: 47 additions & 0 deletions cypress/e2e/tc01_failedPayment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@

import Calendar from '../pageObjects/calendar.js'
import Utils from '../pageObjects/utils.js'
import Bookings from '../pageObjects/booking.js'

describe('FareHarbor - Failed Payments testing', function () {
const calendar = new Calendar()
const utils = new Utils()
const bookings = new Bookings()
before(function () {
cy.visit('/embeds/book/bigappletours/items/?full-items=yes')
cy.fixture('example').then(function(data) {
this.data = data
})
})

/* Suggestion:
Instead of hardcording the locators in *it block* of cy.get() command, the approach is to call pageObjectClassname.methodName() for all cases
for eg: In tourActivities.js class of pageObjects folder, we create getWalkingTours() method and return this : cy.get('.grid-block-width-1-3')
pageObjects >>>folder
tourActivities.js >> class
getWalkingTours(){ >>>>> method
return cy.get('.grid-block-width-1-3')
}

In test class:
import TourActivities from "../pageObjects/tourActivities.js" >>>> before the describe block
const tourActivities = new TourActivities() >>> in the describe block
tourActivities.getWalkingTours().click() >>> in the it block ; [this approach would be better than cy.get('.grid-block-width-1-3').click();]
*/

//Perform the necessary operations to book a tour but with failure in payment
it('Book a tour when payments fail',function(){
const numberOfPeople = this.data.lessNumberOfPeople
const card = this.data.failureCard
const waitTime = this.data.customWaitTime
cy.get('.grid-block-width-1-3').click();
calendar.selectNextMonth();
calendar.selectTime();
utils.addPeople(numberOfPeople);
bookings.fillContactInformation();
bookings.fillPaymentCC(card);
cy.get('.btn-huge').click();
cy.wait(waitTime)
cy.get('.form-errors.ng-scope > .test-server-error-indicator > .ng-binding').should('be.visible');
})
})
48 changes: 48 additions & 0 deletions cypress/e2e/tc02_invoiceTotalAmountValidation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@

import Calendar from '../pageObjects/calendar.js'
import Utils from '../pageObjects/utils.js'
import Bookings from '../pageObjects/booking.js'

describe('FareHarbor - Invoice Total Amount Validation', function () {
const calendar = new Calendar()
const utils = new Utils()
const bookings = new Bookings()
before(function () {
cy.visit('/embeds/book/bigappletours/items/?full-items=yes')
cy.fixture('example').then(function(data) {
this.data = data
})
})

/* Suggestion:
Instead of hardcording the locators in *it block* of cy.get() command, the approach is to call pageObjectClassname.methodName() for all cases
for eg: In tourActivities.js class of pageObjects folder, we create getWalkingTours() method and return this : cy.get('.grid-block-width-1-3')
pageObjects >>>folder
tourActivities.js >> class
getWalkingTours(){ >>>>> method
return cy.get('.grid-block-width-1-3')
}

In test class:
import TourActivities from "../pageObjects/tourActivities.js" >>>> before the describe block
const tourActivities = new TourActivities() >>> in the describe block
tourActivities.getWalkingTours().click() >>> in the it block ; [this approach would be better than cy.get('.grid-block-width-1-3').click();]
*/

//Perform the necessary operations to book a tour and validate the total amount incurred
it('Validating total amount for services purchased',function(){
const numberOfPeople = this.data.peopleForTotalAmountValidation
const card = this.data.successCard
const waitTime = this.data.customWaitTime
cy.get('.grid-block-width-1-3').click();
calendar.selectNextMonth();
calendar.selectTime();
utils.addPeople(numberOfPeople);
bookings.fillContactInformation();
bookings.fillPaymentCC(card);
cy.get('.btn-huge').click();
cy.wait(waitTime)
cy.get('.receipt-header').should('be.visible');
utils.validateTotalAmountMoneyCalculation()
})
})
55 changes: 55 additions & 0 deletions cypress/e2e/tc03_activityBookingForMonthAndYear.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@

import Calendar from '../pageObjects/calendar.js'
import Utils from '../pageObjects/utils.js'
import Bookings from '../pageObjects/booking.js'

describe('FareHarbor - scenario to book activity 1 month from now and 1 year from now', function () {
const calendar = new Calendar()
const utils = new Utils()
const bookings = new Bookings()
beforeEach(function () {
cy.visit('/embeds/book/bigappletours/items/?full-items=yes')
cy.fixture('example').then(function(data) {
this.data = data
})
})

/* Suggestion:
Instead of hardcording the locators in *it block* of cy.get() command, the approach is to call pageObjectClassname.methodName() for all cases
for eg: In tourActivities.js class of pageObjects folder, we create getBigApplePrivateTours() method and return this : cy.get('.item-grid li:nth-child(3)')
pageObjects >>>folder
tourActivities.js >> class
getBigApplePrivateTours(){ >>>>> method
return cy.get('.item-grid li:nth-child(3)')
}

In test class:
import TourActivities from "../pageObjects/tourActivitie.js" >>>> before the describe block
const tourActivities = new TourActivities() >>> in the describe block
tourActivities.getBigApplePrivateTours().click() >>> in the it block ; [this approach would be better than cy.get('.item-grid li:nth-child(3)').click();]
*/

//Perform the necessary operations to book a tour in the next month from the current date
it('booking tour in next month from present date',function(){
const numberOfPeople = this.data.lessNumberOfPeople
const card = this.data.successCard
const waitTime = this.data.customWaitTime
cy.get('.grid-block-width-1-3').click();
calendar.selectNextMonth();
calendar.selectTime();
utils.addPeople(numberOfPeople);
bookings.fillContactInformation();
bookings.fillPaymentCC(card);
cy.get('.btn-huge').click();
cy.wait(waitTime)
cy.get('.receipt-header').should('be.visible');
})

//Verify if user is stopped from booking tour in the next year
it('booking tour in next year from present date',function(){
const nextYearValue = this.data.nextYear
cy.get('.grid-block-width-1-3').click();
calendar.selectNextYear(nextYearValue);
calendar.emptyState();
})
})
49 changes: 49 additions & 0 deletions cypress/e2e/tc04_bigApplePrivateTour.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@

import Calendar from '../pageObjects/calendar.js'
import Utils from '../pageObjects/utils.js'
import Bookings from '../pageObjects/booking.js'

describe('FareHarbor - Big Apple\'s Private Tour for 4 people and filling up the details', function () {
const calendar = new Calendar()
const utils = new Utils()
const bookings = new Bookings()
before(function () {
cy.visit('/embeds/book/bigappletours/items/?full-items=yes')
cy.fixture('example').then(function(data) {
this.data = data
})
})

/* Suggestion:
Instead of hardcording the locators in *it block* of cy.get() command, the approach is to call pageObjectClassname.methodName() for all cases
for eg: In tourActivities.js class of pageObjects folder, we create getBigApplePrivateTours() method and return this : cy.get('.item-grid li:nth-child(3)')
pageObjects >>>folder
tourActivities.js >> class
getBigApplePrivateTours(){ >>>>> method
return cy.get('.item-grid li:nth-child(3)')
}

In test class:
import TourActivities from "../pageObjects/tourActivities.js" >>>> before the describe block
const tourActivities = new TourActivities() >>> in the describe block
tourActivities.getBigApplePrivateTours().click() >>> in the it block ; [this approach would be better than cy.get('.item-grid li:nth-child(3)').click();]
*/

//Perform the necessary operations to book Big Apples Private tour with details filled for 4 people
it('Verify if Big Apple\'s Private tour is booked for 4 people with details',function(){
const numberOfPeople = this.data.bookingPeopleForBigApplesPrivateTour
const card = this.data.successCard
const waitTime = this.data.customWaitTime
cy.get('.item-grid li:nth-child(3)').click();
calendar.selectNextMonth();
calendar.selectTime();
utils.addFourToSixPeople(numberOfPeople);
utils.fillCateringOptions()
utils.fillAdditionalInformation()
bookings.fillContactInformation();
bookings.fillPaymentCC(card);
cy.get('.btn-huge').click();
cy.wait(waitTime)
cy.get('.receipt-header').should('be.visible');
})
})
51 changes: 39 additions & 12 deletions cypress/e2e/test.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,54 +8,81 @@
const utils = new Utils()
const bookings = new Bookings()
before(function () {
cy.visit('https://demo.fareharbor.com/embeds/book/bigappletours/items/?full-items=yes')
//added baseUrl in the cypress.config.js file
cy.visit('/embeds/book/bigappletours/items/?full-items=yes')
cy.fixture('example').then(function(data) {
this.data = data
})
})

context('When page is initially opened', function () {
it('company information is present', function () {
cy.request('https://demo.fareharbor.com/api/v1/companies/bigappletours/').then((response) => {
expect(response.body.company.name).to.eq("Big Apple Tours and Activitys")
expect(response.status).to.eq(403)
//Response body returns : Big Apple Tours and Activities
expect(response.body.company.name).to.eq("Big Apple Tours and Activities")
// response status received : 200
expect(response.status).to.eq(200)
})
})

it('activity overlay should be present', function () {
cy.get('#ng-app').should.('be.visible');
//Cypress by default clears the current session data before each test so disabled testIsolation by setting it to false in cypress config file
cy.get('#ng-app').should('be.visible'); //syntax correction hence removed . after should
})
}),
})

context('I pick an activity', function () {
it('Im able to click the activity', function () {
cy.get('.grid-block-width-1-3').click();
})

it('Im able to see the calendar', function () {
cy.get('.test-calendar-indicator').should('be.visible');
})
}),
})

context('I select a day/time for my activity', function () {
it('I pick a day', function () {
//please note that booking for next day is not possible as the tour slots are sold out till 21st Oct,2023
calendar.selectNextDay();
})

it('I pick a time', function () {
//fixed locators
calendar.selectTime();
})
})

context('I select the amount of people', function () {
it('I add 26 adults', function () {
utils.addPeople(26);
//maximum adults or persons [adult+child] that can be selected in application is 25
it('I add 25 adults', function () {
const numberOfPeople = this.data.maxNumberOfPeople
utils.addPeople(numberOfPeople);
})
})

context('I fill in my contact information', function () {
it('I add my name, phone number and email', function () {
bookings.fillContactInformation();
})

it('I enter my credit card details', function () {
bookings.fillPaymentCC();
//providing valid card details for successful payment
const card = this.data.successCard
bookings.fillPaymentCC(card);
})
})
}),

context('I pay and get confirmation', function () {
it('I complete and play', function () {
cy.get('btn-huge').click();
it('I complete and pay', function () {
//updated correct locator
cy.get('.btn-huge').click();
})

it('I get my receipt', function () {
//placing an explicit wait
const waitTime = this.data.customWaitTime
cy.wait(waitTime)
cy.get('.receipt-header').should('be.visible');
})
})
Expand Down
11 changes: 8 additions & 3 deletions cypress/fixtures/example.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
{
"name": "Using fixtures to represent data",
"email": "[email protected]",
"body": "Fixtures are a great way to mock data for responses to routes"
"successCard": 4242424242424242,
"failureCard" : 4000000000006975,
"customWaitTime" : 10000,
"nextYear" : "2024",
"maxNumberOfPeople" : 25,
"bookingPeopleForBigApplesPrivateTour" : "4",
"peopleForTotalAmountValidation" : "5",
"lessNumberOfPeople" : "2"
}
16 changes: 8 additions & 8 deletions cypress/pageObjects/booking.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,20 @@ class bookings {
//Telephone
//cy.get('').select('[data-country-code='+faker.address.countryCode().toLowerCase()+'"]')
cy.get('.flag-container').click()
cy.get('.country-list').contains(faker.address.country()).click()
cy.get('.bookform-contact-phone').type(faker.phone.phoneNumber('999-###-###'))
cy.get('.country-list').contains('France').click() //Hardcoding this value as it fails for some cases
//using faker.phone.number as faker.phone.phoneNumber is deprecated
cy.get('.bookform-contact-phone').type(faker.phone.number('999-###-###'))

//Email
cy.get('#id_email').type(faker.internet.exampleEmail())

}
fillPaymentCC() {
//Always use the same positive card
cy.get('#id_card_number').type('4242424242424242')
fillPaymentCC(cardData) {
//Using different data combinations to test success & failure
cy.get('#id_card_number').type(cardData)
//Select Next Year, Select second month of next year
cy.get('#id_card_expiry_month').select(15)
cy.get('#id_card_expiry_year').select(2)

cy.get('#id_card_expiry_month').select(2) //updated the second month for next year. We have only 12 months in a year so the value passed > 15 is incorrect
cy.get('#id_card_expiry_year').select("2024") //updated next year
cy.get('#id_cardholders_name').type(faker.name.fullName())
cy.get('.card-cvc').type(faker.finance.creditCardCVV())
cy.get('#id_country_code').select(Math.floor(Math.random() * 150))
Expand Down
Loading