Cypress test: Writing multiple elements to database failed - cypress

Trying to write multiple elements into database failed, only the last one is written:
// my_test.cy.js
import CreateProductPage from "../pages/CreateProductPage";
describe('product detail page', () => {
beforeEach(() => {
cy.login('admin', 'shop')
})
it('should print typed product', () => {
cy.createProduct(null, 'Component Product 0', '0')
CreateProductPage.elements.productDetailTabs().should('exist') // <--- to detect that the entity is written
cy.createProduct('combinable', 'Combined Test Product', '0')
CreateProductPage.elements.productDetailTabs().should('exist') // <--- to detect that the entity is written
})
})
// commands.js
Cypress.Commands.add('createProduct', (type, name, grossPrice) => {
cy.visit('/#/sw/product/create')
CreateProductPage.elements.productDetailTabs().should('not.exist').then(() => {
if(type === 'combinable') {
CreateProductPage.elements.radioBtnCombinableProduct().click()
}
CreateProductPage.elements.inputProductName().clear().type(name)
CreateProductPage.elements.inputPriceFieldGross().type(grossPrice)
SwPageHeader.elements.btnProductSave().click()
})
})
Questions:
This failed because of asynchronous nature of cypress?
If so, how to interrupt? Chaining with then(), the behavior is the same
With this is code (adding wait()) it works, but i'm looking for the right way
// my_test.cy.js
describe('product detail page', () => {
beforeEach(() => {
cy.login('admin', 'shop')
})
it('should print typed product', () => {
cy.createProduct(null, 'Component Product 0', '0')
CreateProductPage.elements.productDetailTabs().should('exist')
cy.wait(300)
cy.createProduct('combinable', 'Combined Test Product', '0')
CreateProductPage.elements.productDetailTabs().should('exist')
})
})
EDIT 1
// pages/CreateProductPage.js
class CreateProductPage {
elements = {
productDetailTabs: () => cy.get('div.sw-product-detail-page__tabs'),
radioBtnCombinableProduct: () => cy.get('.sw-product-detail-base__info input#type_combinable_product-0'),
radioBtnUnCombinableProduct: () => cy.get('.sw-product-detail-base__info input#type_combinable_product-1'),
inputProductName: () => cy.get('input#sw-field--product-name'),
inputPriceFieldGross: () => cy.get('div.sw-list-price-field__price input#sw-price-field-gross'),
}
}
module.exports = new CreateProductPage();

If the problem is one of waiting, you will need to figure out something that indicates to the user that the save was successful and test that.
For example, if there was a toast message on screen:
...
SwPageHeader.elements.btnProductSave().click()
cy.contains('span', 'Product was saved').should('be.visible')

Blockquote
This failed because of asynchronous nature of cypress?
Code behaves synchronously if you have only cy commands inside the code block.
As suggested in https://stackoverflow.com/a/74721728/9088832 you should wait for some element to appear or for an API request to be completed that is responsible for the product creation.

Related

Unable to read data from fixture file after writing it

I'm writing a URL with an ID in a .json file that I'm extracting from the API, but the problem is that the cy.log() is printing the data before it is wrote in the file. Because the AppelData.json has the written data, but cy.log() prints nothing.
After the second run, the cy.log() prints the previous data from AppelData.json.
So how do I make, that cy.log() to print the data from AppelData.json only after it is been written?
describe('Creer un appel modal', () => {
beforeEach(() => {
cy.fixture('AppelData').then(function(data) {
this.AppelData = data
})
cy.visit('/')
cy.loginAsAdmin()
})
it('Create an intervention request using existing site', function() {
navigateTo.plusAppelButton()
onAppelModal.SelectSite(this.AppelData.Site)
onAppelModal.SelectMetier(this.AppelData.Metier)
onAppelModal.FillMotif(this.AppelData.Motif)
cy.intercept('POST','/documents/datatable/intervention_request/**').as('response')
cy.contains('Valider').click()
cy.wait('#response').get('#response').then(xhr => {
console.log(xhr)
cy.readFile('cypress/fixtures/AppelData.json').then(AppelData => {
AppelData.AppelID = xhr.request.url.replace(/\D/g,'').replace(/3/, '')
cy.writeFile('cypress/fixtures/AppelData.json', AppelData)
cy.log(this.AppelData.AppelID) // logs no value on the first run, and prints old value from the 2nd run
})
})
})
})
Thank you!!
Assuming that that the value is updated into the fixtures file, you can do this:
it('Create an intervention request using existing site', function () {
navigateTo.plusAppelButton()
onAppelModal.SelectSite(this.AppelData.Site)
onAppelModal.SelectMetier(this.AppelData.Metier)
onAppelModal.FillMotif(this.AppelData.Motif)
cy.intercept('POST', '/documents/datatable/intervention_request/**').as(
'response'
)
cy.contains('Valider').click()
cy.wait('#response')
.get('#response')
.then((xhr) => {
console.log(xhr)
cy.readFile('cypress/fixtures/AppelData.json').then((AppelData) => {
AppelData.AppelID = xhr.request.url.replace(/\D/g, '').replace(/3/, '')
cy.writeFile('cypress/fixtures/AppelData.json', AppelData)
})
})
cy.readFile('cypress/fixtures/AppelData.json').then((AppelData) => {
cy.log(AppelData.AppelID)
})
})

