1

I have a cypress test that checks the table on a page, however there are two different tables that could render depending on the table type conditional. If the type of the table is product one table will render, if the type is equipment a different table will render.

How can I write this cypress test to check IF that specific table is on the page, then run a certain set of checks and vis versa?

Test description:

The conditional is if the selectedIssueKind is Product or Equipment. The next page will have tables with different column headers and info.

My thought was to have the test check the data-class and then do the tests for that data-class table. So:

if ('[data-cy=product-issue-tracker-table]') { product table tests} else {equipment table tests}

However, if I click on an equipment issue I get an error that it cannot find Part ID, but that's because it's trying to check the wrong table (product), so my if ('[data-cy=product-issue-tracker-table]') check, doesnt seem to work.

Any tips for working with conditionals would be greatly appreciated! If you need anymore information please let me know!

Cheers!

Cypress test

it.only('Issue Tracker table exists, column headers are correct and there is data', () => {
    if ('[data-cy=product-issue-tracker-table]') {
      cy.get('[data-cy=issue-tracker-table] table').contains('th', 'Issue ID')
      cy.get('[data-cy=issue-tracker-table] table').contains('th', 'Part ID')
      cy.get('[data-cy=issue-tracker-table] table').contains(
        'th',
        'Station Name'
      )
      cy.get('[data-cy=issue-tracker-table] table').contains(
        'th',
        'Description'
      )
      cy.get('[data-cy=issue-tracker-table] table').contains('th', 'SN')
      cy.get('[data-cy=issue-tracker-table] table').contains('th', 'Timestamp')
      cy.contains('td', /\w/g).should('be.visible')
    } else {
      cy.get('[data-cy=issue-tracker-table] table').contains('th', 'Issue ID')
      cy.get('[data-cy=issue-tracker-table] table').contains('th', 'Part')
      cy.get('[data-cy=issue-tracker-table] table').contains(
        'th',
        'Station Name'
      )
      cy.get('[data-cy=issue-tracker-table] table').contains(
        'th',
        'Description'
      )
      cy.get('[data-cy=issue-tracker-table] table').contains('th', 'Start Time')
      cy.get('[data-cy=issue-tracker-table] table').contains('th', 'End Time')
      cy.contains('td', /\w/g).should('be.visible')
    }
  })

HTML

<div data-cy="issue-tracker-table">
  <div class="vgt-wrap issue-tracker-table-style cursor-pointer mb-12" data-cy="product-issue-tracker-table" pagination="[object Object]">
    <!--v-if--><div class="vgt-inner-wrap"><!--v-if--><!--v-if--><!--v-if-->
    <div class="vgt-fixed-header"><!--v-if-->
    </div>
    <div class="vgt-responsive">
      <table id="vgt-table" class="vgt-table condensed">
        <colgroup>
          <col id="col-0">
          <col id="col-1">
          <col id="col-2">
          <col id="col-3">
          <col id="col-4">
          <col id="col-5">
          <col id="col-6">
            
       </colgroup>
        <!-- Table header -->
        <thead>
          <tr><!--v-if--><!--v-if-->
            <th scope="col" class="vgt-left-align" aria-sort="descending" aria-controls="col-0" style="min-width: auto; width: auto;">
              <span>Issue ID</span>
              <!--v-if-->
            </th>
            <th scope="col" class="vgt-left-align" aria-sort="descending" aria-controls="col-1" style="min-width: auto; width: auto;">
              <span>Part ID</span>
              <!--v-if-->
            </th>
            <th scope="col" class="vgt-left-align" aria-sort="descending" aria-controls="col-2" style="min-width: auto; width: auto;">
              <span>Station Name</span><!--v-if-->
            </th>
            <th scope="col" class="vgt-left-align" aria-sort="descending" aria-controls="col-3" style="min-width: auto; width: auto;">
              <span>Description</span><!--v-if-->
            </th>
            <th scope="col" class="vgt-left-align" aria-sort="descending" aria-controls="col-4" style="min-width: auto; width: auto;">
              <span>SN</span>
              <!--v-if-->
            </th>
            <th scope="col" class="vgt-left-align" aria-sort="descending" aria-controls="col-5" style="min-width: auto; width: auto;">
              <span>Timestamp</span>
              <!--v-if-->
            </th>
            <th scope="col" class="vgt-left-align" aria-sort="descending" aria-controls="col-6" style="min-width: auto; width: auto;">
              <span></span>
              <!--v-if-->
            </th>
              
            </tr><!--v-if-->
        </thead><!-- Table body starts here -->
        <tbody><!-- if group row header is at the top --><!--v-if--><!-- normal rows here. we loop over all rows -->
          <tr class="">
            <!--v-if--><!--v-if-->
            <td class="vgt-left-align">
              <span>1</span>
                
            </td>
            <td class="vgt-left-align">
              <span></span>
                
            </td>
            <td class="vgt-left-align">
              <span></span>
                
            </td>
            <td class="vgt-left-align">
              <span> Connect to DUT by SSH</span>
                
            </td>
            <td class="vgt-left-align">
              <span></span>
                
            </td>
            <td class="vgt-left-align">
              <span>Mar 28 2022 - 10:01 AM</span>
                
            </td>
            <td class="vgt-left-align"><!--v-if--><!--v-if-->
              <div data-cy="issue-tracker-view-data-button">
                <button class="primary-button">View Data</button>
                  
              </div><!--v-if-->
            </td>
              
            </tr><!-- if group row header is at the bottom --><!--v-if-->
        </tbody><!--v-if-->
      </table>
        
      </div><!--v-if--><!--v-if-->
    </div>
      
    </div><!--v-if--><!-- Issue Data Modals --><!--v-if--><!--v-if--><!-- Issue Data Modals --><!-- Add New Analysis section -->
  <div class="flex">
    <span class="tab font-normal text-card-orange bg-card-orange bg-opacity-20 py-1 px-4 mb-3 mr-9 rounded-full w-max">Analysis Records</span>
    <!--v-if--><!--v-if-->
    <div data-cy="add-new-analysis-button">
      <button class="primary-button">Add New Analysis</button>
        
      </div><!--v-if--></div>
        
 </div>
