Normally, Cypress test runner loads your site inside an iframe. This allows the "top" parent window, controlled by Cypress a direct access to your site. Nice, but many sites work hard to avoid being iframed. Cypress already strips X-frame
protection headers, and "fixes" most common frame-busting JavaScript code like if (top !== self)
.
Imagine the website using the following frame-busting code, and it somehow slipping Cypress JS regex
1 | <script> |
The image below shows the frame-busting in action - the site has reference to self
window that is different from the top
window. Also, the menu shows the different JavaScript contexts - one per window object, which is often a source of confusion.
Nothing is foolproof, especially my brain, and having a child iframe for the application under test creates its own confusion. So many times I have opened DevTools, inspecting window
or some global object, and wondering - where is the property I have just set? "Ohh, yeah, it was in the APP context, how could I forget!"
What can we do instead of iframing the application under test website? Cypress needs direct access to the window
that is going to load the site. Child iframe window is one possibility, another one is a window opened with window.open call. As long as the document.domain
values match between the Cypress window and the loaded site, the two windows will be able to communicate. Cypress proxy takes care of setting the document.domain='localhost'
for you, you can see that script injected into the HEAD
element if you inspect the child iframes.
So, let's replace cy.visit
with window.open
. Here is some code I have lifted from the example repo bahmutov/cypress-open-child-window.
Note: I am using Cypress v3.4.1 and Chrome v76 to run this code.
1 | Cypress.Commands.add('openWindow', (url, features) => { |
Note that after we get window reference, we wait 500ms to let the document to load. After that we hope the document.domain
is set to localhost
, allowing our Cypress Test Runner to access it without a security exception.
I am cheating here a little bit. I am using the undocumented cy.state
function that internally stores document
and window
references. But this is a privilege of working on Cypress every day 😁
With the child window accessible, the test runs pretty much normally. For example, here is a test that clicks on the button and checks that the counter is displayed correctly.
1 | it('counts clicks', () => { |
1 | { |
The Chrome windows and Cypress main window are shown below
We can open DevTools in the child window and see that top
reference is the same as self
reference.
The child window has only the elements from the loaded application under test
There is only a single context in the child window, this makes working with JavaScript a little bit simpler.
Since Cypress takes DOM snapshots for its time-traveling debugger, it still works - and the snapshots are shown inside the iframe.
Note: there are probably differences in the way child window is controlled by Cypress. At least if you want to detect from the application code if the site is running inside Cypress, instead of checking window.Cypress
you need to test window.opener && window.opener.Cypress
.
Try it out, and open any issues please.