https://cypress.io logo
Custom commands results reused inside another cust...
# i-need-help
n
Basically I have custom commands for API calls to create environment needed for tests. So I have something like this:
Copy code
Cypress.Commands.add('getSomeDataById', (dataId, userId) => {
    cy.request('GET', '/myEndpoint')
        .its('body')
        .then((body) => {
            body.data.forEach((data) => {
                if (data.id === dataId && data.userId === userId) {
                    cy.wrap(data).as('data');
                }
            });
        });
});
then in the next command I have:
Copy code
Cypress.Commands.add(
    'someOtherCustomCommandForCreation', () => {
        cy.get('@data').then((data) => {
            // For this data do that
        });
    },
);
Additional question would be, if we know that custom command yields the results automatically. Is there a difference in using an alias as in the example above or it is better practice to use it as.
Copy code
Cypress.Commands.add(
    'someOtherCustomCommandForCreation', (dataId, userId) => {
        cy.getSomeDataById(dataId, userId).then((data) => {
            // For this data do that
        });
    },
);
h
I would say that using aliases to implicitly pass data between two commands is an anti-pattern. Chaining off the previous subject is definitely the better option here.
I don't think it's generally a good idea for anything but the individual tests in
.cy.ts
files to set / get aliases. It feels similar to a local vs. global variables sort of pattern. Aliases should be used like local variables for subjects in a single Cypress test, not globals passed around to many different places.
l
It's probably best to keep alias usage to a minimum, but there are definitely use cases for it. We have cases where we use it a lot like you are here 🙂 A better approach, however, would be to use
{ prevSubject: true }
on the second command, then chain them together. But I do realize this is not always convenient. In my case we have multiple commands that make use of an object that we build up as the test progresses, which we alias. (We collect data during the test, then do a lot of assertions on a confirmation page at the end of the test.) A global variable will work too, and maybe some clever architecture will get you there.
h
I have seen use-cases where you need to build up some data in the background across a whole test. We have an internal Cypress plugin to generate very particular reports for a formal validation/documentation system we have to use in our industry, and we build up the entries for those reports during the lifecycle of a test using a custom command. The statefulness of that report generation is contained within normal JS variables in the command file, not aliases. Definitely avoid if possible though - global state is a big hammer.
If you just need to get a little data from one command to another though, I agree that
{ prevSubject: true }
on the second command should be your first choice by a long shot.
l
It's hard to reason through it without spending an hour screen-sharing a use case, but suffice it to say one case where I will choose it is if it allows the test file to be easier to read. State is less problematic in tests (vs full applications) due to their short run times.
h
Yeah that's basically how we arrived at it in our case too. We need to log "steps" within a single test for automating a formal validation process (medical device industry), and it's easier to have a stateful
cy.testStep
command and some
{before,after}Each
hooks to flush that state to the report file on disk than to have the test author juggle the underlying report data at all. I don't recall offhand though when/if any global variable state in a command file is re-loaded by the Cypress runtime. Gotta be careful not to leak across tests.
We have some other stuff we have to do in a
beforeEach
hook so I just reset the state there
Can't hurt 🤷‍♂️
l
That's one win for aliases vs globals; Cypress expects them and you know they are cleared out with each test. Plus you get to see them in the test log.
h
Yeah though you're kinda SOL with your linter / TypeScript if you use aliases that way
l
True 😄
h
Tradeoffs
Anyhow, there's a whole menu of options 🙂
tl;dr if you can, just use the yielded result
One other potential risk of the aliases approach is aliases won't shadow, they'll just overwrite, so a user could clobber that data without knowing it.
l
Yes, and if you don't "initialize" an alias, then attempt to reference it, you'll have problems.
I don't know of a way to check if an alias is undefined.
h
As soon as "check if" makes it into Cypress, probably a good idea to go a different route most of the time
I don't think there's a way, not that I know offhand. No such issue with yielded subjects though, so one more reason to prefer that.
l
One day maybe I'll rearchitect and remove these, but for now this is in our
e2e.js
, in a
beforeEach()
😄
n
thanks both for sharing experience. As you mentioned, we also have specific case where we need to store the state before the test and return it after. My service doesn't have control of other service DBs and not to many services are dockerized.
also I have something similar for initializing alias
cy.wrap(false).as('alertFound');
luckily only on one place. And most of my conditional approach comes from neediness to restore to some previous state. But if something goes wrong then some states are not changed and there are no leftovers
5 Views