Skip to main content

Network Requests in Cypress

 

Network Requests:

 

 What you’ll learn

  • How Cypress enables you to stub out the back end with cy.route()
  • What tradeoffs we make when we stub our network requests
  • How Cypress visualizes network management in the Command Log
  • How to use Fixtures to reuse XHR responses
  • How to use Aliases to refer back to XHR requests and wait on them
  • How to write declarative tests that resist flake

Note: If you’re looking for a resource to make an HTTP request take a look at cy.request()

Testing Strategies

Cypress helps you test the entire lifecycle of Ajax / XHR requests within your application. Cypress provides you direct access to the XHR objects, enabling you to make assertions about its properties. Additionally you can even stub and mock a request’s response.

🚨 Please be aware that Cypress only currently supports intercepting XMLHttpRequests. Requests using the Fetch API and other types of network requests like page loads and <script> tags will not be intercepted or visible in the Command Log. You can automatically polyfill window.fetch to spy on and stub requests by enabling an experimental feature experimentalFetchPolyfill. See #95 for more details and temporary workarounds.


Cypress also has a new experimental route2 feature that supports requests using the Fetch API and other types of network requests like page loads. For more information, check out the cy.route2() documentation.


Common testing scenarios:

  • Asserting on a request’s body
  • Asserting on a request’s url
  • Asserting on a request’s headers
  • Stubbing a response’s body
  • Stubbing a response’s status code
  • Stubbing a response’s headers
  • Delaying a response
  • Waiting for a response to happen

Within Cypress, you have the ability to choose whether to stub responses or allow them to actually hit your server. You can also mix and match within the same test by choosing to stub certain requests, while allowing others to hit your server.

Let’s investigate both strategies, why you would use one versus the other, and why you should regularly use both.

Use Server Responses

Requests that are not stubbed actually reach your server. 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.

When requests are not stubbed, this guarantees that the contract between your client and server is working correctly.

In other words, you can have confidence your server is sending the correct data in the correct structure to your client to consume. It is a good idea to have end-to-end tests around your application’s critical paths. These typically include user login, signup, or other critical paths such as billing.

There are downsides to not stubbing responses you should be aware of:

  • Since no responses are stubbed, that means your server has to actually send real responses. This can be problematic because you may have to seed a database before every test to generate state. For instance, if you were testing pagination, you’d have to seed the database with every object that it takes to replicate this feature in your application.
  • Since real responses go through every single layer of your server (controllers, models, views, etc) the tests are often much slower than stubbed responses.

If you are writing a traditional server-side application where most of the responses are HTML you will likely have few stubbed responses. However, most modern applications that serve JSON can take advantage of stubbing.

Benefits
  • More likely to work in production
  • Test coverage around server endpoints
  • Great for traditional server-side HTML rendering
Downsides
  • Requires seeding data
  • Much slower
  • Harder to test edge cases
Suggested Use
  • Use sparingly
  • Great for the critical paths of your application
  • Helpful to have one test around the happy path of a feature

Stub Responses

Stubbing responses enables you to control every aspect of the response, including the response body, the statusheaders, and even network delay. Stubbing is extremely fast, most responses will be returned in less than 20ms.

Stubbing responses is a great way to control the data that is returned to your client.

You don’t have to do any work on the server. Your application will have no idea its requests are being stubbed, so there are no code changes needed.

Benefits
  • Control of response bodies, status, and headers
  • Can force responses to take longer to simulate network delay
  • No code changes to your server or client code
  • Fast, < 20ms response times
Downsides
  • No guarantee your stubbed responses match the actual data the server sends
  • No test coverage on some server endpoints
  • Not as useful if you’re using traditional server side HTML rendering
Suggested Use
  • Use for the vast majority of tests
  • Mix and match, typically have one true end-to-end test, and then stub the rest
  • Perfect for JSON APIs

 Real World Example

The Cypress Real World App (RWA) end-to-end tests predominately rely on server responses, and only stub network responses on a few occasions to conveniently create edge-case or hard-to-create application states.

This practice allows the project to achieve full code-coverage for the front end and back end of the app, but this has also required creating intricate database seeding or test data factory scripts that can generate appropriate data in compliance with the business-logic of the app.

Check out any of the Real World App test suites to see Cypress network handling in action.

Stubbing

Cypress enables you to stub a response and control the bodystatusheaders, or even delay.

To begin stubbing responses you need to do two things.

  1. Start a cy.server()
  2. Provide a cy.route()

These two commands work together to control the behavior of your responses within the command’s options. cy.server() enables stubbing, while cy.route() provides a routing table so Cypress understands which response should go with which request.

See cy.server() options and cy.route() options for instructions on how to stub responses.

Requests

Cypress automatically indicates when an XHR request happens in your application. These are always logged in the Command Log (regardless of whether it’s stubbed). Cypress indicates when a request has started and when it is finished. Additionally, Cypress takes a snapshot of the DOM at the moment the request is made and another snapshot at the moment the response returns.

Snapshot of request and response