Cypress: Code in page object file is being executed as a test case before the before hook

I was trying to use a condition as a function within a page object.
class Folders {
DropdownCheckFunction(){
cy.get('.more-less-container').then((dropdown) => {
if(dropdown.find('.name').contains('More')){
cy.get('more-less-container').click()
}
else{
console.log('folders are in expanded state')
}
})
}
Drafts(){
this.DropdownCheckFunction()
cy.get('.category-content').find('[title="Drafts"]').click()
.get(".folder-details").should('contain.text', 'Drafts')
}
Issue here is that the page object is getting executed as a test case and is happening before the code in BEFORE hook is being run. Below is the test file code
describe('Testing all cases related to Drafts', () => {
before(() => {
cy.login()
})
})
it('Needs to open the Drafts folder', () => {
openFolder.Drafts()
});
Attaching the error seen on the test runner for reference
The problem is bad formatting.
If you line up your code, you can see it - your test is outside the scope of the before().
describe('Testing all cases related to Drafts', () => {
before(() => {
cy.login()
})
}) // move this bracket to below the `it()`
it ('Needs to open the Drafts folder', () => {
openFolder.Drafts()
});

In Cypress, trying to have multiple contexts leads to tests not finishing

After years of reviewing answers here, I finally have a question ^^
I'm a new Cypress user and trying to make tests one after another, but doing so make the first context to finish before all its tests (line 29).
Please find code below :
describe('Home', () => {
beforeEach(() => {
cy.viewport(1920, 1080)
visit('home')
})
context('Content and links', ()=>{
before(() => {
getTransloco('market', 'fr').then(res => loco = res);
cy.clearFirestore();
cy.importData({
...marketWithSellers(market, [seller]),
...createStandaloneProducts(products, seller),
});
})
it('Contains all data and links', async () => {
cy.contains(loco.app)
cy.contains(loco.subtitle)
get('order').contains(loco.order)
cy.log('ongoing')
get('signin').contains(loco.signin)
get('link-home').should('exist')
get('link-market').should('exist').should('have.class','disabled')
get('link-orders').should('exist')
get('link-profiles').should('not.exist')
get('link-signin').should('exist') // <== the test stops here /!\
get('order').click()
cy.contains(loco['select-market'])
get('market_0').should('exist').contains(market.name!)
cy.get('[aria-roledescription="map"]').should('exist')
cy.get('[role="button"]').should('exist')
cy.contains(loco['sellers-list']).scrollIntoView().should('exist')
get('seller_0').contains(seller.name)
get('link-about').should('exist')
get('link-facebook').should('exist')
get('link-instagram').should('exist')
get('link-twitch').should('exist')
cy.contains(loco.contact)
get('link-contact').should('have.attr', 'href', 'mailto:contact#kampoy.com')
})
})
context('Top nav bar', ()=>{
before(() => {
getTransloco('buyer', 'fr').then(res => loco = res);
})
it('Nav bar appears after scrolling down', () => {
get('top-nav').should('not.have.class', 'visible')
get('link-contact').scrollIntoView()
get('top-nav').should('have.class', 'visible')
})
it('Nav bar appears only has home, auth and order links', () => {
get('top-nav').children().should('have.length', 3)
get('top-nav link-home').contains(loco['app-name'])
get('top-nav link-auth').contains(loco.signin)
get('top-nav link-order').contains(loco.order)
})
})
})
The strange part is, if I comment the second context (top nav bar), then all my first context is working fine...
Does anyone understand why ?
Thanks for your time !

Cypress.Cookies.preserveOnce('session_id') isn't working when called in afterEach hook

I'm working with cypress tests and I want to avoid having to log in before each test. so, I wanted to preserve the cookies in each test file.
The log statement in the afterEach hook is triggered, however cookies are cleared in the second testcase.
describe('Users Page Scenarios', () => {
before(() => {
myApp.pages.Login.navigate();
myApp.pages.Login.login(
credentials.globalAdmin.email,
credentials.globalAdmin.password
);
});
beforeEach('navigate to users page before each test', () => {
myApp.sharedComponents.Header.navigateToUsers();
});
afterEach(() => {
Cypress.Cookies.preserveOnce('session_id');
cy.log('test');
});
describe('Users List', () => {
it('Should redirect the user to users page after clicking on users in the navigation header', () => {
cy.url().should('eq', `${Cypress.config().baseUrl}/user`);
});
})
describe('New User Creation', () => {
it('Should open new user modal after clicking on invite administrator', () => {
myApp.pages.Users.UsersList.inviteAdministrator();
cy.url().should('eq', `${Cypress.config().baseUrl}/user/new`);
});
it('Should create a new user successfully', () => {
myApp.pages.Users.UsersList.inviteAdministrator();
myApp.pages.Users.UsersInfo.createNewUser(user.generateUser());
})
});
The docs indicate that Cypress.Cookies.preserveOnce('session_id') is used in beforeEach().
Looks like after is too late.
describe('Dashboard', () => {
before(() => {
// log in only once before any of the tests run.
// your app will likely set some sort of session cookie.
// you'll need to know the name of the cookie(s), which you can find
// in your Resources -> Cookies panel in the Chrome Dev Tools.
cy.login()
})
beforeEach(() => {
// before each test, we can automatically preserve the
// 'session_id' and 'remember_token' cookies. this means they
// will not be cleared before the NEXT test starts.
//
// the name of your cookies will likely be different
// this is an example
Cypress.Cookies.preserveOnce('session_id', 'remember_token')
})
If you have localStorage or sessionStorage to preserve, or you have not identified all cookies correctly, try with cy.session()
beforeEach(() => { // must be beforeEach()
cy.session('mySession', () => { // preserves localStorage, sessionStorage, cookies
myApp.pages.Login.navigate();
myApp.pages.Login.login(...); // only called once (despite beforeEach())
})
})

Intercept each request with before and after hooks

I have to make 5 requests (order doesn't matter) to 5 different endpoints. The URL of these endpoints is the same, except for the business line. These business lines are the array of the from.
I want show a skeleton loader before each request and hide once it finish. So, basically the flow is:
1. [Hook - before request]
2. [Log of data fetched]
3. [Hook - after request]
This is my service:
export function getInsurances(
userIdentity: string,
hooks?: RequestHooks
): Observable<Policy[]> {
return from(["all", "vehicle", "health", "soat", "plans"]).pipe(
tap(() => hooks?.beforeRequest && hooks.beforeRequest()),
flatMap<string, Observable<Policy[]>>(businessLine => {
return InsurancesApi.getPolicies<Policy>(
userIdentity,
businessLine
).pipe(
map(policies => {
return policies.map(policy => PolicyStandarizer(policy));
}),
finalize(() => {
hooks?.afterRequest && hooks.afterRequest();
})
);
}),
catchError(err => of(err)),
takeUntil(HttpCancelator)
);
}
This is my subscribe:
const hooks = {
beforeRequest() {
Log.info("Before Request");
setStatus(HttpRequestStatus.PENDING);
},
afterRequest() {
Log.warn("After Request");
setStatus(HttpRequestStatus.RESOLVED);
},
};
getInsurances(userIdentity, hooks).subscribe(
policies => {
Log.normal("Policies:", policies);
setInsurances(policies);
},
(err: Error) => {
setError(err);
}
);
And have this output (sorry for paste the link, I can't embed the image because rep):
https://i.stack.imgur.com/Nbq49.png
The finalize is working fine, but the tap is executing five times at once.
Thank you.
I think you get this behavior because from emits the items synchronously, so its essentially the same as doing:
for (let i = 0; i < arr.length; i++) {
console.log('before req');
observer.next(arr[i]);
}
observer.complete();
afterRequest is shown properly because the actions involved are asynchronous.
If you want to trigger that event only once, before all the requests are fired, you could try this:
from([...])
.pipe(
finalize(() => hooks?.beforeRequest && hooks.beforeRequest()),
flatMap(/* ... */)
)
EDIT - log event before each request
flatMap(
value => concat(
of(null).pipe(
tap(() => hooks?.beforeRequest && hooks.beforeRequest()),
ignoreElements(), // Not interested in this observable's values
),
InsurancesApi.getPolicies(/* ... */)
)
)

Resources