Conditional Testing with Cypress

WEB
5 min read

Dolt is Git for data and DoltHub is our web application that houses Dolt databases. We use Cypress.io as our end-to-end testing solution for DoltHub. To learn more about our journey with Cypress, check out our previous blogs:

When I had first started at DoltHub, I had no experience writing tests in Cypress. I thought conditional testing was pretty powerful and unintuitive. For Cypress masters, this may come across as pretty basic. But for me, once I grasped this concept, it really helped me write more effective, reusable tests.

Last we left off, we were using Cypress to log in and then test user functionality, but what should you do if you want to use the same tests when you're not logged in? Or what if we need to test based on whether or not an element is on a page? This is where conditional testing comes in.

When should we use conditional testing?

First, we have to consider whether or not conditional testing is actually what we want to do. The only way to avoid flakey tests is to ensure the DOM is completely loaded and settled. Luckily, that's the case for our example. In future blog posts, we'll go over what to do if you are unsure about the state of the DOM.

Today we'll be testing our 'Add' button. The dropdown for the add button shows different options based on if the user is logged in or out, so we need to handle these different conditions in the tests for the element. When not logged in, a user can only make a new issue and a new pull request. Once logged in, and with the correct permissions, a user can also create a new table, upload a file, create docs, and create bounties. We want to check for each of these links depending on the logged in state.

Logged In Dropdown

Logged in Dropdown

Logged Out Dropdown

Logged out Dropdown

Sharing tests across many pages

At DoltHub, we like to section out our Cypress tests into different files to help with readability. We have three different folders for tests, public paths (logged out), private paths (logged in), and shared tests. The tests for the add button go in the shared tests folder and are used in tests for both public and private paths.

Looking at our two options we notice that there are two links that persist whether or not we're logged in, we'll test for those first.

function addDropdown() {
  cy.get("[data-cy=dropdown-new-issue-link]").should("be.visible");
  cy.get("[data-cy=dropdown-new-pull-request-link]").should("be.visible");
}

Now we use our conditionals. Luckily, conditionals and testing functions work exactly as you assume they would and it's as simple as assigning a variable to your test function!

function addDropdown(loggedIn: boolean) {
  cy.get("[data-cy=dropdown-new-issue-link]").should("be.visible");
  cy.get("[data-cy=dropdown-new-pull-request-link]").should("be.visible");

  if (loggedIn) {
    // Conditional tests here
  }
}

Finally add your conditional tests, and you're good to go!

function addDropdown(loggedIn: boolean) {
  cy.get("[data-cy=dropdown-new-issue-link]").should("be.visible");
  cy.get("[data-cy=dropdown-new-pull-request-link]").should("be.visible");

  if (loggedIn) {
    cy.get("[data-cy=dropdown-create-new-table-link]").should("be.visible");
    cy.get("[data-cy=dropdown-upload-a-file-link]").should("be.visible");
    cy.get("[data-cy=dropdown-new-docs-link]").should("be.visible");
    cy.get("[data-cy=dropdown-new-bounty-link]").should("be.visible");
  }
}

Getting fancy with it

You can even get really complicated with it. In the codeblock below I'm conditionally checking depending on three variables. If the user is logged in, if the database has docs (a user cannot make new docs if they're already created), and whether or not the user has the rights to make a bounty. It also makes sure that these links aren't there if they're not supposed to be.

export default function addDropdown(
  loggedIn: boolean,
  hasDocs: boolean,
  hasPerms: boolean
) {
  cy.get("[data-cy=dropdown-new-issue-link]").should("be.visible");
  cy.get("[data-cy=dropdown-new-pull-request-link]").should("be.visible");

  if (loggedIn) {
    cy.get("[data-cy=dropdown-create-new-table-link]").should("be.visible");
    cy.get("[data-cy=dropdown-upload-a-file-link]").should("be.visible");

    if (hasDocs) {
      cy.get("[data-cy=dropdown-new-docs-link]").should("not.exist");
    } else {
      cy.get("[data-cy=dropdown-new-docs-link]").should("be.visible");
    }

    if (hasPerms) {
      cy.get("[data-cy=dropdown-new-bounty-link]").should("be.visible");
    } else {
      cy.get("[data-cy=dropdown-new-bounty-link]").should("not.exist");
    }
  } else {
    cy.get("[data-cy=dropdown-create-new-table-link]").should("not.exist");
    cy.get("[data-cy=dropdown-upload-a-file-link]").should("not.exist");
    cy.get("[data-cy=dropdown-new-docs-link]").should("not.exist");
    cy.get("[data-cy=dropdown-new-bounty-link]").should("not.exist");
  }
}

Conditional testing based on the presence of an element

But what if you want to run different tests depending on the presence of an element on your DOM? We use that kind of conditional testing to clean up leftover databases in case our test suite that creates and deletes a temporary database to test changing state (i.e. adding/deleting data, issues, pull requests, etc) fails. Since our Cypress tests run against production, this cleanup test ensures we don't leave behind clutter after our tests have run.

cy.get("body", opts).then(($body) => {
  // Check if cypresstesting databases exist
  if ($body.text().includes(`No search results for "${owner}"`)) {
    cy.get("[data-cy=no-repos-msg]").should("be.visible");
    cy.get("[data-cy=navbar-desktop-profile-link]", opts).click();
    cy.location("href", opts).should(
      "eq",
      `${Cypress.config().baseUrl}/profile`
    );
  } else {
    // If they do exist, go through each database and delete
    cy.get(
      "[data-cy=repository-list-most-recent] [data-cy=repo-list-item] a",
      // opts is our default options that we've imported from another shared file
      opts
    ).then((items) => {
      [...items].forEach((i) => {
        deleteDatabase(i.getAttribute("href"));
      });
    });
    // after deleting, automated_testing lands on a page that has different navbar, redirect to profile to get the same navbar data-cy for signout
    cy.visitPage("/profile", loggedIn);
  }
});

Sometimes tests like this can be unstable or flaky. Unless you can guarantee that you know the state of your DOM, we would not recommend testing this way. We currently have these tests commented out, and are looking for a better solution. Read more about testing with an unstable DOM here.

Conclusion

Cypress is a wonderful testing tool that can help you test whatever you need. Throughout our time using it, we ended up making a lot of helper functions to increase reusability and readability. If you're interested in learning more, make sure to take a look at the open source code for our Cypress tests, or join us on Discord to chat about it more!

SHARE

JOIN THE DATA EVOLUTION

Get started with Dolt

Or join our mailing list to get product updates.