Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Aliases in custom commands

Tags:

cypress

Using latest Cypress (14.3.0), I have a test with many lines like this (simplified example):

cy.contains("a", "foo").as("a").click()
cy.get("@a").find("span.bar")

It clicks on the (first) a element containing the text "foo" and then expects the specified span element to appear inside that a element. The alias is needed, because the DOM changes after the click.

To clean things up I am trying to have a custom command to be used like this:

cy.contains("a", "foo").clickAndFind("span.bar")

My command code (first attempt):

Cypress.Commands.add("clickAndFind", { prevSubject: "element" }, (e, s) => {
   cy.wrap(e).as("alias").click()
   return cy.get("@alias").find(s)
})

This does not work for me, because the given HTMLElement e does not contain any DOM queries and thus wrapping and aliasing it is not the same as calling as on the command chain (directly after contains). Thus I get the The subject is no longer attached to the DOM error.

Is there any way to get this to work as intended?

Edit: It turns out I simplified a little to much. The following is an only slightly simplified excerpt of the real test code. I post this to show that it's not enough to just rerun the last query without respecting the full command chain.

const sortUp = "span.fa-arrow-up-z-a"
const sortDown = "span.fa-arrow-down-a-z"
cy.contains(".accordion-header", "Patients")
  .siblings(".accordion-collapse").as("area")
cy.get("@area").find("thead tr.headers").as("headers")
cy.get("@area").find("tbody").as("patients")
cy.get("@headers").contains("a", "Patient").clickAndFind(sortUp)
cy.get("@headers").contains("a", "Patient").clickAndFind(sortDown)
cy.get("@patients").contains("tr", patient).should("have.class", "table-warning")
like image 578
Marcus Avatar asked Nov 01 '25 14:11

Marcus


1 Answers

user16883681's solution does not always work, because rerunning only the last query gives me the wrong element back if that query also matches an element further up on the page. A proper solution should respect all queries of the command chain.

After looking into the source code of the “as” command, I came up with this solution:

Cypress.Commands.add("clickAndFind", { prevSubject: "element" }, (e, s) => {
  const alias = addAlias()
  cy.wrap(e).click()
  return cy.get(alias).find(s)
})

function addAlias(alias = Date.now().toString()) {
  // @ts-ignore
  const command = cy.state("current").get("prev")
  const fileName = command.get("fileName")
  // @ts-ignore
  const subjectChain = cy.subjectChain() ? [...cy.subjectChain()] : undefined
  // @ts-ignore
  cy.addAlias(cy.state("ctx"), { alias, command, fileName, subjectChain })
  return "@" + alias
}

Usage example:

cy.contains("a", "foo").clickAndFind("span.bar")

Note: The // @ts-ignore lines are in case you are using TypeScript. Some commands used in the code are not exposed in types. So otherwise you would get IDE errors like:

Property 'state' does not exist on type 'cy & CyEventEmitter'.ts(2339)

Then you might also want to add cy.clickAndFind to its interface:

declare global {
   namespace Cypress {
      interface Chainable {
         /**
          * Clicks on the given element and then finds  
          * child element(s) by the specified CSS selector.
          * This even works if the DOM changes after the click.
          * 
          * @param {string} selector - a CSS selector, e.g. "span.fa-arrow-up-z-a"
          */
         clickAndFind(selector: string): Chainable
      }
   }
}
like image 179
Marcus Avatar answered Nov 04 '25 03:11

Marcus



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!