Cannot read properties of undefined (reading 'data') - cypress

This is my code
describe('Demo', () => {
before(() => {
cy.fixture('example.json').then(function (data) {
this.data = data
})
})
it('Fixture demo', () => {
cy.visit('https://admin-demo.nopcommerce.com/login')
cy.get('input[name=Email]').clear().type(this.data.email)
cy.get('input[name=Password]').clear().type(this.data.password)
cy.get('.login-button').click()
})
})
Error is showing in this line
cy.get('input[name=Email]').clear().type(this.data.email)
^

To fix please change arrow functions () => { to non-arrow functions function() {.
If using this you need to use function().
before(function() {
cy.fixture('example.json').then(function(data){
this.data=data
})
})
it('Fixture demo', function() {
cy.visit('https://admin-demo.nopcommerce.com/login')
cy.get('input[name=Email]').clear().type(this.data.email)
...
})

You are getting confused with component class approach, Cypress test file is not a class file, you shouldn't use this keyword here
To fix this you can declare a global variable for your data and use it later in your tests
describe('Demo', () => {
let exampleData; //Create variable here
before(() => {
cy.fixture('example.json').then(function (data) {
exampleData = data //Assign data here
})
})
it('Fixture demo', () => {
cy.visit('https://admin-demo.nopcommerce.com/login')
cy.get('input[name=Email]').clear().type(exampleData.email) //Use it like this
cy.get('input[name=Password]').clear().type(exampleData.password) //Use it like this
cy.get('.login-button').click()
})
})

Related

Using expect to compare values from .then(), returns undefined

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

JS Cypress: unable to use alias for array

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:

How to use getText inside a div and store it in a varaible in Cypress

cy.xpath('some xpath').then(($btn)=>{const text = $btn.text()})
I am currently using like this but i can use the text only inside .then() so i have problem of calling it outside .
Please tell me how to get the text and store in a variable.
e.g. var text= cy.xpath('some xpath') like this so that i can use it anywhere in inside the code.
You can use an alias using as (cypress docs) to store your innerText.
cy.xpath('some xpath').invoke('text').as('text')
Now later if you want to use it you can use it like:
cy.get('#text').then((text) => {
//Do something with text
})
Please be aware that all alias are cleared between tests.
This means if you do this
it('checks some xpath', () => {
cy.xpath('some xpath')
.then(($btn) => { return $btn.text()})
.as('myvar')
})
it('uses myvar', () => {
cy.get('#myvar').then((myvar) => {
const newvar = myvar + '2' // FAILS - "alias myvar has not been declared
})
})
Some better ways
Use alias from this scope
it('checks some xpath', () => {
cy.xpath('some xpath')
.then(($btn) => { return $btn.text()})
.as('myvar')
})
it('uses myvar', function() { // note "function"
const newvar = this.myvar + '2' // this.myvar is set by alias but NOT cleared
})
Use "back-flip" variable
let myvar;
it('checks some xpath', () => {
cy.xpath('some xpath')
.then(($btn) => { return $btn.text()})
})
it('uses myvar', () => { // arrow function is ok
const newvar = myvar + '2' // myvar is global to both tests
})

sinon fake server.requests returns an empty array

I'm not sure if I'm doing it correctly, I just followed their documentation.
I'm using mocha + sinonjs
actions.js
import axios from 'axios';
export const attachmentActions = {
getAttachment(id) {
axios.get('/api/attachment/${id}')
.then(response => {
console.log(respons);
})
.catch(error => {
console.log(error);
})
}
}
export const actions = {
...attachmentActions,
}
actions.test.js
describe('actions-test', () => {
let server;
beforeEach(() => {
server = sinon.createFakeServer();
});
afterEach(() => {
server.restore();
})
it('call getAttachment', () => {
server.requests[0].respond(200,
{'Content-type':'application/json'},
JSON.stringify({mimeType: 'image/png', path: '/user/image/'}));
actions.getAttachment(1)
//assertion
})
})
With the code above I got TypeError: Cannot read property 'respond' of undefined
I also tried below
it('call getAttachment', () => {
server.respondWith('GET','/api/attachment/1',[200,
{'Content-type':'application/json'},
JSON.stringify({mimeType: 'image/png', path: '/user/image/'}))];
actions.getAttachment(1);
server.respond();
//assertion
})
but when I run the test axios returns 404
Would really appreciate if someone can pinpoint if I miss something?
sinon version: 7.3.2

How can I test Observable.ajax (redux-observable)?

I have been playing with rxjs and redux-observable for the last few days and have been struggle to find a way to a test for Observable.ajax. I have the following epic which create a request to https://jsonplaceholder.typicode.com/,
export function testApiEpic (action$) {
return action$.ofType(REQUEST)
.switchMap(action =>
Observable.ajax({ url, method })
.map(data => successTestApi(data.response))
.catch(error => failureTestApi(error))
.takeUntil(action$.ofType(CLEAR))
)
}
where,
export const REQUEST = 'my-app/testApi/REQUEST'
export const SUCCESS = 'my-app/testApi/SUCCESS'
export const FAILURE = 'my-app/testApi/FAILURE'
export const CLEAR = 'my-app/testApi/CLEAR'
export function requestTestApi () {
return { type: REQUEST }
}
export function successTestApi (response) {
return { type: SUCCESS, response }
}
export function failureTestApi (error) {
return { type: FAILURE, error }
}
export function clearTestApi () {
return { type: CLEAR }
}
The code works fine when runs in browser but not when testing with Jest.
I have try,
1) Create a test based on https://redux-observable.js.org/docs/recipes/WritingTests.html. The store.getActions() returns only { type: REQUEST }.
const epicMiddleware = createEpicMiddleware(testApiEpic)
const mockStore = configureMockStore([epicMiddleware])
describe.only('fetchUserEpic', () => {
let store
beforeEach(() => {
store = mockStore()
})
afterEach(() => {
epicMiddleware.replaceEpic(testApiEpic)
})
it('returns a response, () => {
store.dispatch({ type: REQUEST })
expect(store.getActions()).toEqual([
{ type: REQUEST },
{ type: SUCCESS, response }
])
})
})
2) Create a test based on Redux-observable: failed jest test for epic. It returns with
Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
it('returns a response', (done) => {
const action$ = ActionsObservable.of({ type: REQUEST })
const store = { getState: () => {} }
testApiEpic(action$, store)
.toArray()
.subscribe(actions => {
expect(actions).to.deep.equal([
{ type: SUCCESS, response }
])
done()
})
})
Can someone point me out what is the correct way to test Observable.ajax ?
I would follow the second example, from StackOverflow. To make it work you'll need to make some minor adjustments. Instead of importing Observable.ajax in your epic file and using that reference directly, you need to use some form of dependency injection. One way is to provide it to the middleware when you create it.
import { ajax } from 'rxjs/observable/dom/ajax';
const epicMiddleware = createEpicMiddleware(rootEpic, {
dependencies: { ajax }
});
The object we passed as dependencies will be give to all epics as the third argument
export function testApiEpic (action$, store, { ajax }) {
return action$.ofType(REQUEST)
.switchMap(action =>
ajax({ url, method })
.map(data => successTestApi(data.response))
.catch(error => failureTestApi(error))
.takeUntil(action$.ofType(CLEAR))
);
}
Alternatively, you could not use the dependencies option of the middleware and instead just use default parameters:
export function testApiEpic (action$, store, ajax = Observable.ajax) {
return action$.ofType(REQUEST)
.switchMap(action =>
ajax({ url, method })
.map(data => successTestApi(data.response))
.catch(error => failureTestApi(error))
.takeUntil(action$.ofType(CLEAR))
);
}
Either one you choose, when we test the epic we can now call it directly and provide our own mock for it. Here are examples for success/error/cancel paths These are untested and might have issues, but should give you the general idea
it('handles success path', (done) => {
const action$ = ActionsObservable.of(requestTestApi())
const store = null; // not used by epic
const dependencies = {
ajax: (url, method) => Observable.of({ url, method })
};
testApiEpic(action$, store, dependencies)
.toArray()
.subscribe(actions => {
expect(actions).to.deep.equal([
successTestApi({ url: '/whatever-it-is', method: 'WHATEVERITIS' })
])
done();
});
});
it('handles error path', (done) => {
const action$ = ActionsObservable.of(requestTestApi())
const store = null; // not used by epic
const dependencies = {
ajax: (url, method) => Observable.throw({ url, method })
};
testApiEpic(action$, store, dependencies)
.toArray()
.subscribe(actions => {
expect(actions).to.deep.equal([
failureTestApi({ url: '/whatever-it-is', method: 'WHATEVERITIS' })
])
done();
});
});
it('supports cancellation', (done) => {
const action$ = ActionsObservable.of(requestTestApi(), clearTestApi())
const store = null; // not used by epic
const dependencies = {
ajax: (url, method) => Observable.of({ url, method }).delay(100)
};
const onNext = chai.spy();
testApiEpic(action$, store, dependencies)
.toArray()
.subscribe({
next: onNext,
complete: () => {
onNext.should.not.have.been.called();
done();
}
});
});
For the first way:
First, use isomorphic-fetch instead of Observable.ajax for nock support, like this
const fetchSomeData = (api: string, params: FetchDataParams) => {
const request = fetch(`${api}?${stringify(params)}`)
.then(res => res.json());
return Observable.from(request);
};
So my epic is:
const fetchDataEpic: Epic<GateAction, ImGateState> = action$ =>
action$
.ofType(FETCH_MODEL)
.mergeMap((action: FetchModel) =>
fetchDynamicData(action.url, action.params)
.map((payload: FetchedData) => fetchModelSucc(payload.data))
.catch(error => Observable.of(
fetchModelFail(error)
)));
Then, you may need an interval to decide when to finish the test.
describe("epics", () => {
let store: MockStore<{}>;
beforeEach(() => {
store = mockStore();
});
afterEach(() => {
nock.cleanAll();
epicMiddleware.replaceEpic(epic);
});
it("fetch data model succ", () => {
const payload = {
code: 0,
data: someData,
header: {},
msg: "ok"
};
const params = {
data1: 100,
data2: "4"
};
const mock = nock("https://test.com")
.get("/test")
.query(params)
.reply(200, payload);
const go = new Promise((resolve) => {
store.dispatch({
type: FETCH_MODEL,
url: "https://test.com/test",
params
});
let interval: number;
interval = window.setInterval(() => {
if (mock.isDone()) {
clearInterval(interval);
resolve(store.getActions());
}
}, 20);
});
return expect(go).resolves.toEqual([
{
type: FETCH_MODEL,
url: "https://test.com/assignment",
params
},
{
type: FETCH_MODEL_SUCC,
data: somData
}
]);
});
});
enjoy it :)

Resources