An open source, front-end testing tool built for the modern web.
- Intro and Install
- Understanding cypress API libraries
- Understanding cypress features sets
- Working with cypress variables context, commands and debugging
- Understanding cypress async behaviour
- Understanding CI/CD
- Understanding Network request, Parallelization etc.
npm install cypress
npx cypress open
NB
npx
is a different way of execute npm package binaries.
Let's create a login form
<h1>Login</h1>
<form (ngSubmit)="submit()">
<div class="form-group">
<label for="exampleInputEmail1">Email address</label>
<input type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="Enter email" [(ngModel)]="login.email" name="email">
</div>
<div class="form-group">
<label for="exampleInputPassword1">Password</label>
<input type="password" class="form-control" id="exampleInputPassword1" placeholder="Password" [(ngModel)]="login.password" name="password">
</div>
<p>{{ login | json }}</p>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
Our Cypress test will look something like this:
/// <reference types="Cypress" />
describe('Actions', () => {
beforeEach(() => {
cy.visit('http://localhost:4200/home')
})
it('Login', () => {
cy.contains('Login');
})
})
NB without
/// <reference types="Cypress" />
it won't work because can't findcy
.
This is a bit generic because we are telling him where it should look for the string Login and we can also check if we are on the right path
it('Login', () => {
// cy.contains('Login');
cy.get('h1').contains('Login');
cy.url().should('include', 'home');
})
This is much better.
Cheap Tip: if you wanna run just one operation inside your test you can do
it.only('Understanding Cypress Wrap Commands', () => {
We can run a test from the terminal by running this command npx cypress run
!!! It works... however in this way is gonna execute all the testes but we re interested only in the login one.
Let's copy the full path inside our ide and type:
npx cypress run --spec /Users/whatever/development/cypress-course/cypress/integration/simple-cy-code/login.spec.ts
Awesome it's running just the login test as expected!
In the console we can also see that cypress is saving a video of our test:
- Started processing: Compressing to 32 CRF
- Finished processing: /Users/whatever/development/cypress-course/cypress/videos/simple-cy-code/login.spec.ts.mp4 (0 seconds)
we can also run this test in a specific browser by adding --browser chrome
just like this:
npx cypress run --spec /Users/valeriorisuleo/development/cypress-course/cypress/integration/simple-cy-code/login.spec.ts --browser chrome
We are going to discuss some of the most common cypress commands
Click()
Type()
Clear()
Check()
Select()
When we test an event listner we can pass also args
, and an argument it comes to use quite often is {force: true}
cy.get('.btn-primary').click({force: true});
It's very useful however keep in mind that every time we use it cypress will not perform these operations:
- Scroll the element into view
- Ensure it is visible
- Ensure it is not disabled
- Ensure it is not readonly
- Ensure it is not animating
- Ensure it is not covered
- Fire the event at a descendent
Each event listener has specific arguments:
Option | Default | Description |
---|---|---|
log |
true |
Displays the command in the Command log |
force |
false |
Forces the action, disables waiting for actionability |
multiple |
false |
Serially click multiple elements |
timeout |
defaultCommandTimeout |
Time to wait for .click() to resolve before timing out |
We are going to discuss some of the most common cypress commands
visit()
get()
wrap()
find()
parent()
filter()
each()
Filter Vs Find
filter()
Get the DOM elements that match a specific selector
find()
Get the descendent DOM elements of a specific selector, more like query behaviour of jQuery
it('selecting anyone of the list which has the name as Mark', () => {
// check if we are on the right element
cy.contains('Employees').click()
// find table with all rows
cy.get('.table')
.find('tr')
.filter('td')
})
Ooops...
CypressError: Timed out retrying: Expected to find element: 'td',
but never found it. Queried from element: [ <tr>, 3 more... ]
It's telling us that the tr
are 4 but the td
are actually 0.
it('selecting anyone of the list which has the name as Mark', () => {
cy.contains('Employees').click();
// find table with all rows
cy.get('.table')
// find td inside tr
.find('tr > td')
// check for Jacob and get its parent
.contains('Jacob').parent()
// get its associated Benefit and then click
.contains('Benefit').click();
})
The error is gone and if we inspect the elements we can reach all the td
In cypress everything is async so in order words we need a Promise and in order to resolve a promise we have to learn how to use the then()
method.
Cypress doesn't execute the code line by line, rather it stores everything in a queue:
Command Execution Order |
---|
cy.get('h1').contains('Login') |
cy.url().should('include', 'home') |
cy.get('#exampleInputEmail1').type('em@ga.co') |
cy.get('.btn-primary').click() |
That is why...
// ...this won't work...
// nope
const button = cy.get('button')
// nope
const form = cy.get('form')
// nope
button.click()
To access what each Cypress command yields you use then()
it('Login', () => {
cy.get('.btn-primary')
.then((el) => {
const btnTxt = el.text();
console.log(btnTxt);
});
})
Alias are very special constructor in cypress which has many different applications something like
- Accessible variables outside of
then()
, hooks, etc. - Working with UI elements
- Working with
cy.fixture()
- DDT - Working with Routes for XHR and API testing
it('Login', () => {
cy.get('.btn-primary')
.then((btn) => {
return btn.text();
}).as('btnText')
})
Whatever returns after we resolved the promise it will be store here as('btnText')
an alias and we can can access to it in this way:
it('Login', () => {
cy.get('.btn-primary')
.then((btn) => {
return btn.text();
}).as('btnText');
cy.get('@btnText')
.then((string) => {
expect(string).is.eql('Submit');
});
});
This is a the long way of working with Promise. Let's do some refactoring by use a cypress built in method invoke()
:
it('Login', () => {
cy.get('.btn-primary').invoke('text').as('btnText');
cy.get('@btnText')
.then((string) => {
expect(string).is.eql('Submit');
});
});
Note: Doing
return btn.text();
is the same thing of doing.invoke('text')
. In other words invoke is the shorthand for then.
Let's have a look at the wrap()
method. This time we want to click
on each row
in our table
.
First we wanna store the tr
into an alias of rows:
cy.get('.table').find('tr').as('rows');
and then...
it('Working with UI elements', () => {
cy.get('.table').find('tr').as('rows');
cy.get('@rows')
.then((rows) => {
cy.wrap(rows).click({multiple: true});
});
});
{multiple: true}
allows use to click on each row.
Yield the object passed into .wrap()
Meaning: anyone of the object that you pass within the wrap method will actually get its actual object properties and members out from it.
Different applications:
-
We can use it to check if an obj returning from a service has a property.
it('Understanding Cypress Wrap Commands', () => { cy.wrap({ name: 'Hulk'}) .should('have.property', 'name').and('eq', 'Hulk'); });
-
Remember when we did...
it('Cypress advanced commands', () => { cy.contains('Employees').click(); // find table with all rows cy.get('.table') // find td inside tr .find('tr > td') // check for Jacob and get its parent .contains('Jacob').parent() // get its associated Benefit and then click .contains('Benefit').click(); })
...now can do:
cy.get('.table').find('tr > td') .then((td) => { cy.wrap(td).contains('Jacob') .invoke('wrap').parent() .contains('Benefit').click(); });
There are 2 types of waiting mechanism available in Cypress.
- Explicit assertions -
expect()
- Implicit assertions -
should()
How these assertions work?
Cypress has a built in retry-ability in almost every command and especially, the wait mechanism isn assertions are really handy!
it('Working with Assertions', () => {
cy.get('jump to slide 2', {timeout: 6000})
.should('have.class', 'ls-nav-active');
});
it('Working with Assertions', () => {
cy.get('jump to slide 2', {timeout: 6000})
.should((el) => {
expect(el).to.have.class('ls-nav-active');
});
});
This is a powerful tool to pass data from an external file and we can do that in 2 very simple steps:
-
touch fixures/keychain.json
{ "email": "emma@ga.co", "password": "mypass" }
-
back to
fixture.spec.ts
describe('Actions', () => { beforeEach(() => { cy.fixture('keychain').as('user'); })
We get our data and make an alias. In this way we can access to it from outside just like this:
it('Login', () => { cy.get('@user').then((user) => { cy.get('#exampleInputEmail1').type(user.email); cy.get('#exampleInputPassword1').type(user.password); }); });
Cy comes with its own API for creating custom commands and overwriting existing commands.
custom commands are a great place to define or override commands in your cypress/support/command.js
file since it is loaded before any test files are evaluated via an import statement in cypress/support/index.js
Cypress.Commands.add(name, callbackFn)
Cypress.Commands.add(name, options, callbackFn)
Cypress.Commands.overwrite(name, callbackFn)
Cypress.Commands.overwrite(name, options, callbackFn)
XMLHttpRequest (XHR) is an API in the form of an object whose methods transfer data between a web browser and a web server. The object is provided by the browser's JavaScript environment. Particularly, retrieval of data from XHR for the purpose of continually modifying a loaded web page is the underlying concept of Ajax design. Despite the name, XHR can be used with protocols other than HTTP and data can be in the form of not only XML,[1] but also JSON,[2] HTML or plain text.[3] - Source: Wikipedia
Cypress provide you access direct to the XHR obj, enabling you to make assertions about its properties. Additionally you can even stub and mock a request's response.
By not stubbing your responses, you are writing true end-to-end tests. This means you are driving your application the same way a real user would. These typically include user login, signup, or other critical paths such as billing.
How to start XHR?
- Start the server
cy.server()
; - Start a routing
Note: For this particular lesson I built a
MEANapp
so you might need to install few things before starting.