Search
⌘K
    to navigateEnterto select Escto close

    Cypress Best Practices

    1. Login programmatically

    To test most of the functionalities, many of the web applications need the user to be logged in.

    Anti Pattern: Sharing page objects, using UI to log in, and not taking shortcuts. Best Practice: Test specs in isolation, programmatically log into the application, and take control of the application's state.

    2. Use proper selectors

    Every test we write will include selectors for elements. The CSS classes might change or be removed. We must use selectors that are resilient to changes.

    Anti-Pattern: Using highly brittle selectors that are subject to change. Best Practice: Use data-cy attributes to provide context to the selectors and isolate them from CSS or JS changes.

    The Selector Playground automatically follows these best practices. When determining a unique selector it will automatically prefer elements with: data-cy data-test

    We shall use data-cy to keep the consistency because it has the highest priority.

    When should we use cy.contains()?

    Sometimes, we need to select element with the text present in the page. In such scenarios, we might need to use cy.contains(). However, we need to ensure that the selected text is always present.

    If the content of the element gets changed, would we want the test to fail?

    • If the answer is yes: use cy.contains()
    • If the answer is no: use a data attribute.

    3. Don’t assign command’s return values

    Don’t assign command’s return values to variables.

    Anti-Pattern: Trying to assign the return value of commands with const, let, or var. Best Practice: Use closures to access and store what commands yield.

    1// DON’T DO THIS. IT DOES NOT WORK
    2const a = cy.get('a')
    3
    4cy.visit('https://example.cypress.io')
    5
    6// nope, it fails
    7a.first().click()

    4. Don't test external sites

    Anti-Pattern: Trying to visit or interact with sites or servers which we do not control. Best Practice: Only test what we can control. When necessary, always use cy.request() to talk to 3rd party servers via their APIs.

    5. Make every test independent of each other

    Anti-Pattern: Coupling multiple tests together i.e. making tests depend on previous/other tests. Best Practice: Tests should always be able to be run independently from one another and still pass. In Cypress, we can run tests in parallel. All the tests can only if they are not dependent on each other.

    6. Avoid single assertion tests

    We are testing behavior of the application, we are doing integration testing, not unit testing.

    Anti-Pattern: Acting like we're writing unit tests. Best Practice: Add multiple assertions and don't worry about it.

    7. Don’t clean state using after or afterEach hooks

    Anti-Pattern: Using after or afterEach hooks to clean up state. Best Practice: Clean up state before tests are run.

    8. Don't use wait for arbitrary time period

    Anti-Pattern: Waiting for arbitrary time periods using cy.wait(Number). Best Practice: Use route aliases or assertions to guard Cypress from proceeding until an explicit condition is met.

    9. Start Web Servers prior to running cypress

    Anti-Pattern: Trying to start a web server from within Cypress scripts with cy.exec() or cy.task() Best Practice: Start a web server prior to running Cypress.

    10. Set a global baseUrl

    Anti-Pattern: Using cy.visit() without setting a baseUrl. Best Practice: Set a baseUrl in your configuration file (cypress.json by default).

    11. Avoid { force: true }

    Cypress is an automation tool where it simulates the user behavior. If we use force: true on any event, it overrides the actionable checks Cypress applies by default i.e. visible, exist etc. If we try to automate without considering the user behavior, some issues might be identified later because these tests will pass even if application is not behaving as expected. There are some exceptions where we might need to use force: true. However, mostly we should avoid using it. To avoid force: true, we can assert for the parent or surrounding elements first before actually asserting the element. And even if it still doesn't work, we can try a plugin called Cypress Real Events.

    12. Use cy.contains() along with a selector

    When we use cy.contains() to select an element with some text or to assert.

    For example,

    1cy.contains("Sign up for free").click();

    But if the Sign up for free link is present multiple times on the same page, our test case might fail. To avoid our test case from failing, we can add a 'selector' with cy.contains(), so cypress would know exactly which string to consider. So whenever possible pass the selector with the text inside the contains function.

    For example,

    1cy.contains("[data-cy='sign-up-link']", "Sign up for free").click();

    13. Avoid chaining of array elements with .eq(), .first() etc. in the test case

    Whenever there's an array of elements is present and we want to use any specific element from that array, let's say first or last element we can specify it in with the selector itself instead of chaining the commands with .eq(), .first(), .last(), etc. There are chances of getting an Element detached from DOM error with this.

    For example,

    1// Incorrect
    2
    3cy.get("[data-cy=check-box]")
    4  .eq(0)
    5  .should("be.visible")
    6  .check();
    1// Correct
    2
    3cy.get("[data-cy=check-box]:eq(0)")
    4  .should("be.visible")
    5  .check();
    6
    7// OR
    8
    9cy.get("[data-cy=check-box]:nth-child(1)")
    10  .should("be.visible")
    11  .check();

    14. Use scrollIntoView() to detect partially visible elements

    Cypress sometimes fails to detect an element even if it exists and maybe partially visible as well. In such cases, we can use scrollIntoView with option as shown:

    1// without option
    2cy.get(clientsPageConfig.submitButton)
    3  .scrollIntoView()
    4  .click();
    5
    6// with option
    7cy.get(clientsPageConfig.submitButton)
    8  .scrollIntoView({ easing: 'linear' })

    15. Use loginPath instead of rootUrl for login test

    While writing login test visit loginPath instead of rootUrl as visiting rootUrl for testing login functionality might not work in every environment because not all application redirects to the login page when visiting the rootUrl. Even if it redirects to login, we can still reduce the time required for the login page redirection.

    1import { loginPath } from "Constants/routes";
    2
    3describe("Login", () => {
    4  beforeEach(() => {
    5    cy.visit(loginPath);
    6  })
    7})
    

    16. No need of common assertions for actionable elements before performing actions

    Whenever we want to perform any action on elements like button or input field e.g. click or type on them respectively. Asserting on them with .should("be.visible") or .should("exist") are not necessary. Cypress internally does this, so we don't need to do it explicitly. More at cypress

    1// incorrect
    2cy.get("[data-cy=submit-modal-button]")
    3  .should("be.visible")
    4  .click();
    5
    6// correct
    7cy.get("[data-cy=submit-modal-button]")
    8  .click();
    Previous
    Next