Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Requery aliased DOM elements #2971

Closed
Sjeiti opened this issue Dec 19, 2018 · 1 comment
Closed

Requery aliased DOM elements #2971

Sjeiti opened this issue Dec 19, 2018 · 1 comment
Labels
pkg/driver This is due to an issue in the packages/driver directory type: feature New feature that does not currently exist

Comments

@Sjeiti
Copy link

Sjeiti commented Dec 19, 2018

Current behavior:

An alias onto a DOM selection is a snapshot. So cy.get('@listElements').should('have.length', 2) will yield the same result at any point in the test even if list elements are added or removed.

This makes aliases difficult to use in some cases.

Desired behavior:

An alias can be overwritten easily though. So you could write a command that overwrites a specific alias and use that instead of get (see updatelistElements command). But this breaks the readability and flow of the test a bit.

The get command does have all the information it would need to update the alias. The yield contains the prevObject property that can be traversed so that together with the selector property the alias can be updated.
This is illustrated in the updateAlias command that wraps a cy.get but updates the alias at the same time.

People could simply use the updateAlias command but it might also be nice to have this functionality in a different way.
Maybe a live alias could be created like so: cy.get('li').as('listElements', {live: true}) that would always update the alias on a get command.
Or maybe the get could be use like this: cy.get('@listElements', {update: true}).

A test script to illustrating the above

Cypress.Commands.add('getListElements', () => cy.get('@list').find('li'))  
Cypress.Commands.add('updateListElements', () => cy.get('@list').find('li').as('listElements'))  
  
Cypress.Commands.add('updateAlias', domAlias => {  
    const aliasName = (domAlias.match(/^@(.*)$/)||[])[1]  
    return aliasName&&cy.get(domAlias).then($result => {  
        const tree = [$result]  
        while (tree[0].prevObject) tree.unshift(tree[0].prevObject)
        return tree.reduce(((cy,o)=>cy.find(o.selector)),cy.wrap(tree.shift().get(0).documentElement)).as(aliasName)
    })||cy.get(domAlias)  
})  
  
describe('TestAlias', () => {  
  
  beforeEach(() => cy  
    .get('body').then($body=>$body.get(0).innerHTML=`<ul><li></li><li></li></ul><button onClick="document.querySelector('ul').appendChild(document.createElement('li'))">add</button>`)  
    .get('ul').as('list')  
    .get('@list').find('li').as('listElements')  
    .get('button').as('addElement')  
  )  
  
  context('test', () => {  
    it('should not get correct amount of elements by alias', () => cy  
      .get('@listElements').should('have.length', 2)  
      .get('@addElement').click()  
      .get('@listElements').should('have.length', 2)  
    )  
    it('should get correct amount of elements by custom command', () => cy  
      .getListElements().should('have.length', 2)  
      .get('@addElement').click()  
      .getListElements().should('have.length', 3)  
    )  
    it('should get correct amount of elements by custom command that updates alias', () => cy  
      .get('@listElements').should('have.length', 2)  
      .get('@addElement').click()  
      .updateListElements().should('have.length', 3)  
      .get('@listElements').should('have.length', 3)  
    )  
    it('should get correct amount of elements by generic alias update command', () => cy  
      .get('@addElement').click()  
      .updateAlias('@listElements').should('have.length', 3)  
      .get('@listElements').should('have.length', 3)  
    )  
  })  
})

Versions

Cypress 3.1.1

@jennifer-shehane jennifer-shehane added the stage: proposal 💡 No work has been done of this issue label Dec 26, 2018
@jennifer-shehane jennifer-shehane changed the title Updating an alias Requery aliased DOM elements Oct 25, 2019
@jennifer-shehane jennifer-shehane added the pkg/driver This is due to an issue in the packages/driver directory label Oct 25, 2019
@jennifer-shehane jennifer-shehane added the type: feature New feature that does not currently exist label Apr 8, 2022
@cypress-bot cypress-bot bot added stage: icebox and removed stage: proposal 💡 No work has been done of this issue labels Apr 28, 2022
@BlueWinds
Copy link
Contributor

Interesting proposal! I know this is a couple years old at this point, but it's well written and I wanted to give a through response before I close it.

As part of the resolution of #7306, I've basically rewritten how aliases (and commands in general) work under the hood, and one consequence of that is that you can get the above behavior pretty easily.

Here are some examples of how it works in Cypress 12.


describe('TestAlias', () => {  
  beforeEach(() => cy  
    .get('body').then($body=>$body.get(0).innerHTML=`<ul><li></li><li></li></ul><button onClick="document.querySelector('ul').appendChild(document.createElement('li'))">add</button>`)  

    cy.get('ul').as('list')  
    .get('@list').find('li').as('listElements')  
    .get('button').as('addElement')  
  )  
  
  context('test', () => {  
    it('requeries the DOM every time an alias is read', () => cy  
      .get('@listElements').should('have.length', 2)  
      .get('@addElement').click()  
      .get('@listElements').should('have.length', 3)  // All aliases auto-update, requerying the DOM every time they're read.
    )  
    it('commands (like .wrap and .then) break the chain of retries', () => {
      cy.get('@listElements).then(($els) => $els).as('aliasWithoutRetrying')  
      cy.get('@addElement').click()  
      cy.get('@aliasWithoutRetrying').should('have.length', 2)  
    })  
  })  
})

Basically - "queries" are always rerun, building up a "subject chain" of functions that can be rerun at any time (eg, whenever an alias is read, or whenever determining the subject of a future command). But non-query commands (like .then(), .wrap(), .click()) break the subject chain, making the subject into a specific set of elements.

So if you want to maintain an alias reference to "the list as it existed at the beginning of my test", you can query for the

  • s, cy.wrap() them, and then alias that - but by default an alias for "the lis in a list" will requery the DOM every time, getting the most up-to-date result.

  • Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    pkg/driver This is due to an issue in the packages/driver directory type: feature New feature that does not currently exist
    Projects
    None yet
    Development

    No branches or pull requests

    3 participants