Cypress - Asynchronous Behavior
Cypress - Understanding Asynchronous Behavior
Section titled “Cypress - Understanding Asynchronous Behavior”Cypress commands don’t execute immediately like typical JavaScript code. Instead, they are enqueued and executed serially one after another. This is a core concept that makes Cypress tests reliable and predictable. While the commands themselves are asynchronous, Cypress manages this asynchronicity for you.
You cannot assign the result of a Cypress command to a variable directly using const result = cy.get(...) because cy.get() (and most cy commands) don’t return the element or value itself. They return a ‘chainer’ object, which is promise-like, allowing you to chain further commands or use .then() to work with the yielded subject.
The Command Queue and .then()
Section titled “The Command Queue and .then()”Cypress commands are added to a queue. Each command only runs after the previous one has completed successfully. To interact with the ‘subject’ (the value or element yielded by a command), you use .then().
// Test Suitedescribe('Asynchronous Behavior Example', () => { it('Should demonstrate command queuing and .then()', () => { cy.visit('https://your-app.com/login'); // Example URL
// This console.log executes immediately, before cy.visit even starts loading console.log('This logs first (standard JavaScript)');
cy.get('h1').invoke('text').then((headerText) => { // This code runs *after* cy.get() finds the h1 and invoke('text') gets its text console.log('Header text from .then():', headerText); // You can use headerText here for assertions or further logic expect(headerText).to.include('Login Page'); });
cy.get('input[name="email"]').type('test@example.com');
// This console.log is also a Cypress command due to cy.then() // It's enqueued and runs *after* the .type() command completes cy.then(() => { console.log('This logs after typing into email field (Cypress queued)'); });
// Another standard JavaScript console.log // This logs *before* the cy.then() above because it's not part of the Cypress chain console.log('This logs second (standard JavaScript, after first log, before Cypress logs from .then)'); });});Execution Order in the Console (Simplified):
- “This logs first (standard JavaScript)”
- “This logs second (standard JavaScript, after first log, before Cypress logs from .then)”
- // (After page loads and h1 is found)
- “Header text from .then(): Login Page Example”
- // (After email is typed)
- “This logs after typing into email field (Cypress queued)”
The key takeaway is that Cypress commands manage their own execution flow. Standard JavaScript (like console.log directly in the test) runs according to JavaScript’s event loop, often before Cypress commands complete.
Promises and Cypress
Section titled “Promises and Cypress”While Cypress commands are ‘promise-like’, they are not native JavaScript Promises. You cannot use async/await directly with cy commands in the same way you would with native Promises without understanding how Cypress handles them. However, .then() allows you to tap into the Cypress command chain, similar to how .then() works with Promises, to execute code once a command’s subject is yielded.
Cypress manages the ‘state’ of commands internally (e.g., pending, resolved, rejected). It automatically retries commands for a certain duration (default: defaultCommandTimeout) if an assertion fails or an element is not found, which greatly contributes to test stability.
If you need to work with actual JavaScript Promises within your Cypress tests (e.g., from an application utility function or a third-party library), you can use cy.wrap() to bring the resolved value of that Promise into the Cypress command chain:
it('Works with a native Promise', () => { const nativePromise = new Promise((resolve) => { setTimeout(() => resolve('Data from Promise'), 100); });
cy.wrap(nativePromise).then((data) => { console.log(data); // 'Data from Promise' expect(data).to.equal('Data from Promise'); });});Avoiding async/await Pitfalls
Section titled “Avoiding async/await Pitfalls”While you can use async/await with Cypress by making your test function async and awaiting cy commands, it’s often unnecessary and can sometimes lead to confusion if not handled correctly. Cypress’s chaining with .then() is usually sufficient and more idiomatic.
// Generally preferred Cypress way:cy.get('input').type('hello').then(() => { cy.get('button').click();});
// Possible with async/await, but be careful:it('Example with async/await', async () => { await cy.get('input').type('hello'); // The 'await' here ensures this command completes before the next JS line, // but the real power of Cypress is its command queue, not traditional async/await. await cy.get('button').click();});The primary benefit of Cypress’s model is that it serializes actions and assertions, waiting for your application to reach a certain state before proceeding. This avoids common flakiness found in other testing tools that rely heavily on manual waits or sleeps.
Further Reading: