DEV Community

Martin Chudomel
Martin Chudomel

Posted on

The Cypress Variable Trap: When if Runs Too Soon in Your Tests

Cypress tests look synchronous, feel synchronous, but absolutely aren’t synchronous.

One of the most common pitfalls I see (and experienced myself) is mixing plain JS variables with Cypress’ command queue, especially when values come from async sources like APIs.

Scenario

Let’s walk through a simple scenario that breaks more often than not:

  1. In before() hook, you fetch some value (e.g., from an API).
  2. You assign it to a variable like this:
let featureEnabled = false
before(() => {
  cy.request('/api/features').then(response => {
    featureEnabled = response.body.enabled
  })
})
Enter fullscreen mode Exit fullscreen mode

3. Then, in your test, you try to run logic based on that value:

it('does something when feature flag is enabled', () => {
  if (featureEnabled) {
    // test one thing
  } else {
    // test something else
  }
})
Enter fullscreen mode Exit fullscreen mode

The Catch

This if (featureEnabled) block is a normal synchronous JavaScript condition.

But the value is set inside a Cypress command cy.request().then(), which runs later — via Cypress’ internal command queue.

So what often happens is:

The if condition evaluates before the API call has actually resolved, meaning the variable still holds the initial value.

Even though the code looks like it should work from top to bottom.

The reliable fix: Keep it in Cypress’ world

The key lesson:

Never use plain variables in test conditions if they depend on async Cypress commands.

What works every time is handing the value over to Cypress using cy.wrap() + alias:

before(() => {
  cy.request('/api/features')
    .its('body.enabled')
    .then(value => {
      cy.wrap(value).as('featureEnabled')
    })
})
Enter fullscreen mode Exit fullscreen mode

Then in the test:

it('does something based on API feature flag', () => {
  cy.get('@featureEnabled').then(featureEnabled => {
    if (featureEnabled) {
      // test one thing
    } else {
      // test another
    }
  })
})
Enter fullscreen mode Exit fullscreen mode

Now the if statement is safely nested inside a Cypress–managed async callback, and it always runs with the correct resolved value.

No race conditions. No false positives. No flaky tests.

Side notes

  • cy.wrap().as() does not magically make the variable reactive, it just stores it safely as a test alias.
  • .then() is not just syntax, it’s essential: it keeps your logic sequential within Cypress’ execution model.

This same trap occurs with cy.wait(), cy.intercept(), or anything else that resolves asynchronously.

Rule of thumb

If a variable’s value comes from any cy.* command, don’t evaluate it outside Cypress’ chain — alias it and consume it via cy.get().then().

Summary

Cypress’ execution model is one of its superpowers, but also the source of many “why did that run too early?” bugs.

Once you stop fighting it and start embracing aliases with cy.wrap() and cy.get().then(), your test suite becomes:

✔ deterministic
✔ more readable
✔ and dramatically less flaky

Top comments (0)