Simplifying Cypress with Variables and Aliases: A Complete Guide
In this tutorial, you will learn how to deal with async commands, we will show you what aliases are and how you can use them to simplify your code. You will see why you rarely need to use variables when working with Cypress. Finally, we will show you how to use aliases for objects, elements and routes.
Return Values
If you are new to Cypress, you will find it difficult to work with the asynchronous nature of the APIs. However, Asynchronous APIs are now a part of JavaScript. They are supported by modern browsers and many core Node modules are also asynchronous.
The patterns that are going to be discussed in this tutorial is going to be useful for you, even when you are not working with Cypress.
It should be noted that you can’t assign or work with the return values of any Cypress command, rather when you are working with Cypress commands, the commands will be enequeued and they run asynchronously.
As such the code below will not work the way you think:
'''const button = cy.get('button')
const form = cy.get('form')
button.click()'''
Closures
If you want to access what each of the Cypress command yields, you will have to use .then(), which is quite familiar is you are used to native Promises in JavaScript:
'''cy.get('button').then(($btn) => {
// $btn is the object that the previous command
// will yield to us
})'''
Every nested command has access to the work that is done in the previous commands. This will end up reading very nicely.
'''cy.get('button').then(($btn) => {
// store the button's text
const txt = $btn.text()
// submit a form
cy.get('form').submit()
// compare the two buttons' text and
// make sure they are different
cy.get('button').should(($btn2) => {
expect($btn2.text()).not.to.eq(txt)
})
})
// these commands run after all of the other
// previous commands have finished
cy.get(...).find(...).should(...)'''
The commands that are outside of the .then() will not run until all of the nested commands finish.
Debugging
Using the .then()functions presents you an excellent opportunity to use the debugger.This can help you to effectively understand the order in which commands are run. This will also enable you to inspect objects that Cypress yields you in each command.
'''cy.get('button').then(($btn) => {
// inspect $btn <object>
debugger
cy.get('#countries').select('USA').then(($select) => {
// inspect $select <object>
debugger
cy.url().should((url) => {
// inspect the url <string>
debugger
$btn // is still available
$select // is still available too
})
})
})'''
Variables
Usually in Cypress you will rarely need to use const, let, or var. This is because whenever you are using closures you will always have access to the objects that are yielded to you without the need to assign them.
However, when you are dealing with mutable objects (i.e objects that change state). Let us give you an example:
HTML
'''<button>increment</button>
you clicked button <span id='num'>0</span> times
```
// app code
```let count = 0
```$('button').on('click', () => {
$('#num').text(count += 1)
})```
// cypress test code
```cy.get('#num').then(($span) => {
// capture what the value of num is right now
const num1 = parseFloat($span.text())
cy.get('button').click().then(() => {
// now capture that again
const num2 = parseFloat($span.text())
// make sure it is what we expected
expect(num2).to.eq(num1 + 1)
})
})'''
The reason we used const in this example is because the $span object is mutable. Whenever you need to compare mutable objects, you will have to store their values.
Aliases
The use of .then() callback functions to access the previous command values is great ,but what will happen when you are running a code in hooks like before or beforeEachas shown below:
'''beforeEach(() => {
cy.button().then(($btn) => {
const text = $btn.text()
})
})
it('that does not have the access to text', () => {
// how do we get the access to text ?!?!
})'''
In the above example, how will you get access to text?
To solve this problem, you could attempt to use let get access to it but that will cause you to do some ugly backflips. Instead, Cypress provides you with aliases (a very powerful construct to solve this problem). We examine how to use aliases next.
Sharing Context
The simplest way to use aliases is by sharing context, in order to alias something that you will like to share, you will have to use the .as() command.
Here is a rewrite of the previous code using aliases:
'''beforeEach(() => {
// alias the $btn.text() as 'text'
cy.get('button').invoke('text').as('text')
})
it('that it has access to text', function () {
this.text // is now available
})'''
Under the hood, aliasing basic objects and primitives will utilize Mocha’s shares context object: This implies that aliases are available as this.*
Mocha will automatically share contexts for us across all applicable hooks for each test.
Accessing Fixtures:
The most common use case for sharing context is when you are dealing with cy.fixture().
Most times, you may load a fixture in a beforeEach hook but wishes to utilize the values in your tests.
'''beforeEach(() => {
// alias the users fixtures
cy.fixture('users.json').as('users')
})
it('utilize the users in some way', function () {
// access the users property
const user = this.users[0]
// make sure that the header contains the first
// user's name
cy.get('header').should('contain', user.name)
})
})'''
Avoiding the use of this keyword
Rather than using the this.* syntax, you can access aliases by using cy.get().
'''beforeEach(() => {
// alias the users fixtures
cy.fixture('users.json').as('users')
})
it('utilize the users in some way', function () {
// use the special '@' syntax to access aliases
// which will avoid the use of 'this'
cy.get('@users').then((users) => {
// access the users argument
const user = users[0]
// make sure that the header contains the first
// user's name
cy.get('header').should('contain', user.name)
})
})'''
You should however note that both approaches have their use cases and have different ergonomics.
Elements
Aliases have some other special characteristics when they are used with DOM elements. Once you alias DOM elements, you will then be able to access them for reuse.
// alias all of the tr's that are found in the table as 'rows'
'''cy.get('table').find('tr').as('rows')'''
In the code snippet above, Cypress makes a reference to the <tr> collection that is returned as the alias rows. If you wish to reference these same rows later, you can choose to use cy.get() command.
Stale Elements
In most single-page JavaScript applications, the DOM will re-render parts of the application constantly. If you alias the DOM elements that have been from the DOM by the time you callcy.get() with the alias, Cypress will automatically re-query the DOM to find these elements again.
'''<ul id="todos">
<li>
Walk the dog
<button class="edit">edit</button>
</li>
<li>
Feed the cat
<button class="edit">edit</button>
</li>
</ul>'''
If the <li> is re-rendered when we click the .edit button, but rather than displaying the edit button it displays an text input field that allows you to edit the todo. In that case the previous has being replaced by a new <li> that has been rendered in its place.
'''cy.get('#todos li').first().as('firstTodo')
cy.get('@firstTodo').find('.edit').click()
cy.get('@firstTodo').should('have.class', 'editing')
.find('input').type('Clean the kitchen')'''
If we reference @firstTodo, Cypress will check to see if all the elements that is referencing still exists in the DOM. If they still exist, Cypress will return those existing elements, else Cypress will replay the commands leading up to the alias definition.
Routes
You can use aliases with routes, this will enable you to:
- ensure your application makes the intended requests
- wait for your server to send the response
- access the actual XHR object for assertions
An example is as shown below:
'''cy.server()
cy.route('POST', '/users', { id: 123 }).as('postUser')
cy.get('form').submit()
cy.wait('@postUser').its('requestBody').should('have.property', 'name', 'Brian')
cy.contains('Successfully created user: Brian')'''
Requests
You can also use aliases with requests. An example of aliasing a request and accessing its properties later is as shown below:
'''cy.request('https://jsonplaceholder.cypress.io/comments').as('comments')
// other test code are here
cy.get('@comments').should((response) => {
if (response.status === 200) {
expect(response).to.have.property('duration')
} else {
// whatever you want to check will be here
}
})
})'''
It will be nice if you may share this link in any developer community or anywhere else, from where other developers may find this content. Thanks.
https://www.w3resource.com/cypress/variables-and-aliases.php
- Weekly Trends and Language Statistics
- Weekly Trends and Language Statistics