Fody
  • 23,754
  • 3
  • 20
  • 37
LovelyAndy
  • 841
  • 8
  • 22
  • 1
    One other question, what is `cy.contains('td', /\w/g).should('be.visible')` aiming to do? It looks like it should be more substantial, as there would be many `td` in the table. – Fody Apr 27 '22 at 19:17
  • I just wanted to do a quick check to see if there was data populating in those `td`s. No the best check I know, but my task is a bit too vague/time-limited to get super granular into what each `td` is being populated with. Totally get that it's not verbose enough:l – LovelyAndy Apr 27 '22 at 19:20
  • 1
    Ok, so `cy.contains()` only returns one element, so you'll need to add an `.each()`. I'll add the code below. – Fody Apr 27 '22 at 19:23

2 Answers2

1

So, this sort of indeterminate testing is not a great practice. The ideal would be to know for sure that you are testing one table or the other.

The conditional is if the selectedIssueKind is Product or Equipment. The next page will have tables with different column headers and info.

Based on that description, I'd think that we could easily split this into two (or more) tests.

// pseudo-code
describe('table tests', () => {
  beforeEach(() => {
    // shared setup
  })

  it('tests the product table', () => {
    // in the product test, we'll select the `selectedIssueKind` of product
    // unclear from the HTML/Cypress how you're doing that, so very vague line below
    cy.get('product').click();
    // code to test the product table
  });

  it('tests the equipment table', () => {
    // in the equipment test, we'll select the `selectedIssueKind` of equipment
    // same as above, very vague example
    cy.get('equipment').click();
    // code to test the equipment table
  });
});

Also, if every test in your spec needed a selectedIssueKind, you could move that logic into your beforeEach(), set an environment variable in your it(), and not repeat that in each test.

// psuedo-code

describe('test', () => {
  beforeEach(() => {
    // other setup
    // grab the cypress environment variable as your selector
    cy.get(Cypress.env('selectedIssueKind')).click();
  })

  it('product test', { env: selectedIssueKind: 'product' } }, () => {
    // code
  });

  it('equipment test', { env: selectedIssueKind: 'equipment' } }, () => {
    // code
  });
});
agoff
  • 5,818
  • 1
  • 7
  • 20
  • Thanks again for the comment! I didn't really consider breaking these up into different tests, I figured since I was doing all of the tests on one page (the page has multiple nested routes) that they would all be in one test. Seems here that I will need to make another spec.js test to get this test implimented so I can use the forEach. I'll give it a shot! – LovelyAndy Apr 27 '22 at 18:49
1

There's a bit of repetition in the test that may be reduced by getting an array of table headers.

You may be testing more than headers, but this gives you the idea.

