How To Get Started With Cypress Debugging

Sonikasinghh - Nov 18 '22 - - Dev Community

One of the most important tasks of a software developer is not just writing code fast; it is the ability to find what causes errors and bugs whenever you encounter one and the ability to solve them quickly.

According to The Cost of Poor Software Quality in the US: A 2020 Report by Herb Krasner of CISQ, the cost of bugs is estimated at roughly $607 billion only for the United States.

This includes all the costs relating to unsuccessful projects and maintaining legacy systems, but it doesn’t include some other aspects like security issues, technical debt, and some other issues that add up to a huge price of $2 trillion.

Even though we are not building applications with Cypress, we’re only testing them; it is still important to keep our test suites away from bugs as much as possible.

And if we introduce bugs, we should have the ability to spot them and fix them as fast as possible.

That is why Cypress does a pretty good job of providing developers with tools and commands that make it easy to debug the test suites easily.

In this Cypress testing tutorial on Cypress debugging, I will talk about some of the best ways to debug your Cypress tests and explain each in detail. I will also point out some pitfalls you might get into and explain how Cypress actually works and why some things are not happening very intuitively.

So, let’s get started!

Using debugger in Cypress

Sometimes, when running tests, especially large and complicated ones, it could be very difficult for you to know what exactly went wrong and which part of the code failed to do as expected.

The debugger is very helpful in these kinds of situations. You can stop the test execution at any time and look up the application’s state to see if everything works as it should.

If you have been in the development field for quite a while, you might already know how to use the debugger in other parts of your applications.

However, if you are unfamiliar with Cypress debugging, using the debugger in Cypress might work differently than you think it should.

Even though using a debugger in Cypress is quite similar to using the debugger in other parts of your application (like your front end), it doesn’t work the same way.

You can only use the debugger in Cypress inside the.then() function. Otherwise, it does not work the way it is supposed to.

Let me explain…

Let’s see what goes wrong if we add the debugger outside a .then() function.

it('should work as usual', () => {
  cy.visit('https://ecommerce-playground.lambdatest.io')

  cy.get('[data-test="some-selector"]')
  debugger // Doesn't work
})
Enter fullscreen mode Exit fullscreen mode

Running the test:

As you can see, the debugger will pause the test to run before the test even starts.

If you have used a debugger before, your expectation might be that first, the cy.visit() and cy.get() functions should run and complete, and then the debugger should pause the test, but that’s not the case.

So, what happened there? Why did the test pause immediately?

Based on the Cypress documentation, it is said that the commands in Cypress are all “Asynchronous,” so what does that mean?

In Cypress, when a set of commands are invoked, they get enqueued for later, and after the whole, it() block is finished, Cypress will run all of the commands in order.

it('hides the thing when it is clicked', () => {
  cy.visit('/my/resource/path') // Nothing happens yet

  cy.get('.hides-when-clicked') // Still nothing happening
    .should('be.visible') // Still absolutely nothing
    .click() // Nope, nothing
    .should('not.be.visible') // Definitely nothing happening yet
})

// Ok, the test function has finished executing...
// We've queued all of these commands and now
// Cypress will begin running them in order!
Enter fullscreen mode Exit fullscreen mode

Based on this information, now you should know why the debugger pauses the test immediately instead of pausing the test after the other two commands have finished running.

The two commands that come before the debugger are only being enqueued to run later, but the debugger, which is not a Cypress command, will not be enqueued and will be invoked immediately.

Causing the test to pause immediately before the other commands can be executed in the queue.

That’s why you should always use the debugger inside the .then() function. This will ensure that the Cypress command will run and complete, and then the debugger will pause the execution of the tests.

Now, let’s re-write the test again, but this time using the debugger inside of the .then() function

it('should only pause when the cy.get() function finishes executing', () => {
  cy.visit('https://ecommerce-playground.lambdatest.io/')

  cy.get('h2')
    .should('exist')
    .then($h2 => {
      debugger
    })
})
Enter fullscreen mode Exit fullscreen mode

In this section of this tutorial on Cypress debugging, the debugger will only pause the test if the h2 element exists in the DOM since we are saying that the debugger should only pause the test only if the h2 element exists in the DOM.

Now, let’s run the test.

As you can see, now the debugger only pauses the test after it gets the element and makes sure that the element does exist in the DOM; otherwise, it will throw an error.

Let’s fail the assertion on purpose and see if the .then() function runs or not

it('should only pause when the cy.get() function finishes executing', () => {
  cy.visit('https://ecommerce-playground.lambdatest.io/')

  cy.get('[data-cy="some-selector-which-does-not-exist"]')
    .should('exist')
    .then($h1 => {
      debugger
    })
})
Enter fullscreen mode Exit fullscreen mode

Running the test:

