So I'm just starting to learn Typescript with Cypress and I noticed this in a page I was reading about here: https://medium.com/#NicholasBoll/cypress-io-scaling-e2e-testing-with-custom-commands-6b72b902aab
export const updateTodo = (name: string) => ($todo: JQuery) => {
cy.wrap($todo).within(() => {
cy.get('label').dblclick()
cy.get('.edit').clear().type(`${name}{enter}`)
})
So I'm still new to typescript and I understand I think that the Jquery element is the subject parameter although I'm still a little bit unsure at what would represent a "Jquery" element/parameter in Cypress.
However what confused me is the fact that there are two fat arrow parameter sections... I've not seen that before? What exactly does that mean? Especially when further down it's called as such (With just a string):
it('should update a todo', () => {
createTodo('Learn Cypress Command API')
.then(updateTodo('Learn Cypress composition'))
.then(getTodoName)
.should('equal', 'Learn Cypress composition')
})
Can anyone explain what's going on here?
Two fat arrows just means the function is returning another function.
So
export const updateTodo = (name: string) => ($todo: JQuery) => {
cy.wrap($todo).within(() => {
cy.get('label').dblclick()
cy.get('.edit').clear().type(`${name}{enter}`)
})
is equivalent to
export const updateTodo = (name: string) => { // outer function
return ($todo: JQuery) => { // inner function
cy.wrap($todo).within(() => {
cy.get('label').dblclick()
cy.get('.edit').clear().type(`${name}{enter}`)
})
Where you use the double-fat-arrow function is another shorthand,
This
.then(updateTodo('Learn Cypress composition'))
is shorthand for this
.then((name: string) => updateTodo(name)('Learn Cypress composition'))
where the double brackets updateTodo()() calls the outer function then the inner function.
Or even longer syntax:
.then((name: string) => {
const innerFn = updateTodo(name);
return innerFn('Learn Cypress composition');
})
Related
I am trying to check the items shown in my table for some assertions. My way is to put all of the items in an array and then iterate over that array.
My problem: All assertions already passed but the cypress runner still takes a lot of time to finish the cy.wrap(.invoke(text)) jobs.
Since this is a very core command of my cypress tests it would be great to have a more efficient function.
My command:
cy.get('table tbody').within(() => {
cy.get('tr').each((tr) => {
cy.wrap(tr.children().eq(index)).invoke('text').then((text) => {
text = text.trim();
arrayWithValuesOfTheList.push(text);
});
})
.then(() => {
//in here all the (quickly passing) assertions are...
});
});
Thanks for any help in advance. I appreciate you all!
You can avoid wrapping the value, will give some increase in speed but it's hard to say what is the slowest part.
const arrayWithValuesOfTheList = []
cy.get('table tbody tr')
.each($tr => {
arrayWithValuesOfTheList.push($tr.children().eq(index).text())
})
.then(() => {
//in here all the (quickly passing) assertions are...
})
})
You can do something like this. It gets the tr values one by one and matches in against a regex pattern.
cy.get('table tbody tr').each(($ele) => {
cy.wrap($ele.text().trim())
.should('match', /myregexp/)
.and('not.include', 'some text')
})
If you want to assert on individual cells, using .each($cell => {...}) is fine but if you want whole-column assertions (e.g sorted, unique-values) it gets difficult with .each().
To build something adaptable for various tests, take a look at the pattern here Sorting the table.
The idea is to create helper functions using .map() to selected table rows and columns.
const { _ } = Cypress
// helpers, reusable
const getColumn = (colIndex) => {
return (rows$) => {
const children$ = _.map(rows$, 'children')
return _.map(children$, `[${colIndex}]`)
}
}
const toStrings = (cells$) => _.map(cells$, 'textContent')
const toNumbers = (texts) => _.map(text, Number)
cy.get('table tbody tr') // rows of table
.then(getColumn(1)) // extract 2nd column
.then(toStrings) // get the text value
.then(toNumbers) // convert if assertions require numeric values
// whole-column assertion example
.should(values => {
const sorted = _.sortBy(values)
expect(values, 'cells are sorted 📈').to.deep.equal(sorted)
})
// individual value assertion
.should(values => {
values.forEach(value => {
expect(value).to.be.lt(100)
})
})
Addressing performance issue
If performance is poor, you can reduce the number of process steps at the Cypress command level by using jQuery-only commands.
This will avoid adding commands to the Cypress queue which is likely to be the slowest part
const arrayWithValuesOfTheList = []
cy.get('table tbody tr td:nth-child(2)') // 2nd col of each row
.then($tds => { // only jQuery methods inside
$tds.text((index, cellText) => {
arrayWithValuesOfTheList.push(cellText.trim())
})
})
.then(() => {
//in here all the (quickly passing) assertions are...
})
})
There is something about state hooks in React that I don't understand. I am dealing with the following situation in my code:
const [myArray, setMyArray] = useState(["foo"])
useEffect(() => {
console.log("myArray has been updated")
}, [myArray])
const clickUpdate = () => {
var myUpdatedArray = myArray
myUpdatedArray.push("bar")
setMyArray(myUpdatedArray)
}
This does not work. Calling the clickUpdate does not update myArray.
The problem is in the clickUpdate function. When I rewrite the function in the following way it works just fine:
...
const clickUpdate = () => {
var myUpdatedArray = [...myArray, "bar"]
setMyArray(myUpdatedArray)
}
or alternatively:
...
const clickUpdate = () => {
setMyArray(myArray=> [...myArray, "bar])
}
Either solution works for me just fine, but I am curious:
What is it about the .push mutation of myUpdatedArray that makes my setMyArray not work properly? Or what else is going on here that I am missing?
Thank you!
React compares the values in the deps array by reference, that's way it works when you create a new array.
If you are just going to add/remove items (not editing them) then you can be more performant and pass myArray.length to the deps list. Thus react will detect changes when the length is changed.
useEffect(() => {
console.log("myArray has been updated")
}, [myArray.length])
It probably is a matter of Promise:
Look at the since field of my return block,
using an arrow function it doesn't return any result:
{
link: 'www.xxxxxx.com/1234',
name: 'jhon doe',
since: {}
},
instead to return directly the value, it works as expected!
Since i need to perform complex operations with selectors, I'd like to use an inline arrow function in that point, how can I fix to get the result out?
let rawMembers = await page.evaluate(() => new Promise((resolve) => {
....
//captute all the link
const anchors = Array.from(document.querySelectorAll('a'));
let result = anchors.map(x => {
return {
link: x.getAttribute("href"),
name: x.innerText,
//since : x.parentNode.parentNode.parentNode.parentNode.getAttribute("class") <--- this works
since: x => { <---using an arrow function, it returns and empty objecy `{}`
// i need a function here to do complex and multiline operations
return x.parentNode.parentNode.parentNode.parentNode.getAttribute("class");
}
....
resolve(results);
i've tried this as well but with the same result
since: x => new Promise((resolve) => {
// i need a function here to do complex and multiline operations
resolve(x.parentNode.parentNode.parentNode.parentNode.getAttribute("class"));
})
In since you have a reference to the arrow function itself, not to its result. Functions are not serialaizable, so you get an empty object. You need to call the function, i.e. to use IIFE:
since: (() => {
return x.parentNode.parentNode.parentNode.parentNode.getAttribute("class");
})()
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's a generalized example:
// myActions.js
export const actionOne = () => (dispatch) => {
dispatch(actionTwo());
};
export const actionTwo = () => ({
type: 'SOME_TYPE',
});
I would like to test that actionTwo has been either called or dispatched, ideally without the test knowing anything about what is going on in actionTwo, because I have a different test that takes care of that.
I am using redux-mock-store to dispatch the tested action to a mocked store and calling store.getActions() to find out if the expected actions within the thunk action creator have been dispatched. I feel it is not the right way to go in this particular scenario because then the test would test more than it should. I really only want to know if actionTwo has been called at all.
I'm aware of spyOn and jest.mock, but I've been unable to use either to solve my problem. Here's what the generalized test looks like:
// myActions.test.js
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import * as actions from 'myActions';
const mockStore = configureMockStore([thunk]);
test('actionOne', () => {
const store = mockStore();
return store.dispatch(actions.actionOne()).then(() => {
// TODO: check if actions.actionTwo was called
});
});
test('actionTwo', () => {
const store = mockStore();
return store.dispatch(actions.actionTwo()).then(() => {
expect(store.getActions()).toEqual([{ type: 'SOME_TYPE' }]);
});
});
I'm grateful for any suggestions!
Took me a while, but I figured it out. It's not ideal (because it involves a small change to the tested code), but the closest to ideal that I could get.
// myActions.js
export const actionOne = () => (dispatch) => {
dispatch(exports.actionTwo());
};
export const actionTwo = () => ({
type: 'SOME_TYPE',
});
The important change is the exports.actionTwo(). That way, I make sure that I can overwrite the function's implementation from the outside (the test file) and the overwriting function will actually be called from within the imported file.
Now I can simply add something like the following to my test file:
beforeEach(() => {
actions.actionTwo = jest.fn(() => () => Promise.resolve());
});
actionTwo is now being mocked and I can use toBeCalledWith and other expectations on it. If I wish to test its actual implementation within the same test file, I can store it in a variable before calling beforeEach, like:
const actionTwo = actions.actionTwo;
And then in the test setup for its implementation, I can overwrite the mock calling
actions.actionTwo = actionTwo;
That's it. Now I can make sure to ignore all side effects from an exported function and test it as an actual unit.
It would be better to assert that two redux actions hit the store, not that actionOne calls the action creator.
Since all actions dispatched to the store must have an action type. Just make assertions about store.getActions():
test('actionOne', () => {
const store = mockStore();
return store.dispatch(actions.actionOne()).then(() => {
expect(store.getActions()).to.have.length(2);
expect(store.getActions()[0].type).to.equal('ACTION_ONE_TYPE');
// make additional assertions about the first action
expect(store.getActions()[1].type).to.equal('ACTION_TWO_TYPE');
});
});