The window.postMessage
calls are often used by complex web applications to send messages between different frames. In this blog post, I will show how to confirm the message was sent when using Cypress end-to-end test runner.
The application
đ You can find the source code for this blog post in the repository bahmutov/cypress-window-message-example.
The top document loads its JavaScript and includes an inner iframe element.
1 | <html> |
1 | <html> |
The top window is listening to the messages sent by the inner frame
1 | window.addEventListener('message', (e) => { |
1 | console.log('in the inner script') |
We see the console messages when visiting the page in the regular browser
Let's confirm this communication works.
Make the application work
Let's visit the page from a Cypress test and see if it works
1 | /// <reference types="cypress" /> |
Open Cypress with npx cypress open
... and the message from the inner
frame never shows up âšī¸
The problem happens because the inner frame communicates using window.top.postMessage
method call. When Cypress visits the site, it embeds the site in an iframe. Thus the Cypress is the top window, and it receives the message from the inner
frame.
Hmm, how do we tell the inner
iframe to send the message to the correct application window? Luckily, Cypress can rewrite the window.top
references on the fly to make sure the message gets to the original intended top window
object. In the cypress.json
enable the following flag:
1 | { |
Now we see the message sent during the test.
Ok, let's test it.
Spy on the console.log method call
The top window logs the received messages. Let's confirm one of the console.log
calls prints the message from the inner frame with text "inner frame is ready". We can spy on the console.log
method, see the Stubs, Spies, and Clocks guide.
1 | it('spies on console.log call', () => { |
Notice the placeholder Cypress.sinon.match.string
used during the assertion - we are not interested in the second argument, we only want to find if the console.log
was called with ("TOP:", some string, "inner frame is ready")
parameters. For Sinon spying assertion examples see my Spies and stubs examples page. There are several console.log
calls made by the application, but we confirm only the one that interests us.
Listening to the window.postMessage
What if the top application window does not log the message send by the inner frame? What if we are only interested in the message "inner frame is ready" sent by the inner frame to know that the application is ready to be tested? We can listen to that message from the spec file.
1 | it('receives the window messages', () => { |
The command cy.stub creates a function that we can call ourselves. In the test above, we call the stub function with the message data, which should be just the string "inner frame is ready"
1 | win.addEventListener('message', (e) => { |
By giving our stub an alias, we can conveniently load it again and confirm that at some point it gets called with the expected argument
1 | // confirm the inner frame sent the ready message |
Pretty sweet, isn't it