Let's say we have the following suite:
describe('Devices', () => {
describe('Master Data Set-Up', () => {
it('should create the device if necessary', () => {
cy.createDevice()
its('body.id')
.as('deviceId');
});
});
describe('Test Suite 1', () => {
it('should allow to send data to device', () => {
cy.get('#deviceId').then((deviceId) => {
cy.sendData(deviceId, 'Some Data');
});
});
});
});
So, we have a set up suite that creates master data. This is a simplified version, actually it contains a couple of it specs and I'd like to keep it like that because it's better to read in the Cypress output.
Then, there is the actual test suite that want's to use data that has previously been created. In this case a server generated id that should be used for another REST call.
This is assuming, that cy.createDevice and cy.sendData are custom commands available that internally use cy.request.
When running that, cy.get('#deviceId') fails because aliases are not shared across describe blocks AFAIK. I tried to use let deviceId but it's undefined as it is not yet available when the test specs are processed.
What is a proper way to do this?
I believe this will be better solution, as cypress is asynchronous so it's better to write it on file and read it
describe('Devices', () => {
describe('Master Data Set-Up', () => {
it('should create the device if necessary', () => {
cy.createDevice()
......
cy.writeFile('deviceId.txt', body.id)
});
});
describe('Test Suite 1', () => {
it('should allow to send data to device', () => {
cy.readFile('deviceId.txt').then((device_id) => {
cy.sendData(device_id, 'Some Data');
})
});
});
});
Upvote for #ArekKhatry's idea, but to be safe I would obtain the the id in a before(). If you ever run tests in parallel, grabbing data from one test to use in another would be flaky.
Note that running cy.createDevice().its('body.id') in the before() still gives you the same test coverage as running inside it(), i.e it tests that the request succeeds and the return value has an id.
The file should be written to cypress/fixtures, otherwise it will write to the project root causing untidy pollution of the file structure.
Also, the id is returned from cy.request() as a number, but must be stringifyed in order to write to a text file.
Here's my variant
describe('Devices', () => {
before(() => {
cy.createDevice()
.its('body.id')
.then(id => {
cy.writeFile('cypress/fixtures/deviceId.txt', id.toString());
cy.log(`Created device: ${id}`);
});
});
describe('Test Suite 1', () => {
it('should allow to send data to device', () => {
cy.fixture('deviceId') // can use simpler cy.fixture here
.then(device_id => { // returned as a string here
const id = parseInt(device_id); // may need to parse to number?
cy.sendData(id, 'Some Data');
})
});
});
});
Ok, so first thanks to Aloysius and Arek for their answers. But I had the gut feeling that there must be some easier way to do this that writing an Id to a file.
As I mentioned before, I had issues with my first attempt to use a global variable:
I tried to use let deviceId but it's undefined as it is not yet
available when the test specs are processed.
I really wanted to understand, why this did not work and did some console debugging.
I added a console log:
describe('Devices', () => {
console.log('Loading test suites...')
(...)
});
When running the tests, I saw the log output twice, once after the first describe block where the device id was stored and then a second time after the master data was written.
Actually, I found out that this issue was cause by the following known Cypress issue:
https://github.com/cypress-io/cypress/issues/2777
After setting the baseUrl, it actually works:
describe('Devices', () => {
let deviceId;
before( () => {
Cypress.config('baseUrl', Cypress.env('system_url'))
cy.visit('/');
})
describe('Master Data Set-Up', () => {
it('should create the device if necessary', () => {
cy.createDevice()
.its('body.id')
.then((id) => {
deviceId = id;
});
});
});
describe('Test Suite 1', () => {
it('should allow to send data to device', () => {
cy.sendData(deviceId, 'Some Data');
});
});
});
Related
I am trying to automate my application with cypress. Have written few tests in my class and before visiting the site I have set up cookies (To login without entering username, password and otp). I have tried several ways to store these cookies and call before each of these tests but cookies work only for the 1st test and I am logged out for the subsequent tests.
Please find my code below. I am calling data using fixtures. It works fine only when i set sso and user agent for every test and I want to eliminate this.
describe('Watchlist Testsuite', () => {
let data;
before(() => {
cy.fixture('example').then((fdata) => {
data = fdata;
});
});
it('Setting up cookies', () => {
cy.setupCookies(data.sso, data.uid, data.userAgent, data.devId, data.url)
.then(() => cy.visit(data.url))
})
it('Enter Passcode', () => {
cy.setCookie('x-sso-token', data.sso)
cy.setCookie('x-user-agent', data.userAgent)
Cypress.config('pageLoadTimeout', 2000)
cy.enterPasscode(data.passcode)
Cypress.config('pageLoadTimeout', 20000)
cy.closeOKButton()
})
it('Create a watchlist', () => {
cy.setCookie('x-sso-token', data.sso)
cy.setCookie('x-user-agent', data.userAgent)
watchlistTest.createWatchlist(data.watchlistName)
})
Below are the ways with which I tried to preserve the cookies for all tests but none of them seem to be working.
Setting up cookies in beforeeach method
beforeEach('Setup cookies', ()=> {
cy.setupCookies(data.sso, data.uid, data.userAgent, data.devId, data.url)
.then(() => cy.visit(data.url))
})
Using cookies defaults
Cypress.Cookies.defaults
({
preserve:[data.sso, data.userAgent]
})
Using cy.session (I have set experimentalSessionAndOrigin to true)
it('setting up cookies',() => {
console.log('sso is ', data.sso)
const validate = () => {
cy.getCookie(data.sso).should('exist')
}
cy.session([window.sessionStorage.setItem('x-sso-token', data.sso),
window.sessionStorage.setItem('x-uid', data.uid),
window.sessionStorage.setItem('x-user-agent', data.userAgent),
window.sessionStorage.setItem('dev-id', data.devId)], {validate})
})
PS: I am using the Cypress 10.3.1 version.
Please guide me on the right approach to solve the issue.
I need a way to get all my ember tests' nameS, since I want to run them in a separate thread by using the --filter "TEST-NAME" flag.
Right now, I am using a hardcoded array.
It's not documented, but you can use the config object on the root QUnit object to get all modules in your test suite, then the tests within each module and their names. Note that any tests not in a module will still appear, but with an empty-named module. Here's a runnable jsfiddle with the example, then the code below.
QUnit.test('no-module', (assert) => { assert.equal(0,0) })
QUnit.module('foobar', () => {
QUnit.test('foo', (assert) => { assert.equal(1, 1) })
QUnit.test('bar', (assert) => { assert.equal(2, 2) })
})
QUnit.module('batbaz', () => {
QUnit.test('bat', (assert) => { assert.equal(3, 3) })
QUnit.test('baz', (assert) => { assert.equal(4, 4) })
})
const testNames = []
QUnit.config.modules.forEach((module) => {
testNames.push(...module.tests.map(test => test.name))
})
console.log(testNames)
We are using Cypress.io to build our automation suite. We have a requirement to seed our database before every test and to clear the data afterward. This could be done like below.
describe('Our test suite', function() {
before(function () {
//loadDbSeed is a custom command that will load the seed file based on the spec file
seed = cy.loadDbSeed()
cy.task('seed:db', seed)
})
it('should be true', function() {
//Some test with some action followed by an assertion
cy.visit('/some-page')
cy.get('[data-cy="identifier"]')
.click()
expect(true).to.equal(true)
})
after(function () {
// clearDb is a custom command that will clear out the DB.
// We are still debating if we must clear the DB after the tests.
// But we might still need to do some common actions for the entire suite after
cy.clearDb()
})
})
The problem we see is that the same before and after operations will be required for all our test suites. So we would like to override these methods so that our tests are something like this.
describe('Our test suite', function() {
before(function () {
// DB seeding is done automatically
// Write only custom before steps required for this test
})
it('should be true', function() {
//Some test with some action followed by an assertion
cy.visit('/some-page')
cy.get('[data-cy="identifier"]')
.click()
expect(true).to.equal(true)
})
after(function () {
// DB clearing is done automatically
// Write only custom after steps required for this test
})
})
How do we achieve this? I have been digging around in the Cypress code and haven't found anything obvious.
If you look at the Cypress docs, using after isn't recommended - Cypress Docs. I'd caution against setting data globally, will you really need it for every test? If you need to enter data on a per-test basis at some point, will that conflict with this global data? You could do something like this on a per test basis:
describe('Our test suite', function() {
beforeEach(function () {
cy.then(async () => {
await MyDatabaseService.resetdata()
await MyDatabaseService.createSomeData()
});
});
it('should be true', function() {
//Some test with some action followed by an assertion
})
})
I've also had to nest some tests as follows when specific tests needed specific data (sorry if some of the formatting here is a bit out, hopefully it'll make sense!):
describe('Our test suite', function() {
beforeEach(function () {
cy.then(async () => {
await MyDatabaseService.resetdata()
await MyDatabaseService.createSomeData()
});
});
it('should be true', function() {
//Some test with some action followed by an assertion
});
describe('These tests need more data, this is a nested describe', function () {
before(function () {
cy.then(async () => {
await MyDatabaseService.addSomeMoreData()
});
it('this test uses the extra data', function () {
// Do some funky tests here
});
});
});
})
For the second tests above, the test will run all three database actions.
Upshot is, if you clear data before you run your tests then it makes things much clearer.
I hope that helps. I'm new to Cypress myself and it can be hard to drop that (bad?) habits we've used for some time in Selenium!
I want to do something like this:
beforeEach(() => {
populateDatabase();
});
afterEach(() => {
clearDatabase();
});
describe("Create and update user test suite", () => {
let user;
it("Post a new user returns a user", async () => {
const initUser = {/*...*/};
user = await createUser(initUser);
//expect...
});
it("Modify the user returns the modified user", async () => {
user = await modifyUser({...user, ...{/*...*/});
//expect...
});
});
describe ("Create and update business test suit", () => {
let business;
//it...
});
That is, I want to retain the database state from test to test within the describe block, and then clear it down at the end the describe block.
But the way this code would work is that it would init and clear down after each test, which isn't what I want.
I could just put all the expects into one single test, but then I'd lose visibility of exactly which part of the test is failing.
What would the best way to handle this be?
To reinitialize your database after upon the completion of each describe block, check the beforeAll and afterAll methods.
Placing these methods inside the describe block will scope them to that specific block
last one cy.get('[data-cy=impact-area-table]').contains(impactareas.name).should('be.visible').click({force: true}); is not working though there's no any error ,it shows that it's fine and test pass but it doesnot open up the impact area ??
import { fillImpactAreaForm } from './utils';
import {contact, campaign, impactArea,impactareas} from '../support/commands.js';
describe('Fundraising test suite', function () {
beforeEach(() => {
cy.resetDb();
cy.visit('/');
});
it('should allow the user to create transactions', () => {
cy.seedOrgAndLogin().then(() => {
return cy.factoryCreate('PersonContacts', contact);
}).then(() => {
cy.factoryCreate('Campaigns', campaign);
}).then(() => {
cy.factoryCreate('ImpactAreas', impactArea);
}).then(() => {
cy.get('[data-cy="sidebar-Impact Areas"]').click({force: true});
cy.reload(true);
cy.get('[data-cy=create-impactarea]').click();
cy.get('[data-cy=impact-area-form]').contains('Close').click();
cy.get('[data-cy=create-impactarea]').click();
fillImpactAreaForm(impactareas);
cy. wait(2000);
cy.get('[data-cy=impact-area-table]').contains(impactareas.name).should('be.visible').click({force: true});
//cy.get('.content-scroll-wrapper.block-content').find('.content-scroll-body').contains(impactArea.name).click({force: true});
});
});
});
It's happening in 2 situations:
you don't have that item in your page or the dictation is different. (mention that cypress is case sensitive for .containt) or maybe your item is not visible.
you have more than one of this item. for example, you have 2 close in your page. it makes ambition to click on witch one. try to make it clear by adding more detail.