cypress: Using .within() in custom commands/functions to return an inner DOM element found in .within() results in unexpected behavior

Current behavior

This command worked in 5.3, but in 5.4 when I chain a .type() command to it. It throws and error

Command:

Cypress.Commands.add('firstCellSearchFilter', () => {
  cy.get('.ag-header-row-floating-filter').within(() => {
    cy.get('.ag-header-cell')
      .first()
      .within(() => {
        cy.get('.ag-text-field-input')
      })
  })
})

Error: cy.type() can only be called on a single element. Your subject contained 3 elements.

Foo.firstCellSearchFilter().type('bar') fails because Foo.firstCellSearchFilter() returns ALL of the cy.get('.ag-text-field-input') elements. Its like the scope of cy.get('.ag-header-cell').first().within() gets reset or ignored

Desired behavior

I would expect the command to scope to the single input element, like it did in 5.3

Versions

Cypress: 5.4 Mac Mojave Chrome 86

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 4
  • Comments: 25 (15 by maintainers)

Most upvoted comments

Another example of unexpected behavior from https://github.com/cypress-io/cypress/issues/9064

@sainthkh I do agree that this is working as documented, but I think it may be helpful to think about how to provide the wanted behavior here. People want to traverse within some DOM and return the inner DOM element they’ve found, so that they can return it to be chained as a command/function.

So, either we can provide an example of how they can do this or we change the behavior of .within()

Maybe the scope of .within() could change if there is a return statement, similar to how what is yielded from .then() changes based on the return statement. (This is just me thinking out loud, I haven’t discussed this with the team).

function getTableCell(
  tableId,
  column,
  row,
) {
  return cy
    .get(tableId)
    .find('tbody>tr')
    .eq(row)
    .within(() => {
      cy.get(column);
    });
}

it('test', () => {
  cy.visit('index.html')
  getTableCell("#myTable", "#three", 0).should('have.text', 'foo')
})

5.4.0

Screen Shot 2020-11-03 at 11 31 27 AM

5.5.0

Screen Shot 2020-11-03 at 11 33 48 AM

@sainthkh After discussing this with the team, we are most concerned about there being consistency among all of the commands and how they work. So, .then() should work similarly to how .within() or .should() or any other command that accepts a function that includes other cypress comannds within it.

So, we’d like to not see new rules made up specifically for .within() - and make sure the logic matches more closely to how the other commands work.

It is absolutely a bug. If its not then what is the point of within()? the within() is supposed to narrow the search scope. The BUG is the when you attach cy.get to an element that is being found in the within() is attaches the action, type() or click() to the element that was found at the top level within() scope instead of the element found in the within() scope.

referring to my original comment

Cypress.Commands.add('firstCellSearchFilter', () => {
  cy.get('.ag-header-row-floating-filter').within(() => {
    cy.get('.ag-header-cell')
      .first()
      .within(() => {
        cy.get('.ag-text-field-input')
      })
  })
})

So you’re telling me that the intended behavior of Foo.firstCellSearchFilter().type() is to type or click on ‘ag-header-row-floating-filter’ (the outer scope) and not ag-text-field-input?

This worked in 5.3 and broke in 5.4

It’s the intended behavior. Before 5.4, cy.within was permanently narrowing scope for the following commands and it’s fixed in #8699.

Here’re the workarounds:

// Use CSS selector pseudo class
Cypress.Commands.add('getThird2', () => {
  cy.get('.first .second:first-child .third')
})
// Use jQuery commands + cy.get() withinSubject option.
Cypress.Commands.add('getThird3', () => {
  cy.get('.third', {
    withinSubject: Cypress.$('.first').find('.second').first(),
  })
})