I need to pass the url and other variable in multiple tests[it-function]. For 1st test code run successfully but for 2nd test it showing error. Is there any workaround or solution? My code is as follows
describe('Document Upload', function()
{
before(function () {
cy.fixture('Credential').then(function (testdata) {
this.testdata = testdata
})
})
//1st test
it('Login as manager',function()
{
const login = new loginPage()
cy.visit(this.testdata.baseUrl);
login.getUserName().type(this.testdata.userDocumentM)
login.getPassword().type(this.testdata.passwordDocumentM)
login.getLoginButton().click()
//Logout
login.getUser().click()
login.getLogout().click()
})
//2nd test
it('Create Documents',function()
{
const login = new loginPage()
cy.visit(this.testdata.baseUrl);
login.getUserName().type(this.testdata.userDocumentM)
})
})
The error is
I have tried with above and also using before function again but same error
before(function () {
cy.fixture('Credential').then(function (testdata) {
this.testdata = testdata
})
})
//2nd test
it('Create Documents',function()
{
const login = new loginPage()
cy.visit(this.testdata.baseUrl);
login.getUserName().type(this.testdata.userDocumentM)
})
Starting with Cypress version 12 Test Isolation was introduced. This now means the Mocha context (aka this) is completely cleaned between tests.
Mocha context
It used to be (undocumented) that the Mocha context could be used to preserve variables across tests, for example
before(function () {
cy.fixture("example.json").as('testdata') // preserved on "this"
})
it("check testdata 1", function () {
expect(this.testdata).to.not.be.undefined // passes
})
it("check testdata 2", function () {
expect(this.testdata).to.not.be.undefined // fails in Cypress v12
})
but now that does not work.
The use of Mocha context is a bit arbitrary anyway, and requires explicit use of function-style functions which is easy to forget, particularly in places like array method callbacks Array.forEach(() => {}).
You can still use the Cypress context to store data
before(function () {
cy.fixture("example").then(function (testdata) {
Cypress.testdata = testdata;
})
})
it("check testdata 1", function () {
expect(Cypress.testdata).to.not.be.undefined // passes
})
it("check testdata 2", function () {
expect(Cypress.testdata).to.not.be.undefined // passes
})
Note this is also undocumented and may change in the future.
Caching methods
Technically, the way to do this is to set the alias with beforeEach().
The cy.fixture() command caches it's value, so you do not get the read overhead for each test (see Fixture returns outdated/false data #4716)
There is also cy.session() for more complicated scenarios, which would be officially supported.
beforeEach(() => {
cy.session('testdata', () => {
cy.fixture("example").then(testdata => {
sessionStorage.setItem("testdata", testdata)
})
})
})
it("check testdata 1", function () {
expect(sessionStorage.getItem("testdata")).to.not.be.undefined
})
it("check testdata 2", function () {
expect(sessionStorage.getItem("testdata")).to.not.be.undefined
})
Lastly, cypress-data-session which fills a gap
From the docs
Feature
cy.session
cy.dataSession
Command is
official ✅
community 🎁
Can cache
the browser session state
anything
Stability
experimental !!! not in v12
production
Cache across specs
yes
yes
Access to the cached data
no ???
yes
Custom validation
no
yes
Custom restore
no
yes
Dependent caching
no
yes
Static utility methods
limited
all
GUI integration
yes
no
Should you use it?
maybe
yes
Cypress version support
newer versions
all
Cypress.env()
This is another way that is officially supported,
before(() => {
cy.fixture("example").then(testdata => {
Cypress.env("testdata", testdata)
})
})
it("log my fixture 1", function () {
expect(Cypress.env("testdata")).to.not.be.undefined // passes
})
it("log my fixture 2", function () {
expect(Cypress.env("testdata")).to.not.be.undefined // passes
})
but there are still certain tests scenarios that reset the browser completely where this may not work.
Related
I have created Cypress e2e tests that use the following functions:
to mock the responses
export function getUserAndSupplier(): void {
cy.intercept('GET', `${Cypress.env('BaseUrl')}/users/me`,
{
fixture: 'shared/Users/me.json',
})
.as('users');
cy.intercept('GET', `${Cypress.env('BaseUrl')}/users/me/supplier`,
{
fixture: 'shared/Suppliers/supplier.json',
})
.as('supplier');
}
to check if responses are in accordance to the fixtures:
export function checkUserAndSupplier(): void {
cy.wait('#users')
.its('response.body')
.should('not.be.undefined')
.then((interception: any) => {
//assertions on each field
});
cy.wait('#supplier')
.its('response.body')
.should('not.be.undefined')
.then((interception: GetCurrentSupplierResponse) => {
//assertions on each field
});
}
Tests have Cucumber preprocessor implemented, the GIVEN and WHEN steps definition for given test are:
beforeEach(() => {
// intercept user and supplier api
getUserAndSupplier();
// intercept GET /paymentProviders
interceptPaymentProviders();
});
Given('User navigates to the {string} page', () => {
cy.visit('/sell/payment-providers');
// assert api calls on user and supplier
checkUserAndSupplier();
});
When('User clicks on {string} button', () => {
getActivationButton()
.scrollIntoView()
.contains('Activate')
.should('be.visible')
.and('not.be.disabled')
.click();
// Ensure Continue Button is disabled
getContinueButton()
.should('be.visible')
.and('be.disabled');
});
while the .feature file test is:
Scenario: Happy path - activate payment method
Given User navigates to the "sell/payment-providers" page
When User clicks on "activate" button
Then User is able to successfully activate payment provider
The problem is that sometimes, despite having responses mocked using fixtures (they're not null or empty), 'response.body' property is undefined, which makes tests flaky.
At the beginning I thought I have some asynchronous functions that lack await and make response.body being undefined, but this was not the case.
What may be the cause of this? And what makes it sometimes working, and sometimes not?
It's hard to tell what exactly is going on, the code looks ok.
Here's some general tips to try.
Cache
It's possible the browser cache is interfering with the intercept. To avoid caching, add this
beforeEach(() => {
Cypress.automation('remote:debugger:protocol', {
command: 'Network.clearBrowserCache'
})
...
})
Debug call sequence
To debug the network calls, combine the two intercepts and use callbacks to console.log what gets intercepted.
If something is changing the order of response, the problem may be caused by the sequence of cy.wait('#users') followed by cy.wait('#supplier') so combining the intercepts will catch that.
export function getUserAndSupplier(): void {
cy.intercept('/users*', (req) => {
if (req.url.endsWith('/me')) {
console.log('users request', req)
req.alias = 'users'
req.reply({fixture: 'shared/Users/me.json'})
}
if (req.url.endsWith('/me/supplier')) {
console.log('supplier request', req)
req.alias = 'supplier'
req.reply({fixture: 'shared/Suppliers/supplier.json'})
}
})
}
Or use a single alias for both paths and check inside the interception.
export function getUserAndSupplier(): void {
cy.intercept('/users*', (req) => {
if (req.url.endsWith('/me')) {
req.reply({fixture: 'shared/Users/me.json'})
}
if (req.url.endsWith('/me/supplier')) {
req.reply({fixture: 'shared/Suppliers/supplier.json'})
}
})
.as('both')
}
export function checkUserAndSupplier(): void {
const checkInterception = (interception) => {
if (interception.request.url.endsWith('/me')) {
console.log('users response', interception.response)
// assertions for users
}
if (interception.request.url.endsWith('/me/supplier')) {
console.log('supplier response', interception.response)
// assertions for supplier
}
}
cy.wait('#both').then(checkInterception); // first interception
cy.wait('#both').then(checkInterception); // second interception
}
Lastly, something in interceptPaymentProviders() is interfering with the other intercepts.
I'm trying to use fixtures to hold data for different tests, specifically user credentials. This is an example of the code. When it gets to the second test I'm getting 'Cannot read properties of undefined (reading 'data')'.
Any ideas why and how I can get around that? Is that wrong?
before(function () {
cy.fixture('credentials').then(function (data) {
this.data = data;
})
})
it('Login correct', () => {
cy.visit('/')
loginPage.signIn(this.data.admin.username,this.data.admin.password)
cy.wait(5000)
// assertion
cy.contains('Dashboard').should('be.visible')
})
And here is my credentials.json file:
{
"admin": {
"username": "*****",
"password": "*****"
}
}
Try using closure variables to assign fixture data.
describe('Some Test', () => {
let data; //closure variable
before(() => {
cy.fixture('credentials').then((fData) => {
data = fData;
});
});
it('Login correct', () => {
cy.visit('/')
loginPage.signIn(data.admin.username, data.admin.password) //usage of closure variable to get the values from the fixtures
cy.wait(5000)
// assertion
cy.contains('Dashboard').should('be.visible')
});
});
Gleb Bahmutov also recommends using closure variables.
I strongly recommend using closure variables instead of this properties. The closure variables are clearly visible and do not depend on function vs () => {} syntax.
As per the cypress docs:
If you store and access the fixture data using this test context
object, make sure to use function () { ... } callbacks. Otherwise, the
test engine will NOT have this pointing at the test context.
So, your it block should also use function:
before(function () {
cy.fixture('credentials').then(function (data) {
this.data = data
})
})
it('Login correct', function () {
cy.visit('/')
loginPage.signIn(this.data.admin.username, this.data.admin.password)
cy.wait(5000)
// assertion
cy.contains('Dashboard').should('be.visible')
})
The above answes are correct. One more way to do the above operation is use cypress.json instead other json files.
In your cypress.json u can add the credentials :
{
"Env": {
"username": "*****",
"password": "*****"
}
}
and refere json directy in your it fucntion.
it('Login correct', function () {
cy.visit('/')
loginPage.signIn(Cypress.env('username'), Cypress.env('password'))
cy.wait(5000)
// assertion
cy.contains('Dashboard').should('be.visible')
})
Trying run a test case for the following:
async getParents() {
const { user, services, FirmId } = this.props;
let types = await Models.getAccounts({ user, services, firmId: FirmId });
let temp = types.map((type) => {
if(this.state.Parent_UID && this.state.Parent_UID.value === type.Account_UID) {
this.setState({Parent_UID: {label: type.AccountName, value: type.Account_UID}})
}
return {
label: type.AccountName,
value: type.Account_UID,
}
})
this.setState({ParentOptions: temp});
}
here is what i have so far for my test:
beforeEach(() => wrapper = mount(<MemoryRouter keyLength={0}><AccountForm {...baseProps} /></MemoryRouter>));
it('Test getParents function ',async() => {
wrapper.setProps({
user:{},
services:[],
FirmId:{},
})
wrapper.find('AccountForm').setState({
SourceOptions:[[]],
Parent_UID: [{
label:[],
value:[],
}],
});
wrapper.update();
await
expect(wrapper.find('AccountForm').instance().getParents()).toBeDefined()
});
If i try to make this ToEqual() it expects a promise and not anobject, what else could I add into this test to work properly.
Goal: Make sure the functions gets called correctly. The test is passing at the moment and has a slight increase on test coverage.
Using Jest and Enzyme for React Js
you can put the await before the async method, like:
await wrapper.find('AccountForm').instance().getParents()
and compare if the state was changed.
In another way, if can mock your API request, because this is a test, then you do not need the correct API, but know if the function calls the API correctly and if the return handling is correct.
And, you cand spy the function like:
const spy = jest.spyOn(wrapper.find('AccountForm').instance(), 'getParents');
and campare if the function was called if they are triggered by some action:
expect(spy).toBeCalled()
Running Cypress 3.1.1 with cypress-cucumber-preprocessor 1.5.1. I need to pass some static data from one step to another (in the same scenario/test). I can do this using an alias, like this:
cy.wrap(someString).as('myString'), but then I have to access it asynchronously:
cy.get('#myString').then(myString => ...)
This is rather cumbersome, particularly when I have to pass multiple values, requiring multiple wrapped closures, for no apparent benefit. (Currently I'm working around this by aliasing an object, but I shouldn't need to do this.)
How can I pass primitive values from one step to another synchronously?
I thought I might be able to simply set this.myString='' to set the value on the Mocha shared context object, but in that case, the property exists but is set to undefined when accessed in later steps.
Even creating my own context variable with let outside of the step definition does not work. Is this simply a limitation of Cypress and/or the cypress-cucumber-preprocessor?
I managed to get it working the following way:
Add 2 tasks to the /plugins/index.js
const testStore = {}
module.exports = (on, config) => {
on('task', {
pushValue({ name, value }) {
console.log(name, value)
testStore[name] = value
console.log(testStore)
return true
},
})
on('task', {
getValue(name) {
return testStore[name]
},
})
Then you can add a variable in any test and reach it in any other place:
it('test', ()=>{
cy.task('pushValue', { name: 'orderNumber', value: orderNumber })
})
it('test 2', ()=>{
cy.task('getValue', 'orderNumber').then((order) => {
cy.visit(`/bookings/${order}`)
})
})
Here is a slightly more complicated (and not fully tested) method. A custom command can be added to save values to a global object.
In the Cypress test runner, all the tests seem to run sequentially, but you may have to be careful if using CI and parallel execution.
In /support/commands.js
export const testStore = {}
Cypress.Commands.add('saveAs', { prevSubject: true }, (value, propName) => {
console.log('saveAs', value, propName)
testStore[propName] = value;
return value;
})
In myTest.spec.js
import { testStore } from '../support/commands.js'
...
it('should have a title', () => {
cy.title()
.saveAs('title') // save for next test
.should('contain', 'myTitle) // this test's expectation
});
it('should test something else', () => {
cy.get('.myElement').contains(testStore.title);
});
Here are 2 samples of the same test. The only difference is that first one uses a promise in beforeAll block to assign a value to the variable while the second one assigns the value directly.
I raised a similar question Running spec after promise has been resolved with one of the comments pointing to this issue https://github.com/jasmine/jasmine/issues/412 which says that this is not supported in Jasmine. Has somebody figured out any workaround?
This fails with TypeError: Cannot read property 'forEach' of undefined
describe('Async car test', function () {
var cars;
beforeAll(function (done) {
// getCars() is a promise which resolves to ['audi', 'bmw']
getCars().then(function (data) {
cars = data;
console.log(cars) // ['audi', 'bmw']
done();
});
});
cars.forEach(function (car) {
it('car ' + car, function () {
expect(car).toBe(car);
});
});
});
This works fine
describe('Car test', function () {
var cars = ['audi', 'bmw'];
cars.forEach(function (car) {
it('car ' + car, function () {
expect(car).toBe(car);
});
});
});
Posting it as an answer, because I can't see things properly in comments.
I'm actually generating tests in my spec as well, and I'm using https://www.npmjs.com/package/jasmine-data-provider , I think you probably cannot generate it directly from resolved promise. And wrapping in another it doesn't work for you. This should work:
var using = require('jasmine-data-provider');
using(cars.forEach, function (car) {
it(car + ' should be' + car, function () {
expect(car).toBe(car);
});
});
This is not an issue with jasmine, it is an issue with your code.
beforeAll does not block subsequent code below the statement. it blocks code that is defined in it('should ...', (done)=>{...});
it('should have cars', (done) => {
cars.forEach(function (car) {
expect(car).toBe(car);
});
});
Since Jasmine does not support adding tests at runtime, the trick is to request the asynchronous data before starting Jasmine, and then using the retrieved data during runtime instead. This can be achieved with a singleton and programmatically starting Jasmine.
See here for a working example.
// car-collection.js
class CarCollection {
static load() {
return this.request()
then((data) => this.cars = data);
}
static request() {
// in practice this function would do something cooler
return Promise.resolve(['audi', 'bmw']);
}
}
modules.export = CarCollection;
Since CarCollection has methods that are static they will be shared across imports and this.cars will persist.
// launcher.js
const Jasmine = require('jasmine');
const CarCollection = require('./car-collection');
CarCollection.load()
.then(() => {
console.log(`car count is ${CarCollection.cars.length}`); // prints: car count is 2
const jasmine = new Jasmine();
jasmine.loadConfigFile(...); // path to jasmine.json
jasmine.execute();
});
An important step here is configure jasmine to know where to look for the test files. Either by loading a config or passing specifics into the execute function.
// car.spec.js
const CarCollection = require('./car-collection');
describe('test', function () {
CarCollection.cars.forEach((car) => {
it('test' + car, () => {
expect(car).toBe(car);
});
});
});
Now run node ./launcher.js and the tests should run.