By default, Cypress is configured to ignore requests that are used to fetch static content like .js or .html files. This keeps the Command Log less noisy. This option can be changed by overriding the default filtering in the cy.server() options.

Cypress automatically collects the request headers and the request body and will make this available to you.

Routing

cy.server()           // enable response stubbing
cy.route({
  method: 'GET',      // Route all GET requests
  url: '/users/*',    // that have a URL that matches '/users/*'
  response: []        // and force the response to be: []
})

When you start a cy.server() and define cy.route() commands, Cypress displays this under “Routes” in the Command Log.

Routing Table

Once you start a server with cy.server(), all requests will be controllable for the remainder of the test. When a new test runs, Cypress will restore the default behavior and remove all routing and stubbing. For a complete reference of the API and options, refer to the documentation for each command.

Fixtures

A fixture is a fixed set of data located in a file that is used in your tests. The purpose of a test fixture is to ensure that there is a well known and fixed environment in which tests are run so that results are repeatable. Fixtures are accessed within tests by calling the cy.fixture() command.

With Cypress, you can stub network requests and have it respond instantly with fixture data.

When stubbing a response, you typically need to manage potentially large and complex JSON objects. Cypress allows you to integrate fixture syntax directly into responses.

cy.server()

// we set the response to be the activites.json fixture
cy.route('GET', 'activities/*', 'fixture:activities.json')

You can additionally reference aliases within responses. These aliases do not have to point to fixtures, but that is a common use case. Separating out a fixture enables you to work and mutate that object prior to handing it off to a response.

cy.server()

cy.fixture('activities.json').as('activitiesJSON')
cy.route('GET', 'activities/*', '@activitiesJSON')

Organizing

Cypress automatically scaffolds out a suggested folder structure for organizing your fixtures on every new project. By default it will create an example.json file when you add your project to Cypress.

/cypress/fixtures/example.json

Your fixtures can be further organized within additional folders. For instance, you could create another folder called images and add images:

/cypress/fixtures/images/cats.png
/cypress/fixtures/images/dogs.png
/cypress/fixtures/images/birds.png

To access the fixtures nested within the images folder, include the folder in your cy.fixture() command.

cy.fixture('images/dogs.png') //returns dogs.png as Base64

Waiting

Whether or not you choose to stub responses, Cypress enables you to declaratively cy.wait() for requests and their responses.

This following section utilizes a concept known as Aliasing. If you’re new to Cypress you might want to check that out first.

Here is an example of aliasing routes and then subsequently waiting on them:

cy.server()
cy.route('activities/*', 'fixture:activities').as('getActivities')
cy.route('messages/*', 'fixture:messages').as('getMessages')

// visit the dashboard, which should make requests that match
// the two routes above
cy.visit('http://localhost:8888/dashboard')

// pass an array of Route Aliases that forces Cypress to wait
// until it sees a response for each request that matches
// each of these aliases
cy.wait(['@getActivities', '@getMessages'])

// these commands will not run until the wait command resolves above
cy.get('h1').should('contain', 'Dashboard')

If you would like to check the response data of each response of an aliased route, you can use several cy.wait() calls.

cy.server()
cy.route({
  method: 'POST',
  url: '/myApi',
}).as('apiCheck')

cy.visit('/')
cy.wait('@apiCheck').then((xhr) => {
  assert.isNotNull(xhr.response.body.data, '1st API call has data')
})

cy.wait('@apiCheck').then((xhr) => {
  assert.isNotNull(xhr.response.body.data, '2nd API call has data')
})

cy.wait('@apiCheck').then((xhr) => {
  assert.isNotNull(xhr.response.body.data, '3rd API call has data')
})

Waiting on an aliased route has big advantages:

  1. Tests are more robust with much less flake.
  2. Failure messages are much more precise.
  3. You can assert about the underlying XHR object.

Let’s investigate each benefit.

Flake

One advantage of declaratively waiting for responses is that it decreases test flake. You can think of cy.wait() as a guard that indicates to Cypress when you expect a request to be made that matches a specific routing alias. This prevents the next commands from running until responses come back and it guards against situations where your requests are initially delayed.

Auto-complete Example:

What makes this example below so powerful is that Cypress will automatically wait for a request that matches the getSearch alias. Instead of forcing Cypress to test the side effect of a successful request (the display of the Book results), you can test the actual cause of the results.

cy.server()
cy.route('/search*', [{ item: 'Book 1' }, { item: 'Book 2' }]).as('getSearch')

// our autocomplete field is throttled
// meaning it only makes a request after
// 500ms from the last keyPress
cy.get('#autocomplete').type('Book')

// wait for the request + response
// thus insulating us from the
// throttled request
cy.wait('@getSearch')

cy.get('#results')
  .should('contain', 'Book 1')
  .and('contain', 'Book 2')

 Real World Example

The Cypress Real World App (RWA) has various tests for testing an auto-complete field within a large user journey test that properly await requests triggered upon auto-complete input changes. Check out the example:

