Search code examples
loopshtml-tablecypressangular-material-table

How do I verify I've selected the correct user from a table in Cypress (html-table / angular-material)


I'm trying to delete a specific user, and would rather the test fail then delete a different user if the user I want doesn't exist.

The issue, I can't seem to find a way to verify the checkbox is next to the user I want. I've been using a .each() nest to first find the user Mandy Smith, and then select the checkbox with the same index.

HTML: the HTML code

Note, I'm trying to delete the user "Mandy Smith" and while I can just use the following code to select her, depending on how quick the test gets to this point it might actually delete the first user.

Current code that deletes Mandy Smith:

         // looks for the checkbox related to Mandy Smith User
          cy.get('input[type="checkbox"]')
            .each(($elem, index) => {
                if(index === 2) {
                  cy.wrap($elem).click({force:true});
                }
            });

While this will generally grab her, depending how quickly filters are being cleared it can actually choose the Automated User for deletion. I of course don't want this, I'm trying to find a good way to verify the index Mandy Smith is at and apply that to her checkbox.

The checkbox is dynamic, so it will iterate up based on what is clicked, so i don't want to choose the exact checkbox with it's label "#mat-checkbox-11" as that's what it happens to be at while i'm looking at the code, but it was 5 earlier.

I thought this would let me find the index and apply it to the checkbox:

// looks for the checkbox related to Mandy Smith User
    it(`should select Mandy, through verification that it's her index`, () => {
        cy.get('.mat-column-name.ng-star-inserted')
          .each(($name, i) => {
            if($name === 'Mandy Smith') {
                let dex = i;
                cy.get('input[type="checkbox"]')
                  .each(($elem, index) => {
                    if(index === dex) {
                        cy.wrap($elem).click({force:true});
                    }
                });
            }
        });
    });

This actually just ends up deleting the Automated User instead.

I did run some tests and found the string it's returning is this, " Automated User Mandy Smith ". So, even though I'm iterating through the table, it seems to pull both text fields. Am I missing something on how to grab a single set of text, when all the class names are the same?

This means it deletes the first user as the if statement will show Mandy Smith, and then choose the first user which is Automated User instead of the second user, Mandy Smith. Maybe I need to know how to grab text from each class with the same name.

I don't know, I'm a little lost now on how to potentially do this.

EDIT: I found I can invoke text within the if statement, but it never gets within the if statement. I checked what it yielded and pasted that into my if and it still didn't work. Here is the code that is grabbing the elements innerText, and it's meant to compare to the word. Is there a reason it wouldn't evaluate true?

    it(`should select Mandy, through verification that it's her`, () => {
        cy.get('.mat-cell.cdk-cell.text-capitalize.cdk-column-name.mat-column-name')
          .each(($name, i) => {
            //let columnText = ($name).invoke('text');
            if(cy.get($name).invoke('text') === ' Mandy Smith ') {
                cy.log('it made it inside the if');
            }
        });
    });

enter image description here

EDIT 7/15/2020: Here is an image of the entire table. Yellow arrow is her row, right above that is user 1's row. Green arrow is her checkbox, red arrow is her name from the name column

html image

Thanks for everyone's posts. I'm going to be messing with it today and test the various comments and see if I can find a solution. Feel free to keep giving feedback, as I'll check throughout the day.

Solved Thanks everyone here is the answer, I got on the phone with a senior developer and after seeing what I was doing he ran me through a few things. Thanks to @oldschooled for his awesome answer, and the follow up @eric99 for offering breaking it down a bit more for me as well. You two are rockstars.

Answer

    it(`should select Mandy Smith with contains only`, () => {
        cy.contains('td', 'Mandy Smith').siblings().eq(0).children().eq(0).click();
    });

Solution

  • @OldSchool's explanation is correct but you did not follow it correctly.

    In comments you show

    cy.get('.mat-cell.cdk-cell.text-capitalize.cdk-column-name.mat-column-name')
      .invoke('text')                // REMOVE THIS COMMAND
      .contains('Mandy Smith');
    

    The problem is invoke('text') after a get() that selects multiple elements will concatenate all the text of all those elements (i.e all cells in the name column).

    In contrast, .get().contains('Mandy') selects the single cell containing 'Mandy', then using sibling() restricts the next part (finding the checkbox) to that table row only.

    If you have one and only one table on the page, I would use the following as it's the shortest and uses Cypress selector power to maximum effect.

    Soultion

    cy.contains('td', 'Mandy Smith')
      .siblings('td')   // get all sibling cells in the 'Mandy Smith' row
      .find('input')    // find the input within these cells
      .click();
    

    Note, if table data is fetched asynchronously, cy.contains(selector, content) is preferred over cy.get(selector).contains(content) since the first one will retry until content arrives.


    I amended the Soultion code above after testing it out on the Angular Material Grid example page, using the example 'Table with selection' (searching for 'Helium' which is in the 2nd row).

    It turns out the selector used in .siblings(selector) cannot handle the same complexity as .get(selector), e.g

    .get('td input')      // succeeds and finds all input cells
    
    .siblings('td input') // fails to find the input, although it does exist
    

    So, using .siblings('td').find('input') gets to the correct place.

    From the .find() docs

    Get the descendent DOM elements of a specific selector

    i.e .find() restricts the search to within the previous subject(s), which are the sibling cells.


    This works on the Angular Material Grid example page. If you still have no luck, please post your actual html of the first two rows as text rather than picture, and I will investigate.


    If you have more than one grid on the page you will need to enhance the selector in order to initially narrow it down to the required grid.


    Exploratory testing

    Cypress has a neat feature that allows you to experiment with different commands on a fragment of the DOM.

    • Copy the fragment of HTML that you are working on into a new .html file, placing that file under the /cypress folder, e.g /cypress/myFragment.html.

    • The content of the fragment html file does not need to be a complete HTML doc, you can omit the header and body tags. In this case you need a <table> tag, a <tbody> tag, and paste in between the first two rows, which are enough for this experiment. (Don't forget to close </table> and </tbody>).

    • Write an experimental spec that visits the fragment HTML, e.g cy.visit('cypress/myFragment.html'). Cypress will load this the same as a full HTML page.

    • Follow the cy.visit() with the commands you want to experiment with, e.g the Solution code above.

    • Run just that spec and adjust the commands until they work.