Intro
What's the Cypress Playground?
The Cypress Playground is a web application created to teach test automation in a "hands-on" style.
What will I learn from using it?
Using the Cypress Playground, you will learn common use cases in test automation, with explanations and examples.
How can I practice test automation using the Cypress Playground?
You can practice by reading the content of each section and then trying to implement the explained concepts to test specific parts of the Cypress Playground app.
Remember:
Practice leads to perfection.
describe()
and
it()
In Cypress, test cases are organized in a test suite.
The most common way to define a test suite is using two different functions.
They are the mocha's describe()
and it()
functions.
They receive a string
as their first argument and a callback function as their second argument.
The describe
's first argument is the test suite description (e.g., 'Authentication', 'Products Search', 'Users List', etc.)
The test cases are defined inside the callback function (its second argument).
The it()
function defines a test case.
The it
's first argument is the test case description (e.g., 'successfully logs in,' 'searches for a nonexistent product,' 'lists the first ten users,' etc.)
The test implementation details are inside the callback function (its second argument).
Below is an example of the skeleton of a test suite with a couple of test cases.
describe('Authentication', () => {
it('successfully logs in', () => {
// test implementation here.
})
it('successfully logs out', () => {
// test implementation here.
})
})
Try it out by creating the skeleton of a test suite for the Cypress Playground app.
cy.visit()
When writing automated tests for web applications, the first step is to visit the URL of the application under test.
For that, Cypress offers the cy.visit()
command.
To visit a web page with Cypress, simply pass the desired URL as a string to the cy.visit()
command.
For example: cy.visit('https://google.com')
.
A good place to put the cy.visit()
command is inside of the callback function of the beforeEach()
hook.
For example:
beforeEach(() => {
cy.visit('https://example.com')
})
This way, you won't need to repeat the cy.visit()
command inside each test case since it will run before each it
block.
Try it out by updating your test suite to visit the Cypress Playground URL before each test case.
.click()
One of the most common tasks in testing web applications is to click a button to submit a form (for example.)
For that, Cypress offers the .click()
command.
To click on an element, you first have to get it, using the cy.get()
command, for example.
After you get it, you can chain a .click() to it.
For example: cy.get('button[type="submit"]').click()
.
You could also get the element using the cy.contains()
command.
For example: cy.contains('button', 'Subscribe').click()
.
Try it out by creating a test that clicks the Subscribe button. Make sure to add an assertion based on what happens after the click.
.type()
Another common task in testing web applications is to type into text fields, such as a textarea.
For that, Cypress offers the .type()
command.
Simply chain a .type()
to the element you want to type into, passing to it the text you want typed, as a string.
For example: cy.get('textarea').type('Cypress is awesome!')
.
Try it out by creating a test that types your name on the "Sign here" field. Make sure what is typed is displayed as a signature preview.
Signature's preview:
.check() and .uncheck()
Sometimes, you need to check (or uncheck) a checkbox.
For that, Cypress offers the .check()
and .uncheck()
commands.
Here's an example of checking a checkbox: cy.get('input[type="checkbox"]').check()
.
And here's how you would uncheck it: cy.get('input[type="checkbox"]').uncheck()
.
Try it out by creating a test that types your name on the "Sign here" field then checks the "Shows signature's preview" checkbox. Make sure the signature preview is displayed.
cy.get('input[type="radio"]')
.check()
.check()
For radio input fields, you can also use the .check()
command.
For example: cy.get(input[type="radio"]).check()
.
Try it out by creating a test that checks the On and Off radio buttons. Make sure the correct text is displayed depending on which radio is checked.
ON
.select()
Another element we usually need to intereact with in web apps is the dropdown select field.
For that, Cypress offers the .select()
command.
Simply chain the .select()
command to a select
HTML element, then pass to it the option you want to select.
The option can be chosen via its content
, value
, or index
.
Examples:
Selection via content: cy.get('select').select('Basic')
.
Selection via value: cy.get('select').select('standard')
.
Selection via index: cy.get('select').select(3)
.
Try it out by creating a test that selects one of the available types and make sure the correct type is displayed.
You haven't selected a type yet.
You might also want to interact with multiple options select fields.
For that, pass to the .select()
command an array of values.
For example: cy.get('select[multiple]').select(['apple', 'cherry'])
.
Try it out by creating a test that selects a few fuits and make sure the correct fruits are displayed.
You haven't selected any fruit yet.
.selectFile()
To select a file into an input of type file, Cypress offers the .selectFile()
command.
Simply pass to it the relative path of the file you want to select, based on the cypress.config.js
file.
For example:
cy.get('input[type="file"]')
.selectFile('cypress/fixtures/example.json')
Try it out by creating a test that selects a file and make sure the correct file name is displayed.
cy.intercept(...).as('alias')
and
cy.wait('@alias')
One of the most awesome features of Cypress, is it ability to "listen" to what's going on at the network level.
For that, Cyrpess offers the cy.intercept()
command.
You can define a route you want Cypress to intercept, give it an alias (using the .as('alias')
command), then you can run an action that triggers the request, and finally, wait for it before moving on, using cy.wait('@alias')
.
Here's a complete example:
cy.intercept('GET', 'https://api.example.com/todos/1').as('getTodo')
cy.contains('button', 'Get TODO').click() // this would trigger the above request.
cy.wait('@getTodo')
Try it out by creating a test that intercepts the request that is triggged after clicking the Get TODO button. Click the button and make sure to wait for the request to happen. Also, make sure a list is displayed.
You could even overwrite the request's response, using a fixture.
For example:
cy.intercept('GET', 'https://api.example.com/todos/1', { fixture: 'todo' }).as('getTodo')
cy.contains('button', 'Get TODO').click() // this would trigger the above request, but instead of making a real API call, a static file defined inside of the cypress/fixtures/
directory would be returned.
cy.wait('@getTodo')
Try it out by creating a test that intercepts the request that is triggged after clicking the Get TODO button, but this time, use a fixture as the request's response. Click the button and make sure to wait for the request to happen. Also, make sure a list is displayed with the same data as the fixture file.
With the cy.intercept()
command, you could even simulate an API failure.
For example: cy.intercept('GET', 'https://api.example.com/todos/1', { statusCode: 500 }).as('serverFailure')
.
Then, you could assert that it has really failed.
For example:
cy.wait('@serverFailure')
.its('response.statusCode')
.should('be.equal', 500)
And maybe, even assert that certain fallback element has been displayed.
For example: cy.contains('.error', 'Oops, something went wrong.').should('be.visible')
.
Try it out by creating a test that intercepts the request that is triggged after clicking the Get TODO button, but this time, simulate an API failure. Click the button and make sure to wait for the request to happen. Also, make sure a fallback element is displayed.
Finally, with the cy.intercept()
command, you could force a network error, to test how your web app would behave if there were no internet available, for instance.
For example: cy.intercept('GET', 'https://api.example.com/todos/1', { forceNetworkError: true }).as('networkError')
.
Then, you could wait for such a failure.
For example: cy.wait('@networkError')
.
And then, you could even assert that certain fallback element has been displayed.
For example: cy.contains('.error', "Oops, it seems you don't have internet connection.").should('be.visible')
.
Try it out by creating a test that intercepts the request that is triggged after clicking the Get TODO button, but this time, simulate an network failure. Click the button and make sure to wait for the request to happen. Also, make sure a fallback element is displayed.
cy.request()
Another great Cypress feature is the cy.request()
command.
Differently than the cy.intercept()
command (which "listens" to requests), with cy.request()
, you can make an HTTP request, like you would with a tool like Postman.
For example: cy.request('GET', 'https://api.example.com/todos/1')
.
After making the request, you could even run an assertion, to ensure it returned the correct status code.
For that, you could combine the .its()
and .should()
commands.
For example:
cy.request('GET', 'https://api.example.com/todos/1')
.its('status')
.should('be.equal', 200)
A good use for the cy.request()
command is when you have a public API that could be used for test data creation.
Try it out by creating a test that makes a request to 'https://jsonplaceholder.typicode.com/todos/1' and make sure the returned status code is 200.
.invoke().trigger()
Another element you might have to interact with when testing web apps is the input of type range.
To deal with such an element, you can combine two Cypress features.
You can use the .invoke()
command to set the input's value.
Then, you can use the .trigger()
command to trigger the value's change.
For example: cy.get('input[type="range"]').invoke('val', 5).trigger('change')
.
Try it out by creating a test that selects a level by invoking its value then triggers the change. Make sure the correct level is displayed.
.type('yyyy-mm-dd').blur()
Another element you might have to interact with when testing web apps is the input of type date.
To deal with such an element, you can combine two Cypress features.
You can use the .type()
command to set the input's value.
Then, you can use the .blur()
command to take the focus out of the element, for instance.
Note that the .type()
command expects the date to be in the yyyy-mm-dd
format.
For example: cy.get('input[type="date"]').type('2024-01-16').blur()
.
Try it out by creating a test that types a date and blurs the date field. Make sure the correct date is displayed.
Cypress.env('secret')
Sometimes, when testing web apps, we need to input some sensitive data.
However, it's not recommended to version such data, or even let it leak, hardcoding it in the test file.
To deal with that, Cypress offers a few options.
First, you can create a file called cypress.env.json
and add it to the .gitignore
file, so it won't be versioned.
Then, you can use the Cypress.env()
command to retrieve information from such a file.
For example:
cy.get('input[type="password"]')
.type(Cypress.env('password'))
With the above code example, you'd retrieve the value defined for the password
property of the cypress.env.json
file.
Try it out by creating a test that types into the password field based on sensitive data comming from the cypress.env.json
file. Check and uncheck the "Show password" checkbox and make sure the password is displayed and then masked.
Even so, with the above code example, although the password would not be hardcoded in the test file, it would still leak into the Cypress command logs.
To fix that, you could do this instead:
cy.get('input[type="password"]')
.type(Cypress.env('password'), { log: false })
Note that, as a second argument for the .type()
command, I'm passing an object with the property log
with the value of false
.
This way, the sensitive data doesn't leak into the Cypress command logs since such a command won't even be logged.
Try it out by creating a test that types into the password field based on sensitive data comming from the cypress.env.json
file. Make sure not to log the sensitve data in the Cypress command logs. Check and uncheck the "Show password" checkbox and make sure the password is displayed and then masked.
.should('have.length', n)
One common challenge when testing web applications is to assert on the number of specific elements.
Imagine you have a unordered list, and you want to assert that it has a specific number of list items.
To deal with that, Cypress offers the .should('have.length', n)
assertion.
Here's how it works: cy.get('ul li').should('have.length', 5)
.
This way, you can count how many elements were found for that specific selector, and assert against the number you expect them to be.
Try it out by creating a test that counts the number of animals in the below list and make sure it's 5.
- Camel
- Cat
- Caterpillar
- Cow
- Dog
cy.clock()
Sometimes, web apps show dynamic data, such as the current date.
If you want to assert on such data, you would want to freeze the date so your tests can provide you with deterministic results.
For that, Cypress offers the cy.clock()
command.
With the cy.clock()
command, Cypress can control the browser's clock.
For example:
beforeEach(() => {
const now = new Date(2024, 3, 15) // month is 0-indexed
cy.clock(now)
cy.visit('https://example.com')
})
Then, you could assert that the frozen date is displayed, for instance.
Like this: cy.contains('p', '2024-04-15').should('be.visible')
.
Try it out by creating a test that asserts on the displayed date. Make sure to freeze the browser's clock by updating the code in the beforeEach
hook.
.then()
Sometimes, you need to input the value of one element into another.
For that, Cypress offers the .then()
command.
The .then()
command enables you to work with the subject yielded from the previous command.
For example:
cy.get('#some-element')
.then(element => {
const value = element[0].innerText
cy.get('input[type="text"]').type(value)
})
The code above searches for an element (with the ID some-element
), and then passes it to the callback function of the .then()
command.
Within the body of the callback function, the internal text of such an element is obtained (which could be a dynamic value), and then this is typed into another field, using the combination of the commands cy.get()
and .type()
.
Try it out by creating a test that copies the below code, types it, and clicks the submit button. Make sure a success message is displayed.
Also, see what happens when the wrong code is typed. Make sure to create a test for that too, asserting an error message is shown.
cy.readFile()
Sometimes, you need to test downloading a file.
For that, Cypress offers the cy.readFile()
command.
The cy.readFile()
command enables you to read a file, as its name suggests.
A use case for that could be as follows.
You could click an element that downloads a file, and then, read the file to check if its content is correct.
Note: By default, Cypress downloads files inside the cypress/downloads/
directory.
Here's an example:
cy.contains('a', 'Download a text file').click()
cy.readFile('cypress/downloads/example.txt')
.should('be.equal', 'This is an example text file.')
Try it out by creating a test that clicks the "Download a text file" link. Make sure to read the file's content to check if it's correct.
Download a text file