Cypress Workshop Part 7: Elements manipulation - Table And Upload
Tags: qa, testing, cypress, javascript
Published: 2023-05-28 21:12:46

## Tables

Tables are collections of different web elements, usually consisting of multiple editable/non-editable fields in a form of inputs, text areas, buttons, dropdowns and similar. 

So, let’s use our knowledge from previous lessons to write some new test cases related to tables:

1: Create a new test file under `e2e/elements_manipulation` and call it for example `tables.cy.js`. 

2: Write the following tests inside: 

```javascript
/// <reference types="Cypress" />

describe('Tables: Tables actions', () => {
  beforeEach('Navigate to tables page', () => {
    // Visit tables page
    // Table records return to default after page refresh
    cy.visit('/webtables');
  });

  it('Check finding and editing a record', () => {
    // Get the table, find the row with record Alden
    cy.get('.rt-tbody')
      .contains('.rt-tr-group', 'Alden')
      .then((row) => {
        // Click on edit button for Alden record
        cy.wrap(row).find('[title="Edit"]').click();
        // Edit first and last name
        cy.get('#firstName').clear().type('Harvey');
        cy.get('#lastName').clear().type('Specter');
        // Submit edit form
        cy.get('#submit').click();
        // Assert that first and last name are changed
        cy.wrap(row).find('.rt-td').eq(0).should('contain', 'Harvey');
        cy.wrap(row).find('.rt-td').eq(1).should('contain', 'Specter');
      });
  });

  it('Check finding and deleting a record', () => {
    // Get the table, find the row with record Alden
    cy.get('.rt-tbody')
      .contains('.rt-tr-group', 'Alden')
      .then((row) => {
        // Click on delete button for Alden record
        cy.wrap(row).find('[title="Delete"]').click();
      });
    // Assert that table does not contain Alden record
    cy.get('.rt-tbody').should('not.contain', 'Alden');
    // Perform search for Alden record
    cy.get('#searchBox').type('Alden');
    // Assert that Alden record does not exist and "No rows found" message is displayed
    cy.get('.rt-tbody').should('not.contain', 'Alden');
    cy.get('.rt-noData').should('contain', 'No rows found').should('be.visible');
  });

  it('Check search for different age records', () => {
    // Define age group
    const ageGroup = [29, 39, 45, 77];
    // For each age group perform following
    cy.wrap(ageGroup).each((age) => {
      // Type age into search input field
      cy.get('#searchBox').clear().type(age);
      // If age is 77 assert that record doesn't exist in the table, otherwise assert that record is present
      if (age === 77) {
        cy.get('.rt-tbody').find('.rt-tr-group').first().should('not.contain', age);
        cy.get('.rt-noData').should('contain', 'No rows found').should('be.visible');
      } else {
        cy.get('.rt-tbody').find('.rt-tr-group').first().should('contain', age);
        cy.get('.rt-tbody').contains('.rt-tr-group', age).should('have.length', 1);
      }
    });
  });

  xit('Check adding a new record - Bad code practice', () => {
    // Click on Add button
    cy.get('#addNewRecordButton').click();
    // Fill new record form
    cy.get('#firstName').type('Harvey');
    cy.get('#lastName').type('Specter');
    cy.get('#userEmail').type('specter@example.com');
    cy.get('#age').type('40');
    cy.get('#salary').type('700000');
    cy.get('#department').type('legal');
    cy.get('#submit').click();
    // Assert that new record is present in the table with correct values
    cy.get('.rt-tbody')
      .contains('.rt-tr-group', 'Harvey')
      .then((row) => {
        cy.wrap(row).find('.rt-td').eq(0).should('contain', 'Harvey');
        cy.wrap(row).find('.rt-td').eq(1).should('contain', 'Specter');
        cy.wrap(row).find('.rt-td').eq(2).should('contain', '40');
        cy.wrap(row).find('.rt-td').eq(3).should('contain', 'specter@example.com');
        cy.wrap(row).find('.rt-td').eq(4).should('contain', '700000');
        cy.wrap(row).find('.rt-td').eq(5).should('contain', 'legal');
      });
  });

  it('Check adding a new record', () => {
    // Click on Add button
    cy.get('#addNewRecordButton').click();
    cy.fixture('users').then((user) => {
      // Fill new record form
      const columnIDs = Object.keys(user.user1);
      const userData = Object.values(user.user1);

      cy.wrap(columnIDs).each((id, value) => {
        cy.get(`#${id}`).type(userData[value]);
      });
      cy.get('#submit').click();
      // Assert that new record is present in the table with correct values
      cy.get('.rt-tbody')
        .contains('.rt-tr-group', user.user1.firstName)
        .then((row) => {
          cy.wrap(userData).each((value, index) => {
            cy.wrap(row).find('.rt-td').eq(index).should('contain', value);
          });
        });
    });
  });
});
```
As you can see, we have multiple tests inside this test file. Before each test (line: 7) we will first visit our test page with a table [page](https://demoqa.com/webtables). One thing to note: This page is written in a way that if you modify the records in the table, after each page request we will get the default state of this table, meaning for each test we will have the same state of the table regardless if we modified it with teat automation or manually. 

Let’s break this test file into multiple explanations for each test: 

**Test 1: Check finding and editing a record**

For this test case, we want to edit the row with record under name “Alden”. We want to edit his first and last name to be “Harvey Specter” and check if the name is actually changed in the table.

Code explanation: 

On the line: 12 we are getting the entire table body

Line: 13 After we got the entire body, we are searching for the row that contains text ‘Alden’

Then line: 14, we are creating a function that will return every data in that row 

Line: 16 we are wrapping the row data, and we are finding ‘Edit’ button element, and we are clicking on it 

After the edit modal is opened, on the lines 18 and 19 we are editing first and last name and typing new values

On the line: 21, we are submitting the form and modal is closing

Line 23: from the wrapped row data, we are finding first column and asserting that it contains updated first name

Line 24: from the wrapped row data, we are finding the second column and asserting that it contains updated last name

Why are we not using IDs for “Edit” button, and why are we wrapping specific row data to get selectors? Because they have indexes, and they are not unique in this case, and we want to make sure we changed only the ‘Alden’ record, in case the records in the table change the order in the future (developer changed the order etc). 

**Test 2: Check finding and deleting a record**

For this test case, we want to delete a record from the table, in this case - record with first name ‘Alden’. We also want to check if a record is deleted by asserting that the table does not contain that record upon deletion, and we are checking that if we search the table record does not appear, but we have a text ‘No rows found’.

Code explanation: 

On the line: 30, we are getting the entire table body

Line 31: We are After we got the entire body, we are searching for the row that contains text ‘Alden’

Line 32: we are creating a function that will return every data in that row 

Line 34: we are wrapping the row data, and we are finding ‘Delete’ button element, and we are clicking on it 

Line 37: We are asserting that table body does not contain record ‘Alden’ any more (row is deleted)

Line 39: We are typing in the search box text ‘Alden’ to search the entire table for that record

Line 41: We are asserting that table body does not contain record ‘Alden’ 

Line 42: We are asserting that ‘No rows found’ text is visible

**Test 3: Check search for different age records**

For this test case, we want to search the table by age number. Let’s say we want to search three existing age records and one non-existing. We will search each age group and for each one of them we will assert that they either exist in the table or not. 

Code explanation: 

On the line: 47 we are storing age records as an array in a variable. As a general rule, always declare a variable with `const` unless you know that the value will change.

Line 49: We are wrapping all of our values from the array and for each one of them we are running a loop function explained below [each](https://docs.cypress.io/api/commands/each)

Line 51: We will first clear our search box (because after first search, age text stays in input field), and we will then type age with value from the array

Since we have age values that exist and don’t exist, we need to split assertions in two conditionals. 

Line: 53: If age is equal to 77 (age that should not exist in the table)

Line 54: we will assert that first row doesn’t contain this age (for this test, we only have one or no records in the table, so it is enough to check only first row as search result) 

Line 55: We will also check that ‘No rows found’ message is displayed.

Line 56: Another condition (else) - for age values that do exist, we will run other assertions as below 

Line 57: We will assert that first row contain the searched age

Line 58: We will assert that there is only one row with that age in the table

**Test 4: Check adding a new record - Bad code practice**

For this test case, we want to submit a new record in the table by clicking on Add option and submitting a new record modal. We also want to check that the record is properly added in the table and contains all values we provided. The reason why we have a note that this is a bad code practice and why the test is skipped (`xit`) is because there is a better way to write this piece of code to be more reusable using basic JS concepts (we will see this in the Test 5 that is identical in functionality as this one, just written with better code style). 

Code explanation: 

Line 65: We will click on ‘Add’ button

When modal opens from line 67 to line 72 we will fill all the info that is needed 

Line 73: We will submit a modal with new record info

Line 75: We will get entire table body

Line 76: We will find a row that contains our record’s first name 

Line 77: we will get data from that row

Line 78-83: We will wrap row data find each of the columns by index and assert that correct value that we provided before is written in the appropriate column. As you can see there is a lot of code duplication here, and in step 2 so in the next test we will write it in a shorter and better way.

**Test 5: Check adding a new record**

This test will do the exact same things as previous one, but we will organise it to have less repetitive and more easily maintained code. 

For the purpose of this test, we will store new user data in a form of fixture, so that if it is needed we can easily find and edit this data. Let’s create a new `users.json` file under `fixtures` folder and insert following data inside:

```json
{
  "user1": {
    "firstName": "Harvey",
    "lastName": "Specter",
    "age": "40",
    "userEmail": "specter@example.com",
    "salary": "700000",
    "department": "legal"
  }
}
```
Code explanation: 

Same as previously, on the line: 89 we are clicking on ‘Add’ button to open the modal to add new record 

On the line: 90, we are calling our ‘users’ fixture to load, then we are creating a function for the test where we will use this data

Line 92: we are creating a new variable `columnIDs` and for this variable we will read object keys from our fixture file for user1 data (object keys in this case are actually names of selector IDs)

Line 93: we are creating a new variable `userData` and for this variable we will read object values from our fixture file for user1 data (object values in this case are actual user data we are inserting in a modal for adding new user) 

Line 95: We will warp all our selectors for column IDs (our object keys) and for each one of them  → next step

Line 96: we will use object keys as selectors, get them, and type user data values from it’s pair - user object values (this is actually a short way of writing what we had in previous test from line 67 to 72). See how we reduced the code here and made it more maintainable for the future? 

Line 98: We will click on submit button in the modal

Line 100: We want to asset that each column for our record contains value that we inserted for the user. So, We are getting entire table here

Line 101: We are finding a row that contains our user’s first name

Line 102: with the data from our specific row we are creating a function to make assertions

Line 103: We are wrapping our user data (object values from fixture file), and for each one of them we are retrieving it’s value (actual value from fixtures file) and index (0,1,2..) that will serve as index for table column in next step

Line 104: we are wrapping entire data from the row, we are finding index of each column, and we are asserting that it has correct value defined in the fixtures file. This is basically the same thing we did in previous test from the line 78 to 83. Again, see how we made it better here using different code style?

:information_source: Learn more about JS objects:

[Keys Values Entries](https://javascript.info/keys-values-entries)
[JS Objects](https://www.w3schools.com/js/js_objects.asp)

## Upload

Upload functionality allows us to submit a file to our web app. There is a simple way of how to handle that in Cypress.

Let’s create our upload test and explain it. 

1: Install a library for uploading files, run in VS terminal following command: 

`npm install cypress-file-upload`

2: Add this line to `support/commands.js` file: 

`import 'cypress-file-upload';`

3: First create a new test file called `upload.cy.js` under `e2e/elements_manipulation` folder.

4: Write following code inside: 

```javascript
/// <reference types="Cypress" />