const headers = ['Issue ID', ...]  // add all the common headers

cy.get('[data-cy="issue-tracker-table"]')
  .then($table => {
    return $table.find('[data-cy=product-issue-tracker-table]').length > 0
  })
  .then(console.log)              // for checking isProduct value
  .then(isProduct => {
    if (isProduct) {
      return headers.concat(['SN', 'Timestamp'])
    } else {
      return headers.concat(['Start Time', 'End Time'])
    }
  })
  .then(console.log)              // for checking headers array
  .then(headers => {
    // One test here
    cy.get('[data-cy=issue-tracker-table] table thead th')
      .each(($th, index, $list)) => {
        // last header is empty, presume you don't want to test it
        if (index < $list.length -1 ) {       
          expect($th.text()).to.eq(headers[index])
        }
      })
  })

Breaking down the steps is useful as well, you can incrementally check each step by (temporarily) logging the result of .then().


I notice the <!--v-if--> which shows you are using a Vue app.

If you want to get really sophisticated you can have the app tell Cypress which kind of table is being displayed. This is an app-action and it reduces risks with asynchronous loading.

The exact code depends on your Vue component, but the pattern is

// in Vue component

if (window.Cypress) {
  window.tableType = tableType
}
// in test

cy.window()
  .then(win => {
    return win.tableType === 'Product'
  })
  .then(console.log)              // for checking isProduct value
  .then(isProduct => {
    ...

Testing all <td> has content and visibility

cy.get('[data-cy=issue-tracker-table] table tbody td')
  .each($td => {
    cy.wrap($td)
      .contains(/\w/)
      .should('be.visible')
  })

Alternative step: tableType instead of isProduct

cy.get('[data-cy="issue-tracker-table"]')
  .then($table => {
    if ($table.find('[data-cy=product-issue-tracker-table]').length > 0) {
      return 'Product'
    } else {
      return 'Equipment'
    }
  })
  .then(console.log)              // for checking tableType value
  .then(tableType => {
    if (tableType === 'Product') {
      return headers = headers.concat(['SN', 'Timestamp'])
    } else {
      return headers = headers.concat(['Start Time', 'End Time'])
    }
  })
  .then(console.log)              // for checking headers array
  .then(headers => {
    // One test here
    cy.get('[data-cy=issue-tracker-table] table thead th')
      .each(($th, index, $list) => {
        // test header here
        if (index < $list.length -1 ) {
          expect($th.text().trim()).to.eq(headers[index])
        }
      })
  })
Fody
  • 23,754
  • 3
  • 20
  • 37
  • Thanks for the comment again! I have a few confirmation questions about the code you've written. 1. Is the `headers` all of the different headers there are, or just the ones both product and equipment share? Then you are just adding `['SN', 'Timestamp']` if it's a product issue? 2. What is the `isProduct` in the second `.then()`? – LovelyAndy Apr 27 '22 at 19:05
  • 1
    Yes, it looks like the first 4 headers are the same, so the `headers` variable at the top is initialized with those. Then add the last two columns depending on the table type using `.concat()` function for joining arrays. – Fody Apr 27 '22 at 19:09
  • 1
    `isProduct` is the result of this check `$table.find('[data-cy=product-issue-tracker-table]').length > 0`. If the `.find()` succeeds it has a length 1, but if it fails it has length 0. The result value `isProduct` is a Boolean, `true` or `false`. That result is passed on down the `.then()` steps. – Fody Apr 27 '22 at 19:12
  • 1
    The `.then(console.log)` is just to check that the correct value is being calculated, and you can remove them once you have it working. – Fody Apr 27 '22 at 19:14
  • That `isProduct` check is really cool! I'm still setting the test up right now, but would I duplicate these for the *Equipment* table check then? – LovelyAndy Apr 27 '22 at 19:24
  • 1
    No, once the `.then(isProduct => { ... })` step is complete you either have the headers for Product or for Equipment so the remaining step is common to both table types. – Fody Apr 27 '22 at 19:28
  • 1
    You can change the naming of `isProduct` to `productType` and give it string value `Product` or `Equipment` if you think that's clearer coding. Let me know, I'll make the change. – Fody Apr 27 '22 at 19:31
  • I think the `productType` makes it a bit more readable, but I don't really understand the second half with string value of `Product` or `Equipment`. – LovelyAndy Apr 27 '22 at 19:37
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/244264/discussion-between-lovelyandy-and-fody). – LovelyAndy Apr 27 '22 at 19:37