As you can see from the screenshot above, the debugger will never pause the test since the assertion has failed and the .then() block did not have a chance to be executed.

Run your Jest testing in massive parallel across multiple browser and OS combinations with LambdaTest, Read more!

Using the .debug() method for Cypress debugging

The .debug() method is a shortcut for Cypress debugging, which you can chain to every other Cypress command wherever in your tests.

It will expose some details in the browser’s console when the .debug() function triggers:

  • Command Name: the name of the last command invoked before the .debug() triggered.

  • Command Args: the list of arguments passed to the last method before the .debug() invocation.

  • Current Subject: a new variable will be created inside the browser with the name subject, which you can interact with using the browser’s console.

The subject variable is the return value of the Cypress command and can be interacted with by using the browser’s console.

Let’s try it out. In this example, I will get the first h2 element on the page.

it('should pause the test by using the .debug() command', () => {
  cy.visit('https://ecommerce-playground.lambdatest.io/')

  cy.get('h2')
    .should('exist')
    .debug() // debugger
})
Enter fullscreen mode Exit fullscreen mode

Execution:

In this case, the subject variable represents the return value of the cy.get('h2') command, which means the subject variable is an h2 element.

When we run the subject.text(), this will give us the text content inside the h2 element.

Using stack trace for Cypress debugging

A stack trace shows a list of method calls that lead to the exception being thrown, together with the filenames and line numbers where the calls happened.

Cypress also translates the stack trace so that the actual source file is shown along with its line numbers instead of the file loaded by the browser.

The Cypress console could be very useful whenever you face an error, luckily, most of the Cypress errors are well explained, and you can easily understand what is going on by simply reading the stack trace.

You can also integrate it with the VSCode IDE to jump right to where the error was thrown and shown in the stack trace.

For example, let’s introduce an error by trying to get an element that doesn’t exist in the DOM

it("should throw an error", () => {
  cy.visit("https://ecommerce-playground.lambdatest.io/")

  cy.get("some-selector-which-does-not-exist").click()
})
Enter fullscreen mode Exit fullscreen mode

Running the test:

As you can see, Cypress does a great job explaining why and where the error happened.

AssertionError: Timed out retrying after 4000ms: Expected to find element: some-selector-which-does-not-exist, but never found it.

If you click the “View stack trace” button, you can see the file name in which the error was thrown.

If you click on it, it will ask you to select your IDE to open it. You can choose VSCode from there. If you can not open it, go to the Settings tab -> Device Settings -> External Editor -> Visual Studio Code or your preferred IDE.

Now, if you click on the link on the stack trace dropdown, it will automatically open that file and navigate to that specific line where the error was introduced.

This is a very good feature of Cypress UI automation tool, helps you easily spot errors and locate where the error happened.

Test your Puppeteer test scripts online. Deploy a quality build faster by running automated checks across over 3000+ browsers and OS combinations with the LambdaTest cloud. Try for free!

Using logs for Cypress debugging

Using logs or console logs is another way to debug your code and get an understanding of what is going on when executing tests.

There are two different commands that you can use for logging outputs inside of your browser’s console, which is the cy.log() and the conventional JavaScript console.log() function.

Using Console log

Since all Cypress commands are asynchronous and will be enqueued for later use, you should never assign returning values of any Cypress command.

If you want to console.log() a returning value of any Cypress command, you should do so inside the .then() function.

If you log a returned value from a Cypress command, it will be logged in the browser’s console, but the value will be just a Cypress Chainer Object.

it('should not return the h2 element', () => {
  cy.visit('https://ecommerce-playground.lambdatest.io/')

  const h2 = cy.get('h2')

  // do not do this, the value will not be the actual "h2" element
  console.log(h2)
})
Enter fullscreen mode Exit fullscreen mode

This is the result that you’ll get, which is not very useful.

If you want to log the return value of any Cypress command, the right way to do it is by logging the value inside of a .then() function. This way, you will get the actual element after you log it.
This is the right way to do it.

('should return the actual h2 element', () => {
  cy.visit('https://ecommerce-playground.lambdatest.io/')

  cy.get('h2').then($h2 => {
    // this will log the actual value of the "h2" element
    console.log($h2)
  })
})
Enter fullscreen mode Exit fullscreen mode

If you want to log the text of the element:

it('should return the text of h2 element', () => {
  cy.visit('https://ecommerce-playground.lambdatest.io/')

  cy.get('h2').then($h2 => {
    // gets back the text inside the "h2" element
    console.log($h2.text())
  })
})
Enter fullscreen mode Exit fullscreen mode

The result in the browser’s console is the text inside of the h2 element.

Using the Cypress log command

Cypress automation tool provides us with another useful command that will help us log outputs inside of Cypress’s dashboard logger.

You can use the command by running cy.log() anywhere in your code.

If you want to log the output of a Cypress command, just make sure you do it inside the .then() function since Cypress commands are being enqueued and not instantly executed.

