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

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()
});

Related

Cypress test: Writing multiple elements to database failed

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.

Blank page after running Cypress tests

Whenever I run a new cypress test, the page that is supposed to show the UI is blank. Even when I click on each part of each test it remains blank. Please see image below.
image
Cypress package version: 10.4.0
Node v16.16.0
Code:
describe("home page", () => {
beforeEach(() => {
cy.visit("http://localhost:3000")
})
context("Hero section", () => {
it("the h1 contains the correct text", () => {
cy.getByData("hero-heading").contains(
"Testing Next.js Applications with Cypress"
)
})
it("the features on the homepage are correct", () => {
cy.get("dt").eq(0).contains("4 Courses")
cy.get("dt").eq(1).contains("25+ Lessons")
cy.get("dt").eq(2).contains("Free and Open Source")
})
})
context("Courses section", () => {
it("CourseL Testing Your First Next.js Application", () => {
cy.getByData('course-0')
.find('a')
.eq(3)
.contains('Get started')
})
})
})
/// <reference types="cypress" />
Cypress.Commands.add('getByData', (selector) => {
return cy.get(`[data-test=${selector}]`);
});
I faced the same issue in Cypress version 12.0.0 and I solved it by adding this configuration to cypress.config.js
testIsolation: false,
Try adding 's' to http; this might solve that else here is similar issue reported which might give you clue to your problem https://github.com/cypress-io/cypress/issues/4235
You might have put the it() describe() statements in the wrong place. Try creating the most simple test file possible or better still use an example test that cypress provides strip it down and continue to test it until it is barebones.
I have a "solution" in my tests - it seems that the it steps lose the URL.
Remove all the it steps:
describe('Register Native', () => {
// it("Verify1", () => {
: a
// })
// it("Verify2", () => {
: b
// })
})
Now I have a structure like this (only one it step):
describe('Registrer Native', () => {
it('Whole test- Without IT parts', () => {
: a
: b
: c
})
})
It is not an optimal solution as I now have a long test without intermediary it sections.

Cypress: Why finicky in handling spec's after hook?

Blocked by a Cypress custom command not executing properly within a spec's after hook when a test failed. Experimented with the following minimal spec examples.
Context
Testing two Cypress custom commands:
cy.cmdLogin(): Standard username password authentication
cy.cmdLogout(): cy.get('logout_button').click()
Spec #1, cy.cmdLogout() in after hook does work
Very simple Cypress tests spec, test cy.cmdLogin() with after hook with cy.cmdLogout(), works perfectly:
context('SPEC Login', () => {
after('AFTER Logout', () => {
if (this.successLogin) {
cy.cmdLogout().then(() => {
cy.log('Logout Success');
});
}
});
it('TEST Login', () => {
cy.cmdLogin().then(() => {
cy.log('Login Success');
this.successLogin = true;
});
});
});
Spec #2, cy.cmdLogout() in after hook does not work
Modified Cypress tests spec, test cy.cmdLogin() with after hook with cy.cmdLogout(), now added a test that forced failure expect(false).to.be.a('boolean').to.be.true;. The after hook cy.cmdLogout() is called and fails:
Verified that Cypress could handle cy.get('logout_button') without failure.
Yet, extending with .click(), cy.get('logout_button').click(), Cypress would throw an error: Can not perform click() on undefined.
context('SPEC Login', () => {
after('AFTER Logout', () => {
if (this.successLogin) {
cy.cmdLogout().then(() => {
cy.log('Logout Success');
});
}
});
it('TEST Login', () => {
cy.cmdLogin().then(() => {
cy.log('Login Success');
this.successLogin = true;
});
});
it('TEST Fail', () => {
expect(false).to.be.a('boolean').to.be.true;
});
});
Spec #3, removed after hook, cy.cmdLogout() moved to another test does work
Modified Cypress tests spec again and removed after hook, test cy.cmdLogin(), test forced failure, and now test cy.cmdLogout(). This works perfectly:
context('SPEC Login', () => {
it('TEST Login', () => {
cy.cmdLogin().then(() => {
cy.log('Login Success');
this.successLogin = true;
});
});
it('TEST Fail', () => {
expect(false).to.be.a('boolean').to.be.true;
});
it('TEST Logout', () => {
if (this.successLogin) {
cy.cmdLogout().then(()=> {
cy.task('log', 'Logout Success');
});
}
});
});
Insight as to why Cypress is finicky with spec's after hook would be very much appreciated
The short answer (for the general situation) is change after() hook to before() hook, but in your case that wouldn't work because this.successLogin will always be undefined inside a before.
You would need to create a cy.isLoggedIn() command or call cy.cmdLogout() unconditionally.
To get after() the logic working on a fail, use
after('AFTER Logout', () => {
cy.cmdLogout()
});
Cypress.on('fail', (error, runnable) => {
cy.cmdLogout()
throw error
})
which is equivalent to this in plain javascript
try {
some-code-that-might-error()
cy.cmdLogout() // does not run if error occurs
} catch (error) {
cy.cmdLogout() // do the logout in error situation
}

How to detect all tests (in multiple files) finished

I would like to drop database after all tests in all files ran. Is there a hook for it in Mocha?
after() hook can be applied only within 1 file only.
There is a root level hook in mocha. For your case, you can create a new file and specify drop database command in after function. That's it!
// init-test.js
after(function() {
// drop database
});
You don't need to move any test in other test file for this solution.
Reference:
https://mochajs.org/#root-level-hooks
Create a parent file that includes/requires all other tests, then use after within that file:
describe('User', () => {
require('../user/user.spec.js')
})
describe('Post', () => {
require('../post/post.spec.js')
})
describe('Tag', () => {
require('../tag/tag.spec.js')
})
describe('Routes', () => {
require('./routes.spec.js')
})
after(() => {
// drop database
})
I'm using process.on('exit'). It works when running only one file as well as when running tests from package.json.
database.mocha-base.js:
db.connect()
process.on('exit', async () => {
console.log('All tests finished. Droping database')
db.dropDatabase(dbName)
db.disconnect()
})
module.exports = {
applyHooks() {
before(async () => {
// truncate tables
})
}
}
I'm including database.mocha-base.js in all tests that need database access:
const dbMochaBase = require('./path/to/database.mocha-base.js')
describe('Tests', () => {
dbMochaBase.applyHooks()
...
})

Root level `beforeEachFile` and `afterEachFile`

I am trying to do a root-level before that runs before each test file, and after each test file completes. So in other words I want it to run before and after the "top most" describe.
The only way I got this to work was by adding into each of my files top-most describe a before and after. However it is getting very redundant, can I just do it at root level?
Does wrapping all of your describes in another describe solve it (no pun intended)?
describe('all stuff', () => {
before(() => {
}
describe('something', () => {
it('...', ()=> {
})
it('...', ()=> {
})
})
after(() => {
})
})

Resources