Failures

In our example above, we added an assertion to the display of the search results.

The search results working are coupled to a few things in our application:

  1. Our application making a request to the correct URL.
  2. Our application correctly processing the response.
  3. Our application inserting the results into the DOM.

In this example, there are many possible sources of failure. In most testing tools, if our request failed to go out, we would normally only ever get an error once we attempt to find the results in the DOM and see that there is no matching element. This is problematic because it’s unknown why the results failed to be displayed. Was there a problem with our rendering code? Did we modify or change an attribute such as an id or class on an element? Perhaps our server sent us different Book items.

With Cypress, by adding a cy.wait(), you can more easily pinpoint your specific problem. If the response never came back, you’ll receive an error like this:

Wait Failure

Now we know exactly why our test failed. It had nothing to do with the DOM. Instead we can see that either our request never went out or a request went out to the wrong URL.

Assertions

Another benefit of using cy.wait() on requests is that it allows you to access the actual XHR object. This is useful when you want to make assertions about this object.

In our example above we can assert about the request object to verify that it sent data as a query string in the URL. Although we’re mocking the response, we can still verify that our application sends the correct request.

cy.server()
// any request to "search/*" endpoint will automatically receive
// an array with two book objects
cy.route('search/*', [{ item: 'Book 1' }, { item: 'Book 2' }]).as('getSearch')

cy.get('#autocomplete').type('Book')

// this yields us the XHR object which includes
// fields for request, response, url, method, etc
cy.wait('@getSearch')
  .its('url').should('include', '/search?query=Book')

cy.get('#results')
  .should('contain', 'Book 1')
  .and('contain', 'Book 2')

The XHR object that cy.wait() yields you has everything you need to make assertions including:

  • URL
  • Method
  • Status Code
  • Request Body
  • Request Headers
  • Response Body
  • Response Headers

Examples

cy.server()
// spy on POST requests to /users endpoint
cy.route('POST', '/users').as('new-user')
// trigger network calls by manipulating web app's user interface, then
cy.wait('@new-user')
  .should('have.property', 'status', 201)

// we can grab the completed XHR object again to run more assertions
// using cy.get(<alias>)
cy.get('@new-user') // yields the same XHR object
  .its('requestBody') // alternative: its('request.body')
  .should('deep.equal', {
    id: '101',
    firstName: 'Joe',
    lastName: 'Black'
  })

// and we can place multiple assertions in a single "should" callback
cy.get('@new-user')
  .should((xhr) => {
    expect(xhr.url).to.match(/\/users$/)
    expect(xhr.method).to.equal('POST')
    // it is a good practice to add assertion messages
    // as the 2nd argument to expect()
    expect(xhr.response.headers, 'response headers').to.include({
      'cache-control': 'no-cache',
      expires: '-1',
      'content-type': 'application/json; charset=utf-8',
      location: '<domain>/users/101'
    })
  })

Tip: you can inspect the full XHR object by logging it to the console

Comments

Popular posts from this blog

The terminal process "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" terminated with exit code: 259.

Integrated terminal in VS code crashes PowerShell: Solution of this Problem:- 1) Open your Project's/file location. 2)Click in this bar after opening the exact location of your project.                                                                                                                                         3)Now, Write 'cmd' in this bar.                                                                                                                     4)Your Terminal will be open as command, Now you can give any command here related to your Project. 5) Your project will be Executed/Start Normally in Terminal                                                                            6) You can ask any question.

Use of for Loop & return in Cypress Automation

  Use of  For Loop & return method in Cypress automation : describe(" Use Of For Loop and return method ",function(){   it ( 'Test Case 15' ,  function () { cy . visit ( 'www.exapmle.com/',{timeout:1000} );        cy . get ( '.tab-tools > .icon > .svg-inline--fa' ). click ({ force : true }); // Now this method will fetch the text which is already displayed & will save it.      cy . get ( '.last-updated-date > span' ). then (( $txt )  =>  {  const   txt  =  $txt . text ()      cy . log ( txt )                  // For loop will run 50 times & after each 3 seconds // it will check Visibility of element. // If it gets newly appeared element/Text It will be terminated before 50 runs var   i ;          for  ( i  =  0 ;  i  <  50 ;  i ++) {              cy . wait ( 3000 )                           cy . get ( '.last-updated-date > span' ). each (( $text )  =>  {   c

Test files sequence in Cypress Or Test file Order in Cypress

  In any case, I used the  testFiles  config in  cypress.json  to set the spec file run order:   You can use your file names. Only the files will be shown in cypress runner which you will define here "New_Api" is a Sub-folder in integration folder. All other are spec files. {      "baseUrl" : "https://exapmle.com" , "testFiles" :  [          "New_Api/*" ,          "Add_Product.spec.js" ,          "Add_Client.spec.js" ,          "Seller.spec.js" ,          "Deal_Status.spec.js" ,          "Buyer_Offer.spec.js" ,          "Buyer_2.spec.js" ,          "Deal_Status.spec.js"            ], }