Create Custom Cypress Commands

Brett Cassette
InstructorBrett Cassette
Share this video with your friends

Social Share Links

Send Tweet
Published 5 years ago
Updated 3 years ago

Do you need to reuse complex Cypress calls often (like when accessing the store)?

You can turn them into custom Cypress commands, and even customize their output in the time-traveling debugger, so it's easy to see a snapshot at the point your command ran!

Recommended Homework:

  • Review the Best Practices for Custom Commands - https://docs.cypress.io/api/cypress-api/custom-commands.html#Best-Practices

Instructor: [0:00] In the previous video, we access the state of the Redux store with this rather cumbersome call. If we want to reuse this in our application over and over again, it would probably be easier if we could call cy.store. Let's turn this whole thing into a custom Cypress command. We can open up our side bar, go under Cypress, support, commands.

[0:23] This file has a couple of examples. We can either have parent commands, child commands, or dual commands. Since we'll call store directly on cy, that'll make it a parent command. A child command would be chained off of some existing command.

[0:39] Let's follow this formula. Cypress.Commands.add store. Since it's a parent command, we won't pass in the option for previous subject. Instead, our second argument is just the function we want to run when we run the command. Let's paste in our command and make sure we return it.

[0:58] If we head back into Cypress and rerun our test, we'll see our custom command ran, or it seems like it did. Instead of seeing store logged out here, we see all of the child commands, window, its, invoke, all of the gut contents of our method. That's going to be confusing to our end user.

[1:18] It would be a lot more helpful if we could just see one log that said store and if when we clicked on it we could actually see the state of the store, the way we do with Cypress commands. How do we do that? Cypress will let us create a log by calling Cypress.log. We'll give it the name that we want, which is store.

[1:39] Since we want to suppress the output of cy.window, we can pass in log false. Most cy commands support log false, but its and invoke will not. Instead, let's just use a then, which won't log anything. Instead, we'll grab our window. We'll return window.store.getState.

[2:03] If we rerun our test, we'll see that we have logged out the name store. We have no child commands logged anymore. How do we actually log out the contents of the store inside the console? What we need is some way to attach the state that we just got to the log that we already created.

[2:21] Fortunately, Cypress supports updating the log throughout the duration of the method. We can call log.set. We can pass in a message, which will always be a string. We'll call JSON.stringify on our state. Then we'll pass in console props, which will allow us to inspect the state in the console. Here, we'll return the state.

[2:44] Finally, we return the state from this whole method so that we can continue chaining Cypress commands off of it. When we rerun Cypress, we can see our store command now has a stringified version of the state printed out as the message.

[2:59] When it's clicked on in the console, we can look through any piece of state we like, including our to-dos, visibility filter and this example of a deeply nested piece of state.

[3:11] Since application state can become sprawlingly huge in any application, we want to allow the user to define which piece of state they want logged in the console and what they want to test against, so let's include a state name argument.

[3:27] By default, we'll return the entire state, but if stateName.length is greater than zero, then we'll return just the subset of state that the user asked for. To continue chaining method calls, we'll call cy.wrap, passing in the state. Don't forget to pass log false. Then we'll call its with the state name. That way, we grab just the piece of state that we're interested in.

[3:49] Finally, we'll call a callback which will run this and this. We'll copy these out. Let's create our callback. This will receive the state and run the logging functions. Then in the case where we don't want to run its, we'll wrap the state, log false, and then just run the callback. We'll make sure to return the two of these and clean it up.

[4:17] Now, we can go back to our test and pass in to-dos to the store and remove the its. When we rerun our test, we'll see that in the store, the only part of the store that we logged out was the part of the store that we were interested in.

[4:34] Because cy.its accepts dot-separated notation, we can drill into the store as deeply as we want. Remember this deeply nested piece of state, example.test.first? We can drill into that, too.

[4:49] When we load it up, we can see this. We can see that it yielded the value one. We can see the piece of state that we drilled into. You can see pretty quickly how effective it is to create custom commands with custom logging.

Eric Laursen
Eric Laursen
~ 4 years ago

I'm having trouble coding-along with the video @ 2:03 :( In short, 'store' is not showing in my cypress runner output window when the video says 'store' is logging. In the video, the last piece of code that is updated is the 'command', and my code matches the video's: Cypress.Commands.add('store', (str = '') => { const log = Cypress.log({ name: 'store' }); return cy .window({ log: false }) .then((win) => win.store.getState()); });

The next thing in the video is that 'store' shows in the runner output replacing 'window' in the runner output. Here, though, 'window' still remains showing in my case.

Markdown supported.
Become a member to join the discussionEnroll Today