describe('Upload: Upload actions', () => {
  before('Navigate to upload page', () => {
    cy.visit('/upload-download');
  });

  it('Check upload action', () => {
    // Upload file
    cy.get('#uploadFile').attachFile('pic.png');
    // Assert that path message is displayed
    cy.get('#uploadedFilePath').should('have.text', `C:\\fakepath\\pic.png`);
  });
});
```
5: Take this picture (download it, right click, save image as..) and move it to `fixtures` folder. Rename it to be for example: `pic.png`


![Image to download](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x19t1ll31qo7qrna80f6.png)

Code explanation: 

On the line: 5 we are visiting our app, and page containing upload functionality

You will see a “Choose file” input element, so what we want to do is to upload our picture to that element. To do that we will use `attachFile()` method from our library. You can see on line: 10 we are getting our input ID selector and performing upload over it. We also need to provide exact name and extension of our file, in this case `pic.png`. Cypress will automatically look for it in fixtures folder.

After doing that, our app will give us a text of a picture file location on the server, so for us in this case - that is a confirmation that image has been uploaded. We want to assert that text is present, so on line: 12 we are writing assertion to confirm that our text element contains text with correct path.

## HOMEWORK:

Write more scenarios for tables (searching, sorting, adding/removing records, etc). Visit links provided in the lesson, learn and understand Objects and Objects methods  in JS.

Don’t forget to push everything you did today on Github :wink: Remember git commands?

`git add .`

`git commit -am "add: table, upload tests"`

`git push`

<img width="100%" style="width:100%" src="https://media.tenor.com/H3f_qIuFXGMAAAAC/suits-harvey-specter.gif">

SEE YOU IN LESSON 8!

[Completed code for this lesson](https://github.com/ViktorijaFilipov/cypress-workshop/tree/main/LESSON_7)

If you have learned something new, feel free to support my work by buying me a coffee :coffee:

[![Buy Me A Coffee][2]][1]

[1]: https://www.buymeacoffee.com/viktorija3141
[2]: https://cdn.buymeacoffee.com/buttons/default-black.png