I have this demo code.
describe('demo', () => {
beforeEach( async () => {
console.log('->before each')
})
afterEach( async () => {
console.log('->after each')
})
describe('->1', () => {
it('->1.1', async () => {
console.log('->1.1')
})
it('->1.2', async () => {
console.log('->1.2')
})
})
describe('->2', () => {
it('->2.1', async () => {
console.log('->2.1')
})
it('->2.2', async () => {
console.log('->2.2')
})
})
})
And i'm waiting this result:
->before each
->1
->1.1
->1.2
->after each
->before each
->2
->2.1
->2.2
->after each
I want afterEach and beforeEach to run only before and after describe()s, not it()s.
How actually works:
->1
->before each
->1.1
->after each
->before each
->1.2
->after each
->2
->before each
->2.1
->after each
->before each
->2.2
->after each
I thought afterEach and beforeEach runs only on same level, not in cildren.
Thank you!
Yes unfortunately beforeEach() and afterEach() run for every it() block. But you could restructure your tests by using beforeAll() and afterAll() as follows:
describe('demo', () => {
describe('->1', () => {
beforeAll(async () => {
console.log('beforeAll');
});
afterAll(async () => {
console.log('afterAll');
});
it('->1.1', async () => {
console.log('->1.1');
});
it('->1.2', async () => {
console.log('->1.2');
});
});
describe('->2', () => {
beforeAll(async () => {
console.log('beforeAll');
});
afterAll(async () => {
console.log('afterAll');
});
it('->2.1', async () => {
console.log('->2.1');
});
it('->2.2', async () => {
console.log('->2.2');
});
});
});
output
->beforeAll
->1
->1.1
->1.2
->afterAll
->beforeAll
->2
->2.1
->2.2
->afterAll
This provides you the expected result but you have to write more code. But in tests, it's ok to have some redundant code.
I think you need another annotation
beforeAll(functionopt, timeoutopt) - execute once before for a describe
afterAll(functionopt, timeoutopt) - execute once after for a describe
More details here
Related
I have a function that is named DateManagement.js:
checkSalesDate: () => {
let itemTagObj = {}
cy.xpath("//div[contains(text(), 'Updated')]")
.then(date => {
itemTagObj.initialDate = date[0].innerHTML
itemTagObj.manualDate = 'Updated: 08 Aug 2022 06:54:50'
})
return itemTagObj
}
I am trying to compare the values inside initialDate and manualDate Here is my main script
import { DateManagement } from '../functions/index'
describe('test date', () => {
it('test date', function() {
itemTagObj = DateManagement.checkUpdatedSalesTag()
expect(itemTagObj.initialDate).to.not.equal(itemTagObj.manualDate)
})
})
When I run this script, My result is undefined for both the values being compared
If I try to cy.log the itemTagObj, it shows that it successfully fetched the values from checkSalesDate function
Is there something missing here that I need to do?
You can return a chain with the itemTagObj as a subject from your function
checkSalesDate: () => {
let itemTagObj = {}
return cy.xpath("//div[contains(text(), 'Updated')]")
.then(date => {
itemTagObj.initialDate = date[0].innerHTML
itemTagObj.manualDate = 'Updated: 08 Aug 2022 06:54:50'
return itemTagObj
})
}
And use it as follows:
import {DateManagement} from '../functions/index'
describe('test date', () => {
it('test date', function () {
DateManagement.checkUpdatedSalesTag().then(itemTagObj => {
expect(itemTagObj.initialDate).to.not.equal(itemTagObj.manualDate)
})
})
})
You can do something like this:
import {DateManagement} from '../functions/index'
describe('test date', () => {
it('test date', function () {
itemTagObj = DateManagement.checkUpdatedSalesTag()
cy.then(() => {
expect(itemTagObj.initialDate).to.not.equal(itemTagObj.manualDate)
})
})
})
Below is the dev console in cypress
I have tried adding below config is Cypress.json
{
"modifyObstructiveCode" : false
}
but this causes Cypress runner to not find my test at all
This is my Cypress code :
/// <reference types="Cypress" />
describe("Service Now TEST login", () => {
it("Login TEST", () => {
cy.visit("https://hptest.service-now.com/login.do")
cy.wait(2000)
cy.get(".form-control", {
timeout: 10000
}).should("be.visible").then(() => {
cy.get("#user_name").type("");
cy.get("#user_password").type("");
cy.get("#sysverb_login").click();
});
})
})
Please help me here.
I can see in your test runner logs that there is a exception generated, you can catch the exception using:
Cypress.on('uncaught:exception', (err, runnable) => {
return false
})
So your code should look like:
describe('Service Now TEST login', () => {
it('Login TEST', () => {
cy.visit('https://hptest.service-now.com/login.do')
//Catch Exception
Cypress.on('uncaught:exception', (err, runnable) => {
return false
})
cy.wait(2000)
cy.get('.form-control', {
timeout: 10000,
})
.should('be.visible')
.then(() => {
cy.get('#user_name').type('')
cy.get('#user_password').type('')
cy.get('#sysverb_login').click()
})
})
})
I am quite new to Cypress and I have some before() calling commands that create bunch of things via API calls and return the IDs of created which I use in the after() for removing them, but somehow it works perfectly if I only return one ID and store in the alias but will fail if I store an array of IDs in alias, is this intended or I did something wrong.
in my code:
before(() => {
cy.setupEnv()
.as('access_token')
.then((token) => cy.setupFlow(token).as('data_id'))
})
after(function () {
console.log(this.access_token)
console.log(this.data_id)
})
console.log(this.data_id) shows fine if setupFlow returns only one ID but becomes undefined if I try to return [id1,id2,id3]and store the array using .as("data_id")
You've struck a strange issue, worth raising with Cypress.
It only seems to happen if you have more than one test.
For example, if I run the following it logs the array.
before(() => {
cy.wrap(1).as('access_token')
cy.then(() => {
return [1,2,3]
}).as('data_id')
})
after(function () {
console.log(this.access_token) // 1
console.log(this.data_id) // [1,2,3]
})
it('test1', () => {
console.log('test1')
expect(true).to.eq(true)
})
If I add a test it logs undefined!
before(() => {
cy.wrap(1).as('access_token')
cy.then(() => {
return [1,2,3]
}).as('data_id')
})
after(function () {
console.log(this.access_token) // 1
console.log(this.data_id) // undefined
})
it('test1', () => {
console.log('test1')
expect(true).to.eq(true)
})
it('test2', () => {
console.log('test2')
expect(true).to.eq(true)
})
One way around this is to use Cypress.env() instead
before(() => {
cy.wrap(1).as('access_token')
cy.then(() => {
Cypress.env('data_id', [1,2,3])
return [1,2,3]
}).as('data_id')
console.log('before')
})
after(function () {
console.log(this.access_token) // 1
console.log(this.data_id) // undefined
console.log(Cypress.env('data_id')) // [1,2,3]
})
beforeEach(function() {
console.log(cy.state())
console.log(this.data_id)
cy.wrap(this.data_id).as('data_id')
})
it('test1', () => {
expect(true).to.eq(true)
console.log('test1')
})
it('test2', () => {
console.log('test2')
expect(true).to.eq(true)
})
Assuming that cy.setupFlow(token) generates an array of values something like [id1, id2, id3]. This will work even when there is one value in the array. You after each should look this:
after(function () {
cy.get('#data_id').then((data_id) => {
//Get individual values
cy.log(data_id[0])
cy.log(data_id[1])
cy.log(data_id[2])
//Get all values using forEach
data_id.forEach((id) => {
cy.log(id) //get all values one by one
})
})
})
I created a small POC for this and it is working as expected.Below are the results.
Code:
describe('SO Ques', () => {
before(function () {
cy.wrap([1, 2, 3]).as('array')
})
it('SO Ques', function () {
cy.log('Hello')
})
after(function () {
cy.get('#array').then((array) => {
cy.log(array[0])
cy.log(array[1])
cy.log(array[2])
})
})
})
Result:
Just a simple question in optimizing my test.
It is possible to create a single test can identify which browser you are using and then it will execute that test create for that browser
For example:
If detects chrome it will run this and it will not run in firefox
describe('Sample test', () => {
it('verify header!', () => {
get('#Header')
})
})
If detects firefox it will run this and it will not run in chrome
describe('Sample test', () => {
it('verify header!', () => {
get('#fixHeader')
})
})
Yes, it's possible to skip when browser is a certain type
describe('Sample test', () => {
it('verify header!', () => {
if (Cypress.browser.name === 'chrome') {
this.skip
}
get('#Header')
})
})
Or by family (e.g cover Chrome, Edge and Electron with one step)
describe('Sample test', () => {
it('verify header!', () => {
if (Cypress.browser.family === 'chromium') {
this.skip
}
get('#Header')
})
})
You can sun specific tests on specific browser like this. You can learn more about it from the cypress docs
//Runs in Electron
it('Test 1', { browser: 'electron' }, () => {
...
})
//Runs in Firefox
it('Test 2', { browser: 'firefox' }, () => {
...
})
//Runs in Chrome
it('Test 3', { browser: 'chrome' }, () => {
...
})
//Doesn't run in Chrome
it('Test 4', { browser: '!chrome' }, () => {
...
})
My specs are behaving weirdly in that when I run the tests alone, they pass. However, when I run the test suite all together, the failure tests still continue to use the success axios mock instead of using the correct failing http axios mock. This results in my tests failing. Am I missing something for isolating the 2 mocks from each other in the different portions of code?
jobactions.js
export const loadUnassignedJobs = (job_type) => {
if (!['unscheduled', 'overdue'].includes(job_type)) {
throw 'Job Type must be "unscheduled" or "overdue".';
}
return (dispatch) => {
dispatch({type: JobActionTypes.LOAD_UNASSIGNED_JOBS_STARTED, job_type });
return axios.get(defaults.baseapi_uri + 'jobs/' + job_type)
.then(function (response) {
dispatch(updateUnassignedJobs(response.data.jobs));
// handle success
})
.catch(function (error) {
// handle error
dispatch({ type: JobActionTypes.LOAD_UNASSIGNED_JOBS_FAILURE, error });
})
.then(function () {
// always executed
});
}
};
export const updateUnassignedJobs = (unassigned_jobs) => {
let unassigned_job_ids = [];
let jobs = {};
for (let job of unassigned_jobs) {
unassigned_job_ids.push(job.id);
jobs[job.id]=job;
}
return({
type: JobActionTypes.LOAD_UNASSIGNED_JOBS_SUCCESS,
jobs,
unassigned_job_ids,
});
};
spec.js
import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";
import * as jobActions from "../../../app/javascript/actions/JobActions"
import { findAction } from '../support/redux_store'
import * as JobActionTypes from '../../../app/javascript/constants/JobActionTypes'
import fixtures_jobs_unscheduled_success from '../fixtures/jobs_unscheduled_success'
import moxios from "moxios";
export const mockStore = configureMockStore([thunk]);
let store;
describe ('loadUnassignedJobs', () => {
context('when bad parameters are passed', async () => {
it('will raise an error', () => {
const store = mockStore();
expect(() => {
store.dispatch(jobActions.loadUnassignedJobs('wrong_type'));
}).to.throw('Job Type must be "unscheduled" or "overdue".');
});
});
context('when unscheduled is passed', () => {
beforeEach(() => {
moxios.install();
console.log("before each called");
console.log(moxios.requests);
store = mockStore();
store.clearActions();
});
afterEach(() => {
console.log("after each called");
console.log(moxios.requests);
moxios.uninstall();
});
context('on success', () => {
beforeEach(() => {
moxios.wait(() => {
let request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: fixtures_jobs_unscheduled_success
});
});
})
it('dispatches LOAD_UNASSIGNED_JOBS_STARTED', () => {
store.dispatch(jobActions.loadUnassignedJobs('unscheduled')).then(() => {
expect(findAction(store, JobActionTypes.LOAD_UNASSIGNED_JOBS_STARTED)).to.be.eql({
type: JobActionTypes.LOAD_UNASSIGNED_JOBS_STARTED,
job_type: 'unscheduled'
});
});
});
it('dispatches updateUnassignedJobs()', () => {
store.dispatch(jobActions.loadUnassignedJobs('unscheduled')).then(() => {
expect(findAction(store,JobActionTypes.LOAD_UNASSIGNED_JOBS_SUCCESS)).to.be.eql(jobActions.updateUnassignedJobs(fixtures_jobs_unscheduled_success.jobs))
});
});
});
context('on error', () => {
beforeEach(() => {
//console.log("before each on error called");
//console.log(moxios.requests);
moxios.wait(() => {
console.log('after waiting for moxios..')
console.log(moxios.requests);
let request = moxios.requests.mostRecent();
request.respondWith({
status: 500,
response: { error: 'internal server error' }
});
});
})
it('dispatches LOAD_UNASSIGNED_JOBS_FAILURE', (done) => {
console.log(moxios.requests);
store.dispatch(jobActions.loadUnassignedJobs('unscheduled')).then(() => {
console.log(moxios.requests);
console.log(store.getActions());
expect(findAction(store, JobActionTypes.LOAD_UNASSIGNED_JOBS_FAILURE)).to.include({
type: JobActionTypes.LOAD_UNASSIGNED_JOBS_FAILURE
});
expect(findAction(store, JobActionTypes.LOAD_UNASSIGNED_JOBS_FAILURE).error).to.include({
message: 'Request failed with status code 500'
});
done();
});
});
it('does not dispatch LOAD_UNASSIGNED_JOBS_SUCCESS', (done) => {
store.dispatch(jobActions.loadUnassignedJobs('unscheduled')).then(() => {
expect(findAction(store, JobActionTypes.LOAD_UNASSIGNED_JOBS_SUCCESS)).to.be.undefined;
done();
});
});
})
});
});
describe('updateUnassignedJobs', () => {
it('assigns jobs to hash and creates an unassigned_job_ids array', () => {
expect(jobActions.updateUnassignedJobs([ { id: 1, step_status: 'all_complete' }, { id: 2, step_status: 'not_started' } ])).to.be.eql(
{
type: JobActionTypes.LOAD_UNASSIGNED_JOBS_SUCCESS,
jobs: { 1: { id: 1, step_status: 'all_complete' }, 2: { id: 2, step_status: 'not_started' } },
unassigned_job_ids: [ 1,2 ]
}
)
});
});
Found the issue!
The it() blocks for the success case were not using the done callback causing the afterEach() moxios.uninstall() to be called prematurely and not resetting the requests after the call was complete. Fixing this, and now all the tests pass.