Imagine you have a parent window that calls window.open. If the parent and child windows are from the same domain, the parent window can check the child window object and "see" when the user closes the child window or tab. A common code is using setInterval to poll the childWindow.closed property.
1 | // parent window |
The parent window might show an overlay, asking the user to work in the child window before closing it.

How would we test this scenario in Cypress?
🎁 You can find the full source code used in this blog post in the repo bahmutov/child-window-closed.
The first test
First, let's stub the window.open to prevent Cypress from "losing" the window under test
1 | it('opens the child window', () => { |

What about closing the window? When the user closes the opened child window, the browser sets the closed: true property. Thus we can set the same property ourselves from the test. First, let me show incorrect way of doing so.
1 | // 🚨 INCORRECT CODE, JUST FOR DEMO |
The test is green, but notice how the overlay is blinking and NOT staying open for 1 second
There is a race condition between checking if the .overlay is visible and setting the mockWindow.closed = true statement. A clear error is shown if we check if the overlay is visible after 1 second delay.
1 | // 🚨 INCORRECT CODE, JUST FOR DEMO |

Let's fix the test. We want to set the mockWindow.closed = true after the second has passed. Thus we use cy.then command.
1 | // ✅ CORRECT TEST |
Now the test shows the correct behavior. The mock window is "opened", the overlay becomes visible and stays for one second, then hides.
Using cy.invoke
In the previous example, we have created a race condition by mixing asynchronous queue of Cypress commands and immediate JavaScript statement mockWindow.closed = true. We solved it by moving the assignment into a Cypress cy.then(callback) command. Let me show you another way of making such assignment using Cypress command cy.invoke to avoid thinking about the order of commands.
1 | it('opens the child window (using cy.invoke)', () => { |
The above test works correctly too.
The key is the statement line cy.wrap(mockWindowController).invoke('close') which makes the assignment from inside the method mockWindowController.close called during Cypress command execution, preserving the order. Unfortunately, we had to create a dummy object "mockWindowController" just to be able to "invoke" mockWindow.closed = true method. Fortunately, there is a shortcut.
Using cy.invoke and Reflect
There is a special JavaScript API Reflect that can be used to interact with objects dynamically. The global Reflect has set(object, method, ...args) method we can use to set the closed property on the mockWindow object right from Cypress cy.invoke.
1 | it('opens the child window (using Reflect.set)', () => { |
The key is the statement cy.wrap(Reflect).invoke('set', mockWindow, 'closed', true) which makes it all work.

Interesting. We cy.wrap(Reflect) to be able to invoke its method .set(...) after all previous commands have finished (checking the overlay, waiting one second, checking if the overlay is still visible). After setting the mockWindow.closed to true, we check the overlay, and yes, the parent window has hidden it.