diff --git a/content/guides/core-concepts/conditional-testing.md b/content/guides/core-concepts/conditional-testing.md index a0c5a6e23d..750aeafdeb 100644 --- a/content/guides/core-concepts/conditional-testing.md +++ b/content/guides/core-concepts/conditional-testing.md @@ -557,25 +557,3 @@ on other commands. If you cannot accurately know the state of your application then no matter what programming idioms you have available - **you cannot write 100% deterministic tests**. - -Still not convinced? - -Not only is this an anti-pattern, but it's an actual logical fallacy. - -You may think to yourself... okay fine, but 4 seconds - man that's not enough. -Network requests could be slow, let's bump it up to 1 minute! - -Even then, it's still possible a WebSocket message could come in... so 5 -minutes! - -Even then, not enough, it's possible a `setTimeout` could trigger... 60 minutes. - -As you approach infinity your confidence does continue to rise on the chances -you could prove the desired state will be reached, but you can never prove it -will. Instead you could theoretically be waiting for the heat death of the -universe for a condition to come that is only a moment away from happening. -There is no way to prove or disprove that it _may_ conditionally happen. - -You, the test writer, must know ahead of time what your application is -programmed to do - or have 100% confidence that the state of a mutable object -(like the DOM) has stabilized in order to write accurate conditional tests. diff --git a/content/guides/core-concepts/introduction-to-cypress.md b/content/guides/core-concepts/introduction-to-cypress.md index 7736c7fc03..da6a930379 100644 --- a/content/guides/core-concepts/introduction-to-cypress.md +++ b/content/guides/core-concepts/introduction-to-cypress.md @@ -852,102 +852,40 @@ the timeout is reached, the test will fail. -### Commands Are Promises +### The Cypress Command Queue -This is the big secret of Cypress: we've taken our favorite pattern for -composing JavaScript code, Promises, and built them right into the fabric of -Cypress. Above, when we say we're enqueuing actions to be taken later, we could -restate that as "adding Promises to a chain of Promises". +While the API may look similar to Promises, with it's `then()` syntax, Cypress +commands are not promises - they are serial commands passed into a central +queue, to be executed asynchronously at a later date. These commands are +designed to deliver deterministic, repeatable and consistent tests. -Let's compare the prior example to a fictional version of it as raw, -Promise-based code: - -#### Noisy Promise demonstration. Not valid code. - -```js -it('changes the URL when "awesome" is clicked', () => { - // THIS IS NOT VALID CODE. - // THIS IS JUST FOR DEMONSTRATION. - return cy - .visit('/my/resource/path') - .then(() => { - return cy.get('.awesome-selector') - }) - .then(($element) => { - // not analogous - return cy.click($element) - }) - .then(() => { - return cy.url() - }) - .then((url) => { - expect(url).to.eq('/my/resource/path#awesomeness') - }) -}) -``` - -#### How Cypress really looks, Promises wrapped up and hidden from us. - -```javascript -it('changes the URL when "awesome" is clicked', () => { - cy.visit('/my/resource/path') - - cy.get('.awesome-selector').click() - - cy.url().should('include', '/my/resource/path#awesomeness') -}) -``` - -Big difference! In addition to reading much cleaner, Cypress does more than -this, because **Promises themselves have no concepts of -[retry-ability](/guides/core-concepts/retry-ability)**. - -Without [**retry-ability**](/guides/core-concepts/retry-ability), assertions -would randomly fail. This would lead to flaky, inconsistent results. This is -also why we cannot use new JS features like `async / await`. - -Cypress cannot yield you primitive values isolated away from other commands. -That is because Cypress commands act internally like an asynchronous stream of -data that only resolve after being affected and modified **by other commands**. -This means we cannot yield you discrete values in chunks because we have to know -everything about what you expect before handing off a value. - -These design patterns ensure we can create **deterministic**, **repeatable**, -**consistent** tests that are **flake free**. +Almost all commands come with built-in +[retry-ability](/guides/core-concepts/retry-ability)**. Without +[**retry-ability**](/guides/core-concepts/retry-ability), assertions +would randomly fail. This would lead to flaky, inconsistent results. -Cypress is built using Promises that come from -[Bluebird](http://bluebirdjs.com/). However, Cypress commands do not return -these typical Promise instances. Instead we return what's called a `Chainer` -that acts like a layer sitting on top of the internal Promise instances. - -For this reason you cannot **ever** return or assign anything useful from -Cypress commands. - -If you'd like to learn more about handling asynchronous Cypress Commands please -read our [Core Concept Guide](/guides/core-concepts/variables-and-aliases). +While Cypress is built using Promises that come from +[Bluebird](http://bluebirdjs.com/), these are not what we expose +as commands and assertions on `cy`. If you'd like to learn more about +handling asynchronous Cypress Commands please read our +[Core Concept Guide](/guides/core-concepts/variables-and-aliases). -### Commands Are Not Promises - -The Cypress API is not an exact 1:1 implementation of Promises. They have -Promise like qualities and yet there are important differences you should be -aware of. +Commands also have some design choices that developers who are used to promise-based +testing may find unexpected. They are intentional decisions on Cypress' part, +not technical limitations. 1. You cannot **race** or run multiple commands at the same time (in parallel). -2. You cannot 'accidentally' forget to return or chain a command. -3. You cannot add a `.catch` error handler to a failed command. - -There are _very_ specific reasons these limitations are built into the Cypress -API. +2. You cannot add a `.catch` error handler to a failed command. -The whole intention of Cypress (and what makes it very different from other +The whole purpose of Cypress (and what makes it very different from other testing tools) is to create consistent, non-flaky tests that perform identically from one run to the next. Making this happen isn't free - there are some trade-offs we make that may initially seem unfamiliar to developers accustomed -to working with Promises. +to working with Promises or other libraries. Let's take a look at each trade-off in depth: @@ -971,43 +909,6 @@ manner in order to create consistency. Because integration and e2e tests primarily mimic the actions of a real user, Cypress models its command execution model after a real user working step by step. -#### You cannot accidentally forget to return or chain a command - -In real promises it's very easy to 'lose' a nested Promise if you don't return -it or chain it correctly. - -Let's imagine the following Node code: - -```js -// assuming we've promisified our fs module -return fs.readFile('/foo.txt', 'utf8').then((txt) => { - // oops we forgot to chain / return this Promise - // so it essentially becomes 'lost'. - // this can create bizarre race conditions and - // bugs that are difficult to track down - fs.writeFile('/foo.txt', txt.replace('foo', 'bar')) - - return fs.readFile('/bar.json').then((json) => { - // ... - }) -}) -``` - -The reason this is even possible to do in the Promise world is because you have -the power to execute multiple asynchronous actions in parallel. Under the hood, -each promise 'chain' returns a promise instance that tracks the relationship -between linked parent and child instances. - -Because Cypress enforces commands to run _only_ serially, you do not need to be -concerned with this in Cypress. We enqueue all commands onto a _global_ -singleton. Because there is only ever a single command queue instance, it's -impossible for commands to ever be _'lost'_. - -You can think of Cypress as "queueing" every command. Eventually they'll get run -and in the exact order they were used, 100% of the time. - -There is no need to ever `return` Cypress commands. - #### You cannot add a `.catch` error handler to a failed command In Cypress there is no built in error recovery from a failed command. A command @@ -1020,12 +921,10 @@ You might be wondering: > does (or doesn't) exist, I choose what to do? The problem with this question is that this type of conditional control flow -ends up being non-deterministic. This means it's impossible for a script (or -robot), to follow it 100% consistently. - -In general, there are only a handful of very specific situations where you _can_ -create control flow. Asking to recover from errors is actually the same as -asking for another `if/else` control flow. +ends up being non-deterministic. This means different test runs may behave +differently, which makes them less deterministic and consistent. In general, +there are only a handful of very specific situations where you _can_ +create control flow using Cypress commands. With that said, as long as you are aware of the potential pitfalls with control flow, it is possible to do this in Cypress!