The difference between the regular console.log() function and the Cypress log() function is that the Cypress log() function will log the output inside of the Cypress console, but the console.log() command will log it inside of the browser’s console.

it('should log the return value inside of the Cypress console', () => {
  cy.visit('https://ecommerce-playground.lambdatest.io/')

  cy.get('h2').then($h2 => {
    cy.log($h2.text())
  })
})
Enter fullscreen mode Exit fullscreen mode

The result in the Cypress console:

Using .pause() command for Cypress debugging

Cypress exposes another command which helps in pausing the test execution and making it easily debuggable, you can then manually start the test again and resume executing Cypress commands.

The .pause() command is also a Cypress chainable and can be chained with any other Cypress command.

Cypress.pause() example of use

it("should pause the test", () => {
  cy.visit("https://ecommerce-playground.lambdatest.io/")

  cy.get("input[name='search']")
    .first()
    .should("exist")
    .and("be.visible")
    // pauses the command execution
    .pause()
    .type("Iphone 12")
})
Enter fullscreen mode Exit fullscreen mode

In this section of this tutorial on Cypress debugging we are getting the input elements in the DOM, which has a “name” attribute of “search,” and we are selecting the first one by running the .first() command.

And we are asserting two assertions on them; after that, we are typing something inside of the input, but just before typing the input, we have run the .pause() method.

The code will run the first three commands and pause before running the fourth and final commands.

Running the test:

As you can see, a new button has appeared on the top of the Cypress console, and also, we can see in the final output of the logs it is written - pause, which is caused by our pause command.

Now, if we press the play button, the test will resume, and the commands will resume executing again.

Resuming the test execution:

The test will resume, and the last command will then be executed. Finally, the text will be typed inside of the search input.

[Bonus Tip] How to reduce type errors in Cypress?

I will give you a bonus, this one is not for Cypress debugging but for reducing and spotting errors inside of your application before running the tests, and it will provide you with a faster development pace.

Use TypeScript

TypeScript is a programming language that enables developers to write code in a type-safe way. Since Cypress ships with the official type declarations and is strongly typed, it will be easier for developers to spot and find type errors before running the tests.

Install TypeScript

Install typescript by running the following command.

Note: TypeScript 3.4+ is required to be used with Cypress.

Create a tsconfig.json file inside of the cypress folder with the following configurations.

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["es5", "dom"],
    "types": ["cypress", "node"]
  },
  "include": ["**/*.ts"]
Enter fullscreen mode Exit fullscreen mode

Adding types for your custom commands

It is also important for you to add types for the custom commands that you will create and use throughout your tests and Cypress allows you to do that.

Let’s say you have the following command.

Cypress.Commands.add("login", (email, password) => {
  /**
   * Login to the application
   * With Cypress
   * ...
   */

  return "{ ... }"
})
Enter fullscreen mode Exit fullscreen mode

If you don’t declare the types for the login custom command, you will get a TypeScript error instantly, so let’s go ahead and create the types for it.

Inside your support folder, create a file with the name index.ts and add the following code.

// cypress/support/index.ts
declare global {
  namespace Cypress {
    interface Chainable {
      login(email: string, password: string): string
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Now your Cypress tests should be able to work with TypeScript perfectly. If you are still getting errors, restart the TypeScript server by pressing the Ctrl+Shift+P on Windows or CMD+Shift+P on macOS and type > in the beginning and search for >TypeScript: Restart the TS server and press it. This will restart your TS server and load the new configurations.

Now you will see an autocomplete option for all of the Cypress commands.

You will also see an autocomplete option for your custom commands.

To get accurate results, you must run Cypress tests on real browsers and OS. The best way to do this is by using LambdaTest’s cloud-based automation testing platform. Accelerate your go-to-market delivery by performing Cypress parallel testing on 40+ versions on the latest across Windows and macOS without compromising accuracy.

Subscribe to the LambdaTest YouTube channel for tutorials around Selenium testing, Playwright browser testing, Appium, and more.

If you’re a developer who needs to perform Cypress end-to-end testing for your app and you’d like to get an overview of everything Cypress has to offer, the Cypress 101 certification is for you.

Test your website or web app online for iOS browser compatibility. Perform seamless cross browser testing on the latest iPhone tester Simulator. Try for free!

Conclusion

In this Cypress debugging tutorial, we have discussed the best ways to debug Cypress tests easily. Cypress UI testing is an excellent framework for validating your front-end applications. It provides us with many great features to debug our test suites easily.

Here are the different ways to debug your Cypress tests:

  • Using a debugger.

  • Using .debug() Cypress command.

  • Using the stack trace.

  • Using logs console.log() and cy.log().

  • Using the Cypress .pause() command.

  • Using TypeScript to reduce type errors.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .