Skip to content

Commit 933cd58

Browse files
author
Blue F
authored
Merge pull request #4148 from cypress-io/commands-are-not-promises
More clearly explain that cypress commands are not promises
2 parents f41cd75 + 77fdca4 commit 933cd58

File tree

2 files changed

+24
-147
lines changed

2 files changed

+24
-147
lines changed

content/guides/core-concepts/conditional-testing.md

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -557,25 +557,3 @@ on other commands.
557557
If you cannot accurately know the state of your application then no matter what
558558
programming idioms you have available - **you cannot write 100% deterministic
559559
tests**.
560-
561-
Still not convinced?
562-
563-
Not only is this an anti-pattern, but it's an actual logical fallacy.
564-
565-
You may think to yourself... okay fine, but 4 seconds - man that's not enough.
566-
Network requests could be slow, let's bump it up to 1 minute!
567-
568-
Even then, it's still possible a WebSocket message could come in... so 5
569-
minutes!
570-
571-
Even then, not enough, it's possible a `setTimeout` could trigger... 60 minutes.
572-
573-
As you approach infinity your confidence does continue to rise on the chances
574-
you could prove the desired state will be reached, but you can never prove it
575-
will. Instead you could theoretically be waiting for the heat death of the
576-
universe for a condition to come that is only a moment away from happening.
577-
There is no way to prove or disprove that it _may_ conditionally happen.
578-
579-
You, the test writer, must know ahead of time what your application is
580-
programmed to do - or have 100% confidence that the state of a mutable object
581-
(like the DOM) has stabilized in order to write accurate conditional tests.

content/guides/core-concepts/introduction-to-cypress.md

Lines changed: 24 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -852,102 +852,40 @@ the timeout is reached, the test will fail.
852852

853853
</Alert>
854854

855-
### Commands Are Promises
855+
### The Cypress Command Queue
856856

857-
This is the big secret of Cypress: we've taken our favorite pattern for
858-
composing JavaScript code, Promises, and built them right into the fabric of
859-
Cypress. Above, when we say we're enqueuing actions to be taken later, we could
860-
restate that as "adding Promises to a chain of Promises".
857+
While the API may look similar to Promises, with it's `then()` syntax, Cypress
858+
commands are not promises - they are serial commands passed into a central
859+
queue, to be executed asynchronously at a later date. These commands are
860+
designed to deliver deterministic, repeatable and consistent tests.
861861

862-
Let's compare the prior example to a fictional version of it as raw,
863-
Promise-based code:
864-
865-
#### Noisy Promise demonstration. Not valid code.
866-
867-
```js
868-
it('changes the URL when "awesome" is clicked', () => {
869-
// THIS IS NOT VALID CODE.
870-
// THIS IS JUST FOR DEMONSTRATION.
871-
return cy
872-
.visit('/my/resource/path')
873-
.then(() => {
874-
return cy.get('.awesome-selector')
875-
})
876-
.then(($element) => {
877-
// not analogous
878-
return cy.click($element)
879-
})
880-
.then(() => {
881-
return cy.url()
882-
})
883-
.then((url) => {
884-
expect(url).to.eq('/my/resource/path#awesomeness')
885-
})
886-
})
887-
```
888-
889-
#### How Cypress really looks, Promises wrapped up and hidden from us.
890-
891-
```javascript
892-
it('changes the URL when "awesome" is clicked', () => {
893-
cy.visit('/my/resource/path')
894-
895-
cy.get('.awesome-selector').click()
896-
897-
cy.url().should('include', '/my/resource/path#awesomeness')
898-
})
899-
```
900-
901-
Big difference! In addition to reading much cleaner, Cypress does more than
902-
this, because **Promises themselves have no concepts of
903-
[retry-ability](/guides/core-concepts/retry-ability)**.
904-
905-
Without [**retry-ability**](/guides/core-concepts/retry-ability), assertions
906-
would randomly fail. This would lead to flaky, inconsistent results. This is
907-
also why we cannot use new JS features like `async / await`.
908-
909-
Cypress cannot yield you primitive values isolated away from other commands.
910-
That is because Cypress commands act internally like an asynchronous stream of
911-
data that only resolve after being affected and modified **by other commands**.
912-
This means we cannot yield you discrete values in chunks because we have to know
913-
everything about what you expect before handing off a value.
914-
915-
These design patterns ensure we can create **deterministic**, **repeatable**,
916-
**consistent** tests that are **flake free**.
862+
Almost all commands come with built-in
863+
[retry-ability](/guides/core-concepts/retry-ability)**. Without
864+
[**retry-ability**](/guides/core-concepts/retry-ability), assertions
865+
would randomly fail. This would lead to flaky, inconsistent results.
917866

918867
<Alert type="info">
919868

920-
Cypress is built using Promises that come from
921-
[Bluebird](http://bluebirdjs.com/). However, Cypress commands do not return
922-
these typical Promise instances. Instead we return what's called a `Chainer`
923-
that acts like a layer sitting on top of the internal Promise instances.
924-
925-
For this reason you cannot **ever** return or assign anything useful from
926-
Cypress commands.
927-
928-
If you'd like to learn more about handling asynchronous Cypress Commands please
929-
read our [Core Concept Guide](/guides/core-concepts/variables-and-aliases).
869+
While Cypress is built using Promises that come from
870+
[Bluebird](http://bluebirdjs.com/), these are not what we expose
871+
as commands and assertions on `cy`. If you'd like to learn more about
872+
handling asynchronous Cypress Commands please read our
873+
[Core Concept Guide](/guides/core-concepts/variables-and-aliases).
930874

931875
</Alert>
932876

933-
### Commands Are Not Promises
934-
935-
The Cypress API is not an exact 1:1 implementation of Promises. They have
936-
Promise like qualities and yet there are important differences you should be
937-
aware of.
877+
Commands also have some design choices that developers who are used to promise-based
878+
testing may find unexpected. They are intentional decisions on Cypress' part,
879+
not technical limitations.
938880

939881
1. You cannot **race** or run multiple commands at the same time (in parallel).
940-
2. You cannot 'accidentally' forget to return or chain a command.
941-
3. You cannot add a `.catch` error handler to a failed command.
942-
943-
There are _very_ specific reasons these limitations are built into the Cypress
944-
API.
882+
2. You cannot add a `.catch` error handler to a failed command.
945883

946-
The whole intention of Cypress (and what makes it very different from other
884+
The whole purpose of Cypress (and what makes it very different from other
947885
testing tools) is to create consistent, non-flaky tests that perform identically
948886
from one run to the next. Making this happen isn't free - there are some
949887
trade-offs we make that may initially seem unfamiliar to developers accustomed
950-
to working with Promises.
888+
to working with Promises or other libraries.
951889

952890
Let's take a look at each trade-off in depth:
953891

@@ -971,43 +909,6 @@ manner in order to create consistency. Because integration and e2e tests
971909
primarily mimic the actions of a real user, Cypress models its command execution
972910
model after a real user working step by step.
973911

974-
#### You cannot accidentally forget to return or chain a command
975-
976-
In real promises it's very easy to 'lose' a nested Promise if you don't return
977-
it or chain it correctly.
978-
979-
Let's imagine the following Node code:
980-
981-
```js
982-
// assuming we've promisified our fs module
983-
return fs.readFile('/foo.txt', 'utf8').then((txt) => {
984-
// oops we forgot to chain / return this Promise
985-
// so it essentially becomes 'lost'.
986-
// this can create bizarre race conditions and
987-
// bugs that are difficult to track down
988-
fs.writeFile('/foo.txt', txt.replace('foo', 'bar'))
989-
990-
return fs.readFile('/bar.json').then((json) => {
991-
// ...
992-
})
993-
})
994-
```
995-
996-
The reason this is even possible to do in the Promise world is because you have
997-
the power to execute multiple asynchronous actions in parallel. Under the hood,
998-
each promise 'chain' returns a promise instance that tracks the relationship
999-
between linked parent and child instances.
1000-
1001-
Because Cypress enforces commands to run _only_ serially, you do not need to be
1002-
concerned with this in Cypress. We enqueue all commands onto a _global_
1003-
singleton. Because there is only ever a single command queue instance, it's
1004-
impossible for commands to ever be _'lost'_.
1005-
1006-
You can think of Cypress as "queueing" every command. Eventually they'll get run
1007-
and in the exact order they were used, 100% of the time.
1008-
1009-
There is no need to ever `return` Cypress commands.
1010-
1011912
#### You cannot add a `.catch` error handler to a failed command
1012913

1013914
In Cypress there is no built in error recovery from a failed command. A command
@@ -1020,12 +921,10 @@ You might be wondering:
1020921
> does (or doesn't) exist, I choose what to do?
1021922
1022923
The problem with this question is that this type of conditional control flow
1023-
ends up being non-deterministic. This means it's impossible for a script (or
1024-
robot), to follow it 100% consistently.
1025-
1026-
In general, there are only a handful of very specific situations where you _can_
1027-
create control flow. Asking to recover from errors is actually the same as
1028-
asking for another `if/else` control flow.
924+
ends up being non-deterministic. This means different test runs may behave
925+
differently, which makes them less deterministic and consistent. In general,
926+
there are only a handful of very specific situations where you _can_
927+
create control flow using Cypress commands.
1029928

1030929
With that said, as long as you are aware of the potential pitfalls with control
1031930
flow, it is possible to do this in Cypress!

0 commit comments

Comments
 (0)