Imagine a page that uses jQuery plugins. For example, the page might look something like this:
1 | <html> |
We are loading jQuery and the application code. The application code loads the jQuery plugin:
1 | // load the jQuery plugin dynamically |
🎁 You can find the source code for this blog post in the repo bahmutov/cypress-jquery-example.
The jquery.warning.js
plugin extends jQuery with a .warning()
method that alerts the user
1 | jQuery.fn.warning = function () { |
A "normal" end-to-end test could confirm the page calls the alert
twice.
1 | it('checks out the jQuery plugin', () => { |
The test works nicely
Warning: the test does pass locally, but I have noticed this test fail sometimes on CI. Remember: the timings on CI can be slow and unpredictable, thus a hidden race condition between the JS loading and the test clicking might show up.
Slowing down the plugin request
But what happens if the plugin jquery.warning.js
is slow to load? Will our site still work? Probably not! Let's test it. We can slow the network request to load the plugin using the cy.intercept command.
1 | it('delays the jQuery plugin load', () => { |
Cypress automatically fails the test if the application throws an error, or does not handle a rejected promise.
So delaying the plugin source code by 100ms breaks the application, since we click the button immediately.
Tip: do you like the way I delayed loading the request using () => Cypress.Promise.delay(100)
? If yes, you will find my course Cypress Network Testing Exercises pretty useful.
Waiting for the network call
Our test clicks on the button too fast - the application hasn't finished loading its source code yet. The most straightforward way for the test to "know" when to proceed is to wait for that network request to finish.
1 | it('waits for the delayed plugin load', () => { |
Great, we can wait for the network request. Once it finishes, we expect the application to work.
Waiting for the object property
There is another to know when the application is ready to work. Cypress tests run in the browser, thus they have direct visibility into the objects created and modified by the application. How do we know when the application has finished registering the jquery.warning.js
? When the code inside the plugin executes jQuery.fn.warning = function () {
statement. The jQuery
object is a global set on the application's window
object, and a Cypress test can get that object using the cy.window command. Then we can automatically wait for that object to get the property jQuery
and even to get a nested path jQuery.fn.warning
using the cy.its command.
1 | it('waits for the jQuery plugin to register itself', () => { |
The key is the chain cy.visit('index.html').its('jQuery.fn.warning')
. The cy.visit
command yields the application's window
object, and .its('jQuery.fn.warning')
retries until the plugin loads and sets the warning
property on the window.jQuery.fn
object. If you want to be more explicit, you could write a different chain of commands:
1 | cy.visit('index.html') |
Tip: see the cy.its
examples at glebbahmutov.com/cypress-examples.
Tip 2: I would rethink how the application works in this case. A slow network plus a fast-clicking user will get an error. You might want to disable the button until the plugin loads and the button is ready to